Yii2: Memory leak / database connection stay open

Created on 28 Sep 2018  路  4Comments  路  Source: yiisoft/yii2

This is a complex issue, please read fully & carefully

This problem was found during testing, it is unlikely to cause problems in production environments.
The problem is quite complex and the code below has been simplified to illustrate the problem. Due to this simplification it might seem to not make sense (why not use ExistValidator if we're checking primary keys of another model); please refrain from making suggestions to change my code, I am aware of how to circumvent the problem.

What steps will reproduce the problem?

A number of preconditions exist, here's what I used to reliably reproduce the issue:

class A extends ActiveRecord {
    public function getB() {
        return $this->hasMany(B::class, ['a_id' => 'id'])->indexBy('id');
    }

    public function rules() {
        return [
            [['most_important_a_id'], RangeValidator::class, 'range' => $this->getB()->each()
        ]
    }
}

The test code looks like this:

public function testValidate()
{
    $a = new A();
    $a->save();
    $b = new B();
    $b->a_id = $a->id;
    $b->save();

    // Manually getting the validator so it is clear that the issue is not in the `save()` logic.
    // Since there is only 1 validator is defined we know it is the `RangeValidator`.
    $a->getValidators()[0]->validate($b->id);

    // Unset our references to $a and $b
    unset($a, $b);
    gc_collect_cycles();
}

What is the expected result?

After each execution of the above test no references should exist to the PDO object other than the one in the database component.

What do you get instead?

There is a cycle:

RangeValidator::$range -> BatchQueryResult::$query -> ActiveQuery::$primaryModel -> A::$_validators -> RangeValidator

This cycle causes the BatchQueryResult to never get freed.
Since it stores a reference to DataReader which in turn has a reference to a PDOStatement the underlying PDO connection is never freed.

This cycle is not caught by the php garbage collector.

Further diagnosis

Theoretically the approach used by the PHP to garbage collect cycles should successfully clear this cycle.

  • During testing I made $_validators public and tried manually unsetting it. This breaks the cycle and allows garbage collection to work.
  • Another fix was to override each() in ActiveQuery, have it clone the query and set $primaryModel to null. This breaks the cycle and allows garbage collection to work.

Questions

If you have any questions or if I've not fully explained everything in a clear manner please let me know before drawing conclusions!

Additional info

| Q | A
| ---------------- | ---
| Yii version | dev-master
| PHP version | 7.2.10

db bug

Most helpful comment

All 4 comments

@SamMousa do you think we should create a workaround for it?

Since it has already been patched and the issue is most likely to occur during running of tests I'd say no.

If we can create a workaround that increases code quality I'd be in favor though

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MUTOgen picture MUTOgen  路  3Comments

AstRonin picture AstRonin  路  3Comments

Kolyunya picture Kolyunya  路  3Comments

indicalabs picture indicalabs  路  3Comments

Locustv2 picture Locustv2  路  3Comments