diff --git a/Zend/tests/gh13670_001.phpt b/Zend/tests/gh13670_001.phpt new file mode 100644 index 0000000000000..def8fa3f5f25e --- /dev/null +++ b/Zend/tests/gh13670_001.phpt @@ -0,0 +1,43 @@ +--TEST-- +GH-13670 001 +--FILE-- +self = $this; + } + public function __destruct() { + global $shutdown; + if (!$shutdown) { + new Cycle(); + } + } +} + +$defaultThreshold = gc_status()['threshold']; +for ($i = 0; $i < $defaultThreshold+1; $i++) { + new Cycle(); +} + +$objs = []; +for ($i = 0; $i < 100; $i++) { + $obj = new stdClass; + $objs[] = $obj; +} + +$st = gc_status(); + +if ($st['runs'] > 10) { + var_dump($st); +} +?> +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/gh13670_002.phpt b/Zend/tests/gh13670_002.phpt new file mode 100644 index 0000000000000..c822585e3632f --- /dev/null +++ b/Zend/tests/gh13670_002.phpt @@ -0,0 +1,66 @@ +--TEST-- +GH-13670 002 +--FILE-- +self = $this; + } +} + +class Canary { + public $self; + public function __construct() { + $this->self = $this; + } + public function __destruct() { + global $shutdown; + if (!$shutdown) { + work(); + } + } +} + +function work() { + global $objs, $defaultThreshold; + new Canary(); + // Create some collectable garbage so the next run will not adjust + // threshold + for ($i = 0; $i < 100; $i++) { + new Cycle(); + } + // Add potential garbage to buffer + foreach (array_slice($objs, 0, $defaultThreshold) as $obj) { + $o = $obj; + } +} + +$defaultThreshold = gc_status()['threshold']; +$objs = []; +for ($i = 0; $i < $defaultThreshold*2; $i++) { + $obj = new stdClass; + $objs[] = $obj; +} + +work(); + +foreach ($objs as $obj) { + $o = $obj; +} + +$st = gc_status(); + +if ($st['runs'] > 10) { + var_dump($st); +} +?> +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/gh13670_003.phpt b/Zend/tests/gh13670_003.phpt new file mode 100644 index 0000000000000..defd3da438678 --- /dev/null +++ b/Zend/tests/gh13670_003.phpt @@ -0,0 +1,68 @@ +--TEST-- +GH-13670 003 +--FILE-- +self = $this; + } +} + +class Canary { + public $self; + public function __construct() { + $this->self = $this; + } + public function __destruct() { + global $shutdown; + if (!$shutdown) { + work(); + } + } +} + +function work() { + global $objs, $defaultThreshold; + new Canary(); + // Create some collectable garbage so the next run will not adjust + // threshold + for ($i = 0; $i < 100; $i++) { + new Cycle(); + } + // Add potential garbage to buffer + foreach (array_slice($objs, 0, $defaultThreshold) as $obj) { + $o = $obj; + } +} + +$defaultThreshold = gc_status()['threshold']; +$objs = []; +for ($i = 0; $i < $defaultThreshold*2; $i++) { + $obj = new stdClass; + $objs[] = $obj; +} + +work(); + +// Result of array_slice() is a tmpvar that will be checked by +// zend_gc_check_root_tmpvars() +foreach (array_slice($objs, -10) as $obj) { + $o = $obj; +} + +$st = gc_status(); + +if ($st['runs'] > 10) { + var_dump($st); +} +?> +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index e7d9c8ef29257..4417f674cbb91 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -557,7 +557,7 @@ static void gc_adjust_threshold(int count) /* TODO Very simple heuristic for dynamic GC buffer resizing: * If there are "too few" collections, increase the collection threshold * by a fixed step */ - if (count < GC_THRESHOLD_TRIGGER) { + if (count < GC_THRESHOLD_TRIGGER || GC_G(num_roots) >= GC_G(gc_threshold)) { /* increase */ if (GC_G(gc_threshold) < GC_THRESHOLD_MAX) { new_threshold = GC_G(gc_threshold) + GC_THRESHOLD_STEP; @@ -1674,7 +1674,13 @@ ZEND_API int zend_gc_collect_cycles(void) finish: zend_get_gc_buffer_release(); + + /* Prevent GC from running during zend_gc_check_root_tmpvars, before + * gc_threshold is adjusted, as this may result in unbounded recursion */ + GC_G(gc_active) = 1; zend_gc_check_root_tmpvars(); + GC_G(gc_active) = 0; + return total_count; }