[php-src] Issue #9239: Garbage collector not triggered on large number of roots

From: Date: Wed, 03 Aug 2022 09:07:58 +0000
Subject: [php-src] Issue #9239: Garbage collector not triggered on large number of roots
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-242111@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/9239 Author: olsavmic ### Description The following code: ```php <?php declare(strict_types = 1); gc_enable(); class Bar { public ?Foo $foo = null; } class Foo { public Bar $bar; } function ff() { $arr = []; for ($i = 0; $i < 10000000; $i++) { $b = new Bar(); $a = new Foo(); $a->bar = $b; $b->foo = $a; $arr[] = $a; } } ff(); function memory_usage_mb(): float { return memory_get_usage() / 1024 / 1024; } echo "GC Status after first run:\n"; var_dump(gc_status()); echo 'Memory usage: ' . memory_usage_mb() . "MB\n"; ff(); echo "GC Status after second run:\n"; var_dump(gc_status()); echo 'Memory usage: ' . memory_usage_mb() . "MB\n"; gc_collect_cycles(); echo "GC Status after gc_collect_cycles:\n"; var_dump(gc_status()); echo 'Memory usage: ' . memory_usage_mb() . "MB\n"; ff(); echo "GC Status after third run:\n"; var_dump(gc_status()); echo 'Memory usage: ' . memory_usage_mb() . "MB\n"; ``` Resulted in this output: ``` GC Status after first run: array(4) { ["runs"]=> int(81) ["collected"]=> int(19580004) ["threshold"]=> int(440001) ["roots"]=> int(209999) } Memory usage: 278.76216888428MB GC Status after second run: array(4) { ["runs"]=> int(129) ["collected"]=> int(38860006) ["threshold"]=> int(620001) ["roots"]=> int(569998) } Memory usage: 317.21421051025MB GC Status after gc_collect_cycles: array(4) { ["runs"]=> int(130) ["collected"]=> int(40000000) ["threshold"]=> int(620001) ["roots"]=> int(0) } Memory usage: 256.33196258545MB GC Status after third run: array(4) { ["runs"]=> int(168) ["collected"]=> int(58540004) ["threshold"]=> int(760001) ["roots"]=> int(729999) } Memory usage: 334.30416107178MB ``` As you can see, escaping from the first ff() call or calling the ff() function for the second time **did not free the roots of GC even though the GC was supposedly called** (the number of runs increased) although there were more than 209999 roots in the buffer. Calling manually gc_collect_cycles **seems** to clear the root buffer but it actually does not do anything - calling ff() for the third time actually shows total of 729999 roots in the buffer. Perhaps this is somehow related to the issue mentioned in https://www.php.net/manual/en/features.gc.collecting-cycles.php > If the root buffer becomes full with possible roots while the garbage collection mechanism is > turned off, further possible roots will simply not be recorded. Those possible roots that are not > recorded will never be analyzed by the algorithm. If they were part of a circular reference cycle, > they would never be cleaned up and would create a memory leak. The presented case here though has GC enabled all the time, the number of roots seems to be counted and it still leaks memory. ---------------------- After removing the assignment $arr[] = $a; from the ff function, the garbage collector is triggered every 10 000 roots (as documented) and the roots are gone as expected: ``` GC Status after first run: array(4) { ["runs"]=> int(1999) ["collected"]=> int(19990000) ["threshold"]=> int(10001) ["roots"]=> int(10000) } Memory usage: 0.99087524414062MB GC Status after second run: array(4) { ["runs"]=> int(3999) ["collected"]=> int(39990000) ["threshold"]=> int(10001) ["roots"]=> int(10000) } Memory usage: 0.99087524414062MB GC Status after gc_collect_cycles: array(4) { ["runs"]=> int(4000) ["collected"]=> int(40000000) ["threshold"]=> int(10001) ["roots"]=> int(0) } Memory usage: 0.45681762695312MB ``` I got the same output for PHP 7.4, PHP 8.0, and PHP 8.1. ### PHP Version PHP 8.1.7 ### Operating System Debian 10

« previous php.bugs (#242111) next »