-
-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dumper::asJsonObjectsMap() produces out of memory error #282
Comments
Why there was memory leaks
So what may go wrong? Example
Dumper::create($var)->asJsonObjectsMap(4)
{
"stdClass#2488": {
"public $objects": [],
"public $id": "lvl1",
"public $lvl2": "object@stdClass#1067" <------- refers to next object
},
"stdClass#1067": {
"public $id": "lvl2",
"public $lvl3": "object@stdClass#2275" <------- refers to next object
},
"stdClass#2275": {
"public $id": "lvl3",
"public $lvl4": "object@stdClass#995" <------- refers to next object
},
"stdClass#995": {
"public $id": "lvl4",
"public $lvl5": { <----------- here must be object reference, but which?
"public $id": "lvl5",
"public $lvl6": {
"public $id": "lvl6",
"public $lvl7": "stdClass#2491 (...)"
}
}
}
} Gotcha. Seems like the last object does not collapse like the rest do. Why? CodebaseI've checked the codebase for if ($objectCollapseLevel < $level && array_key_exists($objectDescription, $this->objects)) {
$output = 'object@' . $objectDescription;
break;
} What do they mean:
So that condition fails if we don't know the object, that's why parsing goes through the object "in depth" and we can see the following example for 100 recursive elements and dumping depth limited to 50: All scanned elements, limited by 50: And dumped in depth the last object, also limited by 50: So everything works great? No really, going deeper... Example 2Imagine the same recursive structure, but with one change: we have two properties that's refers to the same "next" element:
Let's dump it: {
"stdClass#2344": {
"public $objects": [],
"public $id": "lvl1",
"public $prop1": "object@stdClass#773",
"public $prop2": "object@stdClass#773"
},
"stdClass#773": {
"public $id": "lvl2",
"public $prop1": "object@stdClass#2512",
"public $prop2": "object@stdClass#2512"
},
"stdClass#2512": {
"public $id": "lvl3",
"public $prop1": "object@stdClass#2541",
"public $prop2": "object@stdClass#2541"
},
"stdClass#2541": {
"public $id": "lvl4",
"public $prop1": { <------- refers to the object lvl5
"public $id": "lvl5",
"public $prop1": { <------- refers to the object lvl6
"public $id": "lvl6",
"public $prop1": "stdClass#2347 (...)",
"public $prop2": "stdClass#2347 (...)"
},
"public $prop2": { <------- refers to the object lvl6
"public $id": "lvl6",
"public $prop1": "stdClass#2347 (...)",
"public $prop2": "stdClass#2347 (...)"
}
},
"public $prop2": { <------- refers to the object lvl5
"public $id": "lvl5",
"public $prop1": { <------- refers to the object lvl6
"public $id": "lvl6",
"public $prop1": "stdClass#2347 (...)",
"public $prop2": "stdClass#2347 (...)"
},
"public $prop2": { <------- refers to the object lvl6
"public $id": "lvl6",
"public $prop1": "stdClass#2347 (...)",
"public $prop2": "stdClass#2347 (...)"
}
}
}
} Gotcha! Again. So here we can see how the output is doubled and every next level generates a huge flood:
SolutionI think it'd be great to force objects to be collapsed even if we haven't reached them while "warming up" the cache. {
"stdClass#2541": {
"public $id": "lvl4",
"public $prop1": { <------- refers to the object lvl5
"public $id": "lvl5",
"public $prop1": { <------- refers to the object lvl6
"public $id": "lvl6",
"public $prop1": "stdClass#2347 (...)",
"public $prop2": "stdClass#2347 (...)"
},
"public $prop2": "object@ID_OF_LVL6_OBJECT"
},
"public $prop2": "object@ID_OF_LVL5_OBJECT"
}
} Looks great, isn't it? Need time to think about the solution. |
If an object is not in the cache, it means that the object is deeper in the structure than the depth limit. The solution is to show for those objects only their ID: |
This is not |
@olegbaturin try |
Done by #287 |
What steps will reproduce the problem?
run this app https://github.com/olegbaturin/yii3-app-api
Ошибка в алгоритме и довольно нетривиальная, опишу на русском.
yii-debug/src/Dumper.php
Line 62 in 7e1c705
Метод
buildObjectsCache()
проходит на заданную глубину$depth
и строит плоский массив из всей вложенной структуры объектов, который сохраняет в$this->objects
. Алгоритм также отслеживает циклические зависимости вида parent->child->parent.Таким образом оказывается, что объект с заданной максимальной глубины вложенности
$depth
оказывается на первом уровне.yii-debug/src/Dumper.php
Line 59 in 7e1c705
Далее происходит этот вызов и каждый элемент из
$this->objects
в методеasJsonInternal()
еще раз парсится на глубину$depth
. Получается, что элемент из массива$this->objects
с глубины$depth
будет парситься повторно на эту же глубину$depth
, т.е. в итоге2* $depth
.yii-debug/src/Dumper.php
Line 162 in 7e1c705
Ошибка возникает из-за того, что в этой проверке предполагается, что все объекты уже есть в кэшэ, т.е. в массиве
$this->objects
. Это не так, потому что$this->objects
содержит объекты с глубины только$depth
, а парсинг делается на глубину2* $depth
. Также методdumpNestedInternal()
не отслеживает цикличекские зависимостиparent->child->parent
.Если конфигурация приложения содержит циклические зависимости
parent->child->parent
, то происходит цикличкский дамп до глубины2* $depth
без учёта вложенных дублей.Additional info
The text was updated successfully, but these errors were encountered: