Skip to content

Commit c04774c

Browse files
committed
* Coroutine tests fixed.
Bug in active coroutine counter state fixed. Coroutine finalization code optimized.
1 parent a3ad077 commit c04774c

File tree

6 files changed

+181
-331
lines changed

6 files changed

+181
-331
lines changed

coroutine.c

Lines changed: 99 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -520,112 +520,119 @@ void async_coroutine_finalize(zend_fiber_transfer *transfer, async_coroutine_t *
520520
ZEND_COROUTINE_FINISH(&coroutine->coroutine);
521521
}
522522

523-
/* Cleanup switch handlers */
524-
zend_coroutine_switch_handlers_destroy(&coroutine->coroutine);
523+
bool do_bailout = false;
525524

526-
// call coroutines handlers
527-
zend_object * exception = NULL;
525+
zend_try {
528526

529-
if (EG(exception)) {
530-
if (EG(prev_exception)) {
531-
zend_exception_set_previous(EG(exception), EG(prev_exception));
532-
EG(prev_exception) = NULL;
533-
}
527+
/* Cleanup switch handlers */
528+
zend_coroutine_switch_handlers_destroy(&coroutine->coroutine);
534529

535-
exception = EG(exception);
536-
GC_ADDREF(exception);
530+
// call coroutines handlers
531+
zend_object * exception = NULL;
537532

538-
zend_clear_exception();
533+
if (EG(exception)) {
534+
if (EG(prev_exception)) {
535+
zend_exception_set_previous(EG(exception), EG(prev_exception));
536+
EG(prev_exception) = NULL;
537+
}
539538

540-
if (zend_is_graceful_exit(exception) || zend_is_unwind_exit(exception)) {
541-
OBJ_RELEASE(exception);
542-
exception = NULL;
543-
}
544-
}
539+
exception = EG(exception);
540+
GC_ADDREF(exception);
545541

546-
// Hold the exception inside coroutine if it is not NULL.
547-
if (exception != NULL) {
548-
if (coroutine->coroutine.exception != NULL) {
549-
// If the coroutine already has an exception, we do not overwrite it.
550-
// This is to prevent losing the original exception in case of multiple exceptions.
551-
zend_exception_set_previous(exception, coroutine->coroutine.exception);
542+
zend_clear_exception();
543+
544+
if (zend_is_graceful_exit(exception) || zend_is_unwind_exit(exception)) {
545+
OBJ_RELEASE(exception);
546+
exception = NULL;
547+
}
552548
}
553549

554-
coroutine->coroutine.exception = exception;
555-
GC_ADDREF(exception);
556-
} else if (coroutine->coroutine.exception != NULL) {
557-
// If the coroutine has an exception, we keep it.
558-
exception = coroutine->coroutine.exception;
559-
GC_ADDREF(exception);
560-
}
550+
// Hold the exception inside coroutine if it is not NULL.
551+
if (exception != NULL) {
552+
if (coroutine->coroutine.exception != NULL) {
553+
// If the coroutine already has an exception, we do not overwrite it.
554+
// This is to prevent losing the original exception in case of multiple exceptions.
555+
zend_exception_set_previous(exception, coroutine->coroutine.exception);
556+
}
561557

562-
zend_exception_save();
563-
// Mark second parameter of zend_async_callbacks_notify as ZVAL
564-
ZEND_ASYNC_EVENT_SET_ZVAL_RESULT(&coroutine->coroutine.event);
565-
ZEND_COROUTINE_CLR_EXCEPTION_HANDLED(&coroutine->coroutine);
566-
ZEND_ASYNC_CALLBACKS_NOTIFY(&coroutine->coroutine.event, &coroutine->coroutine.result, exception);
567-
zend_async_callbacks_free(&coroutine->coroutine.event);
558+
coroutine->coroutine.exception = exception;
559+
GC_ADDREF(exception);
560+
} else if (coroutine->coroutine.exception != NULL) {
561+
// If the coroutine has an exception, we keep it.
562+
exception = coroutine->coroutine.exception;
563+
GC_ADDREF(exception);
564+
}
568565

569-
if (coroutine->coroutine.internal_context != NULL) {
570-
zend_async_coroutine_internal_context_dispose(&coroutine->coroutine);
571-
}
566+
zend_exception_save();
567+
// Mark second parameter of zend_async_callbacks_notify as ZVAL
568+
ZEND_ASYNC_EVENT_SET_ZVAL_RESULT(&coroutine->coroutine.event);
569+
ZEND_COROUTINE_CLR_EXCEPTION_HANDLED(&coroutine->coroutine);
570+
ZEND_ASYNC_CALLBACKS_NOTIFY(&coroutine->coroutine.event, &coroutine->coroutine.result, exception);
571+
zend_async_callbacks_free(&coroutine->coroutine.event);
572+
573+
if (coroutine->coroutine.internal_context != NULL) {
574+
zend_async_coroutine_internal_context_dispose(&coroutine->coroutine);
575+
}
572576

573-
// Call finally handlers if any
574-
if (coroutine->finally_handlers != NULL && zend_hash_num_elements(coroutine->finally_handlers) > 0) {
575-
coroutine_call_finally_handlers(coroutine);
576-
}
577+
// Call finally handlers if any
578+
if (coroutine->finally_handlers != NULL && zend_hash_num_elements(coroutine->finally_handlers) > 0) {
579+
coroutine_call_finally_handlers(coroutine);
580+
}
577581

578-
zend_async_waker_destroy(&coroutine->coroutine);
582+
zend_async_waker_destroy(&coroutine->coroutine);
579583

580-
zend_exception_restore();
584+
zend_exception_restore();
581585

582-
// If the exception was handled by any handler, we do not propagate it further.
583-
// Cancellation-type exceptions are considered handled in all cases and are not propagated further.
584-
if (exception != NULL
585-
&& (ZEND_COROUTINE_IS_EXCEPTION_HANDLED(&coroutine->coroutine)
586-
|| instanceof_function(exception->ce, zend_ce_cancellation_exception))) {
587-
OBJ_RELEASE(exception);
588-
exception = NULL;
589-
}
586+
// If the exception was handled by any handler, we do not propagate it further.
587+
// Cancellation-type exceptions are considered handled in all cases and are not propagated further.
588+
if (exception != NULL
589+
&& (ZEND_COROUTINE_IS_EXCEPTION_HANDLED(&coroutine->coroutine)
590+
|| instanceof_function(exception->ce, zend_ce_cancellation_exception))) {
591+
OBJ_RELEASE(exception);
592+
exception = NULL;
593+
}
590594

591-
// Before the exception leads to graceful termination,
592-
// we give one last chance to handle it using Scope handlers.
593-
if (exception != NULL && ZEND_ASYNC_SCOPE_CATCH(
594-
coroutine->coroutine.scope,
595-
&coroutine->coroutine,
596-
NULL,
597-
exception,
598-
false,
599-
ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(coroutine->coroutine.scope)
600-
)) {
601-
OBJ_RELEASE(exception);
602-
exception = NULL;
603-
}
595+
// Before the exception leads to graceful termination,
596+
// we give one last chance to handle it using Scope handlers.
597+
if (exception != NULL && ZEND_ASYNC_SCOPE_CATCH(
598+
coroutine->coroutine.scope,
599+
&coroutine->coroutine,
600+
NULL,
601+
exception,
602+
false,
603+
ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(coroutine->coroutine.scope)
604+
)) {
605+
OBJ_RELEASE(exception);
606+
exception = NULL;
607+
}
604608

605-
// Notify the async scope that the coroutine has finished.
606-
// For the Scheduler, the coroutine's Scope may be undefined.
607-
if (EXPECTED(coroutine->coroutine.scope != NULL)) {
608-
async_scope_notify_coroutine_finished(coroutine);
609-
coroutine->coroutine.scope = NULL;
610-
}
609+
// Notify the async scope that the coroutine has finished.
610+
// For the Scheduler, the coroutine's Scope may be undefined.
611+
if (EXPECTED(coroutine->coroutine.scope != NULL)) {
612+
async_scope_notify_coroutine_finished(coroutine);
613+
coroutine->coroutine.scope = NULL;
614+
}
611615

612-
// Otherwise, we rethrow the exception.
613-
if (exception != NULL) {
614-
zend_throw_exception_internal(exception);
615-
}
616+
// Otherwise, we rethrow the exception.
617+
if (exception != NULL) {
618+
zend_throw_exception_internal(exception);
619+
}
616620

617-
if (EG(exception)) {
618-
if (!(coroutine->flags & ZEND_FIBER_FLAG_DESTROYED)
619-
|| !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))
620-
) {
621-
coroutine->flags |= ZEND_FIBER_FLAG_THREW;
622-
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR;
621+
if (EG(exception)) {
622+
if (!(coroutine->flags & ZEND_FIBER_FLAG_DESTROYED)
623+
|| !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))
624+
) {
625+
coroutine->flags |= ZEND_FIBER_FLAG_THREW;
626+
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR;
623627

624-
ZVAL_OBJ_COPY(&transfer->value, EG(exception));
625-
}
628+
ZVAL_OBJ_COPY(&transfer->value, EG(exception));
629+
}
626630

627-
zend_clear_exception();
628-
}
631+
zend_clear_exception();
632+
}
633+
} zend_catch {
634+
do_bailout = true;
635+
} zend_end_try();
629636

630637
if (EXPECTED(ZEND_ASYNC_SCHEDULER != &coroutine->coroutine)) {
631638
// Permanently remove the coroutine from the Scheduler.
@@ -639,6 +646,10 @@ void async_coroutine_finalize(zend_fiber_transfer *transfer, async_coroutine_t *
639646
ZEND_ASYNC_DECREASE_COROUTINE_COUNT
640647
}
641648
}
649+
650+
if (UNEXPECTED(do_bailout)) {
651+
zend_bailout();
652+
}
642653
}
643654

644655
/**
@@ -701,7 +712,7 @@ ZEND_STACK_ALIGNED void async_coroutine_execute(zend_fiber_transfer *transfer)
701712
ZEND_COROUTINE_SET_STARTED(&coroutine->coroutine);
702713

703714
/* Call switch handlers for coroutine entering */
704-
if (UNEXPECTED(&coroutine->coroutine.switch_handlers != NULL)) {
715+
if (UNEXPECTED(coroutine->coroutine.switch_handlers != NULL)) {
705716
ZEND_COROUTINE_ENTER(&coroutine->coroutine);
706717
}
707718

@@ -753,18 +764,6 @@ ZEND_STACK_ALIGNED void async_coroutine_execute(zend_fiber_transfer *transfer)
753764
} zend_catch {
754765
coroutine->flags |= ZEND_FIBER_FLAG_BAILOUT;
755766
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_BAILOUT;
756-
757-
if (EXPECTED(ZEND_ASYNC_SCHEDULER != &coroutine->coroutine)) {
758-
// Permanently remove the coroutine from the Scheduler.
759-
if (UNEXPECTED(zend_hash_index_del(&ASYNC_G(coroutines), coroutine->std.handle) == FAILURE)) {
760-
async_throw_error("Failed to remove coroutine from the list");
761-
}
762-
763-
// Decrease the active coroutine count if the coroutine is not a zombie.
764-
if (false == ZEND_COROUTINE_IS_ZOMBIE(&coroutine->coroutine)) {
765-
ZEND_ASYNC_DECREASE_COROUTINE_COUNT
766-
}
767-
}
768767
} zend_end_try();
769768

770769
coroutine->context.cleanup = &async_coroutine_cleanup;
@@ -1014,6 +1013,7 @@ void async_coroutine_cancel(zend_coroutine_t *zend_coroutine, zend_object *error
10141013
// In any other case, the cancellation exception overrides the existing exception.
10151014
//
10161015
ZEND_ASYNC_WAKER_APPLY_CANCELLATION(waker, error, transfer_error);
1016+
ZEND_ASYNC_DECREASE_COROUTINE_COUNT;
10171017
async_scheduler_coroutine_enqueue(zend_coroutine);
10181018
return;
10191019
}

scheduler.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,22 @@ static bool resolve_deadlocks(void)
259259
{
260260
zval *value;
261261

262+
const zend_long active_coroutines = ZEND_ASYNC_ACTIVE_COROUTINE_COUNT;
263+
const zend_long real_coroutines = zend_hash_num_elements(&ASYNC_G(coroutines));
264+
265+
if (active_coroutines > real_coroutines) {
266+
async_warning(
267+
"The active coroutine counter contains an incorrect value: %u, real counter: %u.",
268+
active_coroutines, real_coroutines
269+
);
270+
}
271+
272+
if (real_coroutines == 0) {
273+
return false;
274+
}
275+
262276
async_warning(
263-
"no active coroutines, deadlock detected. Coroutines in waiting: %u", ZEND_ASYNC_ACTIVE_COROUTINE_COUNT
277+
"no active coroutines, deadlock detected. Coroutines in waiting: %u", real_coroutines
264278
);
265279

266280
ZEND_HASH_FOREACH_VAL(&ASYNC_G(coroutines), value)
@@ -655,6 +669,7 @@ void async_scheduler_main_coroutine_suspend(void)
655669
switch_to_scheduler(NULL);
656670

657671
ZEND_ASYNC_CURRENT_COROUTINE = NULL;
672+
ZEND_ASSERT(ZEND_ASYNC_ACTIVE_COROUTINE_COUNT == 0 && "The active coroutine counter must be 1 at this point");
658673
ZEND_ASYNC_DEACTIVATE;
659674

660675
if (ASYNC_G(main_transfer)) {

scope.c

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ static bool scope_can_be_disposed(async_scope_t *scope, bool with_zombies, bool
104104
static void scope_check_completion_and_notify(async_scope_t *scope, bool with_zombies);
105105

106106
// Finally handlers execution functions
107-
static void async_scope_call_finally_handlers(async_scope_t *scope);
107+
static bool async_scope_call_finally_handlers(async_scope_t *scope);
108108

109109
// Structure for scope finally handlers context
110110
typedef struct {
@@ -1068,18 +1068,21 @@ static void scope_dispose(zend_async_event_t *scope_event)
10681068
return;
10691069
}
10701070

1071-
ZEND_ASYNC_EVENT_DEL_REF(scope_event);
1071+
if (ZEND_ASYNC_EVENT_REF(scope_event) == 1) {
1072+
ZEND_ASYNC_EVENT_DEL_REF(scope_event);
1073+
}
10721074

10731075
async_scope_t *scope = (async_scope_t *) scope_event;
10741076

10751077
ZEND_ASSERT(scope->coroutines.length == 0 && scope->scope.scopes.length == 0
10761078
&& "Scope should be empty before disposal");
10771079

1078-
if (scope->finally_handlers != NULL && zend_hash_num_elements(scope->finally_handlers) > 0) {
1079-
async_scope_call_finally_handlers(scope);
1080-
if (ZEND_ASYNC_EVENT_REF(scope_event) == 1) {
1081-
return;
1082-
}
1080+
if (scope->finally_handlers != NULL
1081+
&& zend_hash_num_elements(scope->finally_handlers) > 0
1082+
&& async_scope_call_finally_handlers(scope)) {
1083+
// If finally handlers were called, we don't dispose the scope yet
1084+
ZEND_ASYNC_EVENT_ADD_REF(&scope->scope.event);
1085+
return;
10831086
}
10841087

10851088
if (scope->scope.parent_scope) {
@@ -1106,9 +1109,13 @@ static void scope_dispose(zend_async_event_t *scope_event)
11061109
// Free exception handlers
11071110
if (scope->exception_fci != NULL || scope->exception_fcc != NULL) {
11081111
zend_free_fci(scope->exception_fci, scope->exception_fcc);
1112+
scope->exception_fci = NULL;
1113+
scope->exception_fcc = NULL;
11091114
}
11101115
if (scope->child_exception_fci != NULL || scope->child_exception_fcc != NULL) {
11111116
zend_free_fci(scope->child_exception_fci, scope->child_exception_fcc);
1117+
scope->child_exception_fci = NULL;
1118+
scope->child_exception_fcc = NULL;
11121119
}
11131120

11141121
// Free finally handlers
@@ -1379,18 +1386,25 @@ static zend_always_inline bool try_to_handle_exception(
13791386
return false; // Exception not handled
13801387
}
13811388

1382-
static void async_scope_call_finally_handlers(async_scope_t *scope)
1389+
static void async_scope_call_finally_handlers_dtor(finally_handlers_context_t *context)
1390+
{
1391+
zend_async_scope_t *scope = context->target;
1392+
scope->try_to_dispose(scope);
1393+
context->target = NULL;
1394+
}
1395+
1396+
static bool async_scope_call_finally_handlers(async_scope_t *scope)
13831397
{
13841398
if (scope->finally_handlers == NULL || zend_hash_num_elements(scope->finally_handlers) == 0) {
1385-
return;
1399+
return false;
13861400
}
13871401

13881402
HashTable *finally_handlers = scope->finally_handlers;
13891403
scope->finally_handlers = NULL;
13901404
finally_handlers_context_t *finally_context = ecalloc(1, sizeof(finally_handlers_context_t));
13911405
finally_context->target = scope;
13921406
finally_context->scope = &scope->scope;
1393-
finally_context->dtor = NULL;
1407+
finally_context->dtor = async_scope_call_finally_handlers_dtor;
13941408
finally_context->params_count = 1;
13951409

13961410
if (scope->scope.scope_object != NULL) {
@@ -1402,5 +1416,9 @@ static void async_scope_call_finally_handlers(async_scope_t *scope)
14021416
if (false == async_call_finally_handlers(finally_handlers, finally_context, 1)) {
14031417
efree(finally_context);
14041418
zend_array_destroy(finally_handlers);
1419+
return false;
1420+
} else {
1421+
ZEND_ASYNC_EVENT_ADD_REF(&scope->scope.event);
1422+
return true;
14051423
}
14061424
}

0 commit comments

Comments
 (0)