in my application I have one instance where I need to inset lots of new records. I noticed that it doesn’t take very long until I reach the memory limit
For testing this, I’ve used my group record which only has id and name columns.
What does the result of your echo statements look like? I just did a test, but couldn’t see the problem. My table has 13 columns. When YII_DEBUG is true, it takes about 500 bytes for each insertion. When YII_DEBUG is false, each new insertion doesn’t increase memory consumption. That means, the incremental memory consumption is caused by the logging. Also, from the code point of view, internally there is no caching or buffering for insertion at the Yii layer. I suspect the memory accumulation occurs at the PDO layer which we have no control on how it manages the memory.
I looked into it some more and I think it is PHP’s circular reference memory leak since the Behavior has an owner property that links back to the model
I don’t really know the details about this. Is there any way how one could manually do some unset() to fix this?
Whenever there is a behavior attached there will be a memory leak unless you call $model->detachBehaviors() before the variable is being unset or loses scope.
That doesn’t help for the memory leak. The __unset was brought up by me, digging into the framework, trying to understand what’s going on. I’m probably on the wrong track.
To conclude what this thread is about: AR objects created in a loop with behaviors automatically attached, leaks memory. An explicit call to detachBehaviors() before unsetting the AR object itself will take care of that, but it seems like there is no easy way to unhook the behavior automatically since __destruct() won’t be called until all references to the AR object are released (the behavior holds at least one reference to the object). The question is: who will call detachBehaviors()?
I highly doubt that there is a way to handle this automatically. The best option is to upgrade to PHP 5.3 since that allegedly fixes the circular references memory leak (didn’t try it yet).
If PHP 5.3 is not an option it gets more difficult and you’ll have to think about where in your code you might instantiate many objects and remember to call detachBehaviors() before they lose scope. I don’t think there is any other way
I agree to your opinion that there’s no automatic solution. Except preventing circular references in the first place. Which seems not to be an option for behaviors.
This kept me thinking. One workaround would be to change Cbehavior and CComponent like this:
// In CBehavior:
public function attach($owner)
{
//$this->_owner=$owner;
foreach($this->events() as $event=>$handler)
$owner->attachEventHandler($event,array($this,$handler));
}
public function attachOwner($owner)
{
$this->_owner=$owner;
}
public function detachOwner($owner)
{
unset($this->_owner);
}
// In CComponent
public function __call($name,$parameters)
{
if($this->_m!==null)
{
foreach($this->_m as $object)
{
if($object->enabled && method_exists($object,$name)) {
$object->attachOwner($this);
$retval= call_user_func_array(array($object,$name),$parameters);
$object->detachOwner();
return $retval;
}
}
}
throw new CException(Yii::t('yii','{class} does not have a method named "{name}".',
array('{class}'=>get_class($this), '{name}'=>$name)));
}
Edit:
This solution misses some logic to also make event handlers in the behavior work that access $this->owner.