|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2021-05-10 14:04 UTC] gy dot dlcs at gmail dot com
Description:
------------
Just like the test script, When you Uncomment the `!$wr->get();` line, You will see the memory leaks in a few seconds.
Test script:
---------------
<?php
ini_set('memory_limit', '16M');
while (true) {
$a = new stdClass();
$a->a = $a;
$wr = WeakReference::create($a);
unset($a);
// Uncomment next line, memory will not leak
// $wr->get();
// Uncomment next line, memory will leak
// !$wr->get();
}
Expected result:
----------------
PHP Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 20480 bytes) in test.php on line 9
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Wed Oct 29 01:00:01 2025 UTC |
Okay, I clearly didn't wait long enough last time. There is indeed a leak here, but a very slow one. I used this modified script: <?php ini_set('memory_limit', '8M'); $m = memory_get_usage(); for ($i = 0; ; $i++) { $m2 = memory_get_usage(); if ($m2 > $m) { $m = $m2; echo "$i: ", $m, "\n"; } $a = new stdClass(); $a->a = $a; $wr = WeakReference::create($a); unset($a); // Uncomment next line, memory will not leak //$wr->get(); // Uncomment next line, memory will leak !$wr->get(); } This shows that on every GC cycle (every 10000 iterations) memory usage increases by 560 bytes: ... 5449999: 6453552 5459999: 6454112 5469999: 6454672 5479999: 6455232 ...Simpler reproducer: // We should have enough iterations to trigger a GC run. for ($i = 0; $i < 10000; $i++) { $a = new stdClass(); $a->a = $a; $wr = WeakReference::create($a); unset($a); // The WeakReference::get() result should be used, but immediately destroyed. !$wr->get(); } This will detect the leak: /home/nikic/php/php-src/Zend/zend_objects.c(186) : Freeing 0x00007ffa5bc09c30 (40 bytes), script=/home/nikic/php/php-src/t415.php The problem is as follows: Cycle GC is triggered when destroying the $this of the WeakReference::get() call. At that point the object is stored inside the call return value, so it cannot be GCed. Then the return value gets destroyed, but as it is TMPVAL, the assumption is that it does not need to be rooted. As such, the cycle leaks. We should be able to address this in the same way as the unserialize() return value, which can also return a dead cycle: We need to explicitly root the object in WeakReference::get().