yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.24k stars 6.9k forks source link

Widget garbage collection #14749

Closed klimov-paul closed 7 years ago

klimov-paul commented 7 years ago

What steps will reproduce the problem?

Relates to #14102

Since now widgets may have event handles and behaviors attached to them. This cause a memory leaks while widget usage as widget instance and behavior introduce a cycle reference. Thus any usage of the Widget with attached behavior causes memory leak.

AnyWidget::widget([
    'id' => 'test',
    'as test' => [
        'class' => TestWidgetBehavior::className()
    ],
]);

What is the expected result?

Widget instance to be destroyed. Memory usage stays the same after Widget::widget() or Widget::end().

What do you get instead?

Widget instance remains in memory consuming space without purpose.

Additional info

Q A
Yii version 2.0.12
PHP version *
Operating system *

Unit test to reproduce:

class TestWidget extends Widget
{
    public static $instanceCount = 0;

    public function __construct($config = [])
    {
        parent::__construct($config);
        static::$instanceCount++;
    }

    public function __destruct()
    {
        static::$instanceCount--;
    }
}

class WidgetTest extends TestCase
{
    public function testGc()
    {
        TestWidget::widget([
            'id' => 'test',
            'as test' => [
                'class' => TestWidgetBehavior::className()
            ],
        ]);
        $this->assertSame(0, TestWidget::$instanceCount);
    }
}
klimov-paul commented 7 years ago

ActiveForm and ActiveField also composes a cycle reference, which causes memory leak even without any behavior attached to them.

cebe commented 7 years ago

Even though these will remain in memory for some time, they still should get collected once the GC is running, which will happen at some point in the execution when PHP hits a limit:

http://php.net/manual/en/features.gc.collecting-cycles.php

Only when the root buffer is full does the collection mechanism start for all the different zvals inside.

...

The root buffer has a fixed size of 10,000 possible roots (although you can alter this by changing the GC_ROOT_BUFFER_MAX_ENTRIES constant in Zend/zend_gc.c in the PHP source code, and re-compiling PHP).

So in case PHP needs more memory it will run the GC itself, imo there is no need to Yii to handle this.