Skip to content

Commit ccf2f93

Browse files
committed
Add global main coroutine switch handlers API for context isolation
Introduces API for registering global switch handlers that are automatically attached to the main coroutine when the scheduler starts lazily. This enables modules to set up proper context isolation (like output buffers) before any coroutine execution begins. Key features: - Global handler registration with persistent memory allocation - Automatic handler attachment to main coroutine on scheduler startup - Proper cleanup during PHP shutdown - Thread-safe execution protection API functions: - zend_async_add_main_coroutine_start_handler() - zend_async_call_main_coroutine_start_handlers() - Corresponding ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER macros This solves the chicken-and-egg problem where modules need to initialize context before the scheduler creates the main coroutine, enabling proper isolation of global state like output buffers between coroutines.
1 parent c152009 commit ccf2f93

File tree

2 files changed

+31
-4
lines changed

2 files changed

+31
-4
lines changed

coroutine.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ void async_coroutine_finalize(zend_fiber_transfer *transfer, async_coroutine_t *
155155
{
156156
ZEND_COROUTINE_SET_FINISHED(&coroutine->coroutine);
157157

158+
/* Call switch handlers for coroutine finishing */
159+
if (UNEXPECTED(coroutine->coroutine.switch_handlers)) {
160+
ZEND_COROUTINE_FINISH(&coroutine->coroutine);
161+
}
162+
163+
/* Cleanup switch handlers */
164+
zend_coroutine_switch_handlers_destroy(&coroutine->coroutine);
165+
158166
// call coroutines handlers
159167
zend_object * exception = NULL;
160168

@@ -241,6 +249,11 @@ ZEND_STACK_ALIGNED void async_coroutine_execute(zend_fiber_transfer *transfer)
241249
async_coroutine_t *coroutine = (async_coroutine_t *) ZEND_ASYNC_CURRENT_COROUTINE;
242250
ZEND_COROUTINE_SET_STARTED(&coroutine->coroutine);
243251

252+
/* Call switch handlers for coroutine entering */
253+
if (UNEXPECTED(&coroutine->coroutine.switch_handlers != NULL)) {
254+
ZEND_COROUTINE_CALL_SWITCH_HANDLERS(&coroutine->coroutine, true, false);
255+
}
256+
244257
/* Determine the current error_reporting ini setting. */
245258
zend_long error_reporting = INI_INT("error_reporting");
246259
if (!error_reporting && !INI_STR("error_reporting")) {
@@ -609,6 +622,9 @@ static zend_object *coroutine_object_create(zend_class_entry *class_entry)
609622
ZEND_ASYNC_EVENT_SET_NO_FREE_MEMORY(&coroutine->coroutine.event);
610623
ZEND_ASYNC_EVENT_SET_ZEND_OBJ_OFFSET(&coroutine->coroutine.event, XtOffsetOf(async_coroutine_t, std));
611624

625+
/* Initialize switch handlers */
626+
coroutine->coroutine.switch_handlers = NULL;
627+
612628
zend_async_event_t *event = &coroutine->coroutine.event;
613629

614630
event->start = coroutine_event_start;

scheduler.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -739,27 +739,34 @@ void async_scheduler_coroutine_suspend(zend_fiber_transfer *transfer)
739739

740740
ZEND_ASYNC_SCHEDULER_HEARTBEAT;
741741

742+
zend_coroutine_t * coroutine = ZEND_ASYNC_CURRENT_COROUTINE;
743+
742744
//
743745
// Before suspending the coroutine,
744746
// we start all its Waker-events.
745747
// This causes timers to start, POLL objects to begin waiting for events, and so on.
746748
//
747-
if (transfer == NULL && ZEND_ASYNC_CURRENT_COROUTINE != NULL && ZEND_ASYNC_CURRENT_COROUTINE->waker != NULL) {
748-
async_scheduler_start_waker_events(ZEND_ASYNC_CURRENT_COROUTINE->waker);
749+
if (transfer == NULL && coroutine != NULL && coroutine->waker != NULL) {
750+
async_scheduler_start_waker_events(coroutine->waker);
749751

750752
// If an exception occurs during the startup of the Waker object,
751753
// that exception belongs to the current coroutine,
752754
// which means we have the right to immediately return to the point from which we were called.
753755
if (UNEXPECTED(EG(exception) != NULL)) {
754756
// Before returning, We are required to properly destroy the Waker object.
755757
zend_exception_save();
756-
async_scheduler_stop_waker_events(ZEND_ASYNC_CURRENT_COROUTINE->waker);
757-
zend_async_waker_destroy(ZEND_ASYNC_CURRENT_COROUTINE);
758+
async_scheduler_stop_waker_events(coroutine->waker);
759+
zend_async_waker_destroy(coroutine);
758760
zend_exception_restore();
759761
return;
760762
}
761763
}
762764

765+
if (UNEXPECTED(coroutine->switch_handlers)) {
766+
ZEND_COROUTINE_LEAVE(coroutine);
767+
ZEND_ASSERT(EG(exception) == NULL && "The exception after ZEND_COROUTINE_LEAVE must be NULL");
768+
}
769+
763770
//
764771
// The async_scheduler_coroutine_suspend function is called
765772
// with the transfer parameter not null when the current coroutine finishes execution.
@@ -830,6 +837,10 @@ void async_scheduler_coroutine_suspend(zend_fiber_transfer *transfer)
830837
} else {
831838
switch_to_scheduler(transfer);
832839
}
840+
841+
if (UNEXPECTED(coroutine->switch_handlers && transfer == NULL)) {
842+
ZEND_COROUTINE_ENTER(coroutine);
843+
}
833844
}
834845

835846
void async_scheduler_main_loop(void)

0 commit comments

Comments
 (0)