Skip to content

Commit 19c3a58

Browse files
committed
async: coroutine engine core, reference schedulers and the coroutine context
The Async Core ABI (zend_async_API.h): coroutines with a packed lifecycle, scheduler slots (launch, enqueue-with-error, suspend, cancel, await), switch/finish handler vectors, the awaiting-info vector, microtasks, and the coroutine context — an embedded internal store for C extensions (process-unique numeric keys) plus the userland zend_async_context_t storage behind a provider-registered class. Fibers run as coroutines under a scheduler; gc_collect_cycles() stays synchronous over a GC coroutine; shutdown drains coroutines gracefully. Two optional in-tree providers validate the ABI from both sides: ext/test_scheduler, the C reference (gated by test_scheduler.enable), and ext/async_scheduler_hook, the PHP bridge — Async\SchedulerHook + Async\Scheduler (onLaunch/onShutdown/onFiber/onEnqueue/onSuspend/onDefer), a coroutine-centric mandate of three hidden capabilities (bindEntry/switchTo/currentCoroutine), explicit main replacement at every end-of-main handover, and the Async\Context / Async\get_context() surface over the engine storage. Tests run both variants in one binary: upstream suites stay byte-for-byte and run schedulerless; scheduler-adapted duplicates and the provider suites opt in.
1 parent 28dab5f commit 19c3a58

81 files changed

Lines changed: 7269 additions & 68 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
GC collects a cycle through a generator parked in Fiber::suspend() on a fiber stack
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public function __destruct() {
8+
echo __METHOD__, "\n";
9+
}
10+
}
11+
12+
function gen() {
13+
$c = new C();
14+
$self = Fiber::getCurrent(); // cycle: fiber -> frames -> $self -> fiber
15+
yield 1;
16+
Fiber::suspend("from gen");
17+
yield 2;
18+
}
19+
20+
$fiber = new Fiber(function () {
21+
$g = gen();
22+
$g->current();
23+
$g->next(); // parks in Fiber::suspend() inside the generator
24+
echo "unreachable\n";
25+
});
26+
27+
$fiber->start();
28+
print "1\n";
29+
30+
// Still referenced: nothing to collect.
31+
gc_collect_cycles();
32+
print "2\n";
33+
34+
$fiber = null;
35+
gc_collect_cycles();
36+
print "3\n";
37+
?>
38+
--EXPECT--
39+
1
40+
2
41+
C::__destruct
42+
3

Zend/zend.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "zend_API.h"
2525
#include "zend_exceptions.h"
2626
#include "zend_builtin_functions.h"
27+
#include "zend_async_API.h"
2728
#include "zend_ini.h"
2829
#include "zend_vm.h"
2930
#include "zend_dtrace.h"
@@ -831,6 +832,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
831832
executor_globals->current_fiber_context = NULL;
832833
executor_globals->main_fiber_context = NULL;
833834
executor_globals->active_fiber = NULL;
835+
memset(&executor_globals->shutdown_context, 0, sizeof(executor_globals->shutdown_context));
834836
#ifdef ZEND_WIN32
835837
zend_get_windows_version_info(&executor_globals->windows_version_info);
836838
#endif
@@ -1979,8 +1981,13 @@ ZEND_API zend_result zend_execute_script(int type, zval *retval, zend_file_handl
19791981
if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) {
19801982
zend_user_exception_handler();
19811983
}
1984+
19821985
if (EG(exception)) {
1983-
ret = zend_exception_error(EG(exception), E_ERROR);
1986+
if (ZEND_ASYNC_CURRENT_COROUTINE == NULL) {
1987+
ret = zend_exception_error(EG(exception), E_ERROR);
1988+
} else {
1989+
ret = FAILURE;
1990+
}
19841991
}
19851992
}
19861993
zend_destroy_static_vars(op_array);

0 commit comments

Comments
 (0)