Hello everyone !
I am using Yii 1.1.2 and PHP 5.2.9, and I ran into memory leak problems with relational Active Record.
I searched the Yii forums for "memory leak" and found several other people have similar problems, and several good advices (disabling logging, disabling YII_DEBUG, and removing events/behaviors before deleting the records).
Unfortunately, my problem persisted even when using all these tricks, so I opened a new topic.
How to reproduce :
You need :
-
a CActiveRecord model class with at least one relation
-
PHP < 5.3 (as I guess the 5.3 and latter versions will solve the problem with the new garbage collector)
-
an Action like this one :
public function actionTest() {
for($i = 0; $i<1000; $i++) {
modelClass::model()->with('relation')->findByPk(1);
if(0 == $i%100) {
var_dump(memory_get_peak_usage());
}
}
}
Run the action, and you will see the memory usage climbing.
Note you can also lazy load the relation, and this problem will still appears.
Now, I looked into the Yii source code to try and find the problem.
It seems there are at least two circular references in the CActiveFinder class, which is used to fetch the relations in the relational Active Record Process :
1- The CActiveFinder class references a CJoinElement as its _joinTree property, and this CJoinElement references the CActiveFinder as its model property.
2- The joinTree is composed of several CJoinElement which inter-reference themselves (children and parent properties).
I wrote a little hack resolving the problem :
In CActiveFinder.php :
Replace CactiveFinder::query() by
public function query($criteria,$all=false)
{
$this->_joinTree->model->applyScopes($criteria);
$this->_joinTree->beforeFind();
$alias=$criteria->alias===null ? 't' : $criteria->alias;
$this->_joinTree->tableAlias=$alias;
$this->_joinTree->rawTableAlias=$this->_builder->getSchema()->quoteTableName($alias);
$this->_joinTree->find($criteria);
$this->_joinTree->afterFind();
if($all)
$result = array_values($this->_joinTree->records);
else if(count($this->_joinTree->records))
$result = reset($this->_joinTree->records);
else
$result = null;
$this->_joinTree = null; // <---- resolves the 1st circular reference
return $result;
}
And CJoinElement::afterFind() by :
public function afterFind()
{
foreach($this->records as $record)
$record->afterFindInternal();
foreach($this->children as $child)
$child->afterFind();
$this->children = null; // <---- resolves the 2nd circular reference
}
With these changes, the memory leak disappears
The downside of this solution is that it becomes impossible to perform several find() with a single CActiveFinder instance (but I don’t know if it may be usefull or not, and if it was even possible ^^).
If you find a better/cleaner solution, or foresee problems that may arise by using this “fix”, I would be really interested in hearing your thoughts
Hope this can help someone