diff --git a/Zend/tests/fibers/gh10134-internal-call-args.phpt b/Zend/tests/fibers/gh10134-internal-call-args.phpt new file mode 100644 index 000000000000..b954ef6a9ddb --- /dev/null +++ b/Zend/tests/fibers/gh10134-internal-call-args.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-10134 (GC sees the arguments of an internal call a fiber is suspended inside) +--FILE-- +start(); + +$fiber = null; +$canary = null; +var_dump(gc_collect_cycles() > 0); + +var_dump('Shutdown'); + +?> +--EXPECT-- +string(11) "Canary dtor" +bool(true) +string(8) "Shutdown" diff --git a/Zend/tests/fibers/gh10134.phpt b/Zend/tests/fibers/gh10134.phpt new file mode 100644 index 000000000000..04a3b092005a --- /dev/null +++ b/Zend/tests/fibers/gh10134.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-10134 (Non-suspended generators in suspended Fiber do not participate in GC) +--FILE-- +x); + } +} + +$gen = (function() { + $canary = new Canary('Generator dtor'); + $fiber = yield; + Fiber::suspend(); +})(); + +$fiber = new Fiber(function() use ($gen, &$fiber) { + $canary = new Canary('Fiber dtor'); + $gen->send($fiber); +}); +$fiber->start(); + +$gen = null; +$fiber = null; +gc_collect_cycles(); + +var_dump('Shutdown'); + +?> +--EXPECT-- +string(14) "Generator dtor" +string(10) "Fiber dtor" +string(8) "Shutdown" diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 14a340ffee37..5809c65cd8b8 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -5012,6 +5012,17 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d if (!ZEND_USER_CODE(EX(func)->common.type)) { ZEND_ASSERT(!(EX_CALL_INFO() & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))); + /* An internal function frame owns the arguments that were pushed for it and releases + * them when it returns. Execution can be suspended inside such a call: a fiber that + * suspends below an internal function leaves that frame on its stack, e.g. + * Fiber::suspend() reached through Generator::send($arg). The arguments are then still + * live, so they have to be reported or a cycle running through one of them is never + * collected. Entered calls are removed from the caller's EX(call) chain, so these are + * not also reported by zend_unfinished_calls_gc(). */ + uint32_t num_args = ZEND_CALL_NUM_ARGS(execute_data); + for (uint32_t i = 0; i < num_args; i++) { + zend_get_gc_buffer_add_zval(gc_buffer, ZEND_CALL_VAR_NUM(execute_data, i)); + } return NULL; }