Skip to content

Commit b58d745

Browse files
committed
Rerun GC if destructors encountered
Since PHP 7.4 objects that have a destructor require two GC runs to be collected. Currently the collection is delayed to the next automatic GC run. However, in some cases this may result in a large increase in memory usage, as in one of the cases of bug #79519. See also bug #78933 and bug #81117 where the current behavior is unexpected for users. This patch will automatically rerun GC if destructors were encountered. I think this should not have much cost, because it is very likely that objects on which the destructor has been called really are garbage, so the extra GC run should not be doing wasted work. Closes GH-5581.
1 parent af319b0 commit b58d745

File tree

7 files changed

+14
-14
lines changed

7 files changed

+14
-14
lines changed

Zend/tests/gc_011.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ $a->a = $a;
1515
var_dump($a);
1616
unset($a);
1717
var_dump(gc_collect_cycles());
18-
var_dump(gc_collect_cycles());
1918
echo "ok\n"
2019
?>
2120
--EXPECTF--
@@ -24,6 +23,5 @@ object(Foo)#%d (1) {
2423
*RECURSION*
2524
}
2625
__destruct
27-
int(0)
2826
int(1)
2927
ok

Zend/tests/gc_016.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@ $a = new Foo();
1818
$a->a = $a;
1919
unset($a);
2020
var_dump(gc_collect_cycles());
21-
var_dump(gc_collect_cycles());
2221
echo "ok\n"
2322
?>
2423
--EXPECT--
2524
-> int(0)
26-
int(0)
2725
int(2)
2826
ok

Zend/tests/gc_017.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,11 @@ unset($a);
3232
unset($b);
3333
unset($c);
3434
var_dump(gc_collect_cycles());
35-
var_dump(gc_collect_cycles());
3635
echo "ok\n"
3736
?>
3837
--EXPECTF--
3938
string(1) "%s"
4039
string(1) "%s"
4140
string(1) "%s"
42-
int(0)
4341
int(1)
4442
ok

Zend/tests/gc_028.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ $bar->foo = $foo;
2828
unset($foo);
2929
unset($bar);
3030
var_dump(gc_collect_cycles());
31-
var_dump(gc_collect_cycles());
3231
?>
3332
--EXPECT--
34-
int(0)
3533
int(1)

Zend/tests/gc_029.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ $bar->foo = $foo;
3030
unset($foo);
3131
unset($bar);
3232
var_dump(gc_collect_cycles());
33-
var_dump(gc_collect_cycles());
3433
?>
3534
--EXPECT--
36-
int(0)
3735
int(1)

Zend/tests/gc_035.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ $a->x[] = $a;
1919
var_dump(gc_collect_cycles());
2020
unset($a);
2121
var_dump(gc_collect_cycles());
22-
var_dump(gc_collect_cycles());
2322
?>
2423
--EXPECT--
2524
int(0)
26-
int(0)
2725
int(2)

Zend/zend_gc.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,10 @@ static void zend_get_gc_buffer_release(void);
14301430
ZEND_API int zend_gc_collect_cycles(void)
14311431
{
14321432
int count = 0;
1433+
zend_bool should_rerun_gc = 0;
1434+
zend_bool did_rerun_gc = 0;
14331435

1436+
rerun_gc:
14341437
if (GC_G(num_roots)) {
14351438
gc_root_buffer *current, *last;
14361439
zend_refcounted *p;
@@ -1475,8 +1478,8 @@ ZEND_API int zend_gc_collect_cycles(void)
14751478
* be introduced. These references can be introduced in a way that does not
14761479
* modify any refcounts, so we have no real way to detect this situation
14771480
* short of rerunning full GC tracing. What we do instead is to only run
1478-
* destructors at this point, and leave the actual freeing of the objects
1479-
* until the next GC run. */
1481+
* destructors at this point and automatically re-run GC afterwards. */
1482+
should_rerun_gc = 1;
14801483

14811484
/* Mark all roots for which a dtor will be invoked as DTOR_GARBAGE. Additionally
14821485
* color them purple. This serves a double purpose: First, they should be
@@ -1609,6 +1612,15 @@ ZEND_API int zend_gc_collect_cycles(void)
16091612
}
16101613

16111614
gc_compact();
1615+
1616+
/* Objects with destructors were removed from this GC run. Rerun GC right away to clean them
1617+
* up. We do this only once: If we encounter more destructors on the second run, we'll not
1618+
* run GC another time. */
1619+
if (should_rerun_gc && !did_rerun_gc) {
1620+
did_rerun_gc = 1;
1621+
goto rerun_gc;
1622+
}
1623+
16121624
zend_get_gc_buffer_release();
16131625
return count;
16141626
}

0 commit comments

Comments
 (0)