@@ -1430,7 +1430,10 @@ static void zend_get_gc_buffer_release(void);
1430
1430
ZEND_API int zend_gc_collect_cycles (void )
1431
1431
{
1432
1432
int count = 0 ;
1433
+ zend_bool should_rerun_gc = 0 ;
1434
+ zend_bool did_rerun_gc = 0 ;
1433
1435
1436
+ rerun_gc :
1434
1437
if (GC_G (num_roots )) {
1435
1438
gc_root_buffer * current , * last ;
1436
1439
zend_refcounted * p ;
@@ -1475,8 +1478,8 @@ ZEND_API int zend_gc_collect_cycles(void)
1475
1478
* be introduced. These references can be introduced in a way that does not
1476
1479
* modify any refcounts, so we have no real way to detect this situation
1477
1480
* 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 ;
1480
1483
1481
1484
/* Mark all roots for which a dtor will be invoked as DTOR_GARBAGE. Additionally
1482
1485
* color them purple. This serves a double purpose: First, they should be
@@ -1609,6 +1612,15 @@ ZEND_API int zend_gc_collect_cycles(void)
1609
1612
}
1610
1613
1611
1614
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
+
1612
1624
zend_get_gc_buffer_release ();
1613
1625
return count ;
1614
1626
}
0 commit comments