@@ -526,6 +526,47 @@ static bool resolve_deadlocks(void)
526526 return false;
527527 }
528528
529+ //
530+ // Let’s count the number of coroutine-fibers that are in the YIELD state.
531+ // This state differs from the regular Suspended state in that
532+ // the Fiber has transferred control back to the parent coroutine.
533+ //
534+ zend_long fiber_coroutines_count = 0 ;
535+
536+ ZEND_HASH_FOREACH_VAL (& ASYNC_G (coroutines ), value ) {
537+ const zend_coroutine_t * coroutine = (zend_coroutine_t * ) Z_PTR_P (value );
538+
539+ if (ZEND_COROUTINE_IS_FIBER (coroutine )
540+ && ZEND_COROUTINE_IS_YIELD (coroutine )
541+ && coroutine -> extended_data != NULL ) {
542+ fiber_coroutines_count ++ ;
543+ }
544+ }
545+ ZEND_HASH_FOREACH_END ();
546+
547+ //
548+ // If all coroutines are fiber coroutines in the SUSPENDED state,
549+ // we can simply cancel them without creating a deadlock exception.
550+ //
551+ if (fiber_coroutines_count == real_coroutines ) {
552+
553+ ZEND_HASH_FOREACH_VAL (& ASYNC_G (coroutines ), value ) {
554+ zend_coroutine_t * coroutine = (zend_coroutine_t * ) Z_PTR_P (value );
555+
556+ if (ZEND_COROUTINE_IS_FIBER (coroutine )
557+ && ZEND_COROUTINE_IS_YIELD (coroutine )
558+ && coroutine -> extended_data != NULL ) {
559+ ZEND_ASYNC_CANCEL (coroutine , zend_create_graceful_exit (), true);
560+
561+ if (UNEXPECTED (EG (exception ) != NULL )) {
562+ return true;
563+ }
564+ }
565+ }
566+ ZEND_HASH_FOREACH_END ();
567+ return false;
568+ }
569+
529570 // Create deadlock exception to be set as exit_exception
530571 zend_object * deadlock_exception = async_new_exception (async_ce_deadlock_error ,
531572 "Deadlock detected: no active coroutines, %u coroutines in waiting" , real_coroutines );
@@ -552,7 +593,7 @@ static bool resolve_deadlocks(void)
552593 ZEND_ASYNC_CANCEL (
553594 & coroutine -> coroutine , async_new_exception (async_ce_cancellation_exception , "Deadlock detected" ), true);
554595
555- if (EG (exception ) != NULL ) {
596+ if (UNEXPECTED ( EG (exception ) != NULL ) ) {
556597 return true;
557598 }
558599 }
@@ -1025,6 +1066,16 @@ bool async_scheduler_coroutine_enqueue(zend_coroutine_t *coroutine)
10251066 // save the filename and line number where the coroutine was created
10261067 zend_apply_current_filename_and_line (& coroutine -> filename , & coroutine -> lineno );
10271068
1069+ // Notify scope that a new coroutine has been enqueued
1070+ zend_async_scope_t * scope = coroutine -> scope ;
1071+
1072+ if (UNEXPECTED (scope == NULL )) {
1073+ // throw error if the coroutine has no scope
1074+ coroutine -> waker -> status = ZEND_ASYNC_WAKER_NO_STATUS ;
1075+ async_throw_error ("The coroutine has no scope assigned" );
1076+ return false;
1077+ }
1078+
10281079 if (UNEXPECTED (zend_hash_index_add_ptr (& ASYNC_G (coroutines ), ((async_coroutine_t * )coroutine )-> std .handle , coroutine ) == NULL )) {
10291080 coroutine -> waker -> status = ZEND_ASYNC_WAKER_IGNORED ;
10301081 async_throw_error ("Failed to add coroutine to the list" );
@@ -1033,8 +1084,6 @@ bool async_scheduler_coroutine_enqueue(zend_coroutine_t *coroutine)
10331084
10341085 ZEND_ASYNC_INCREASE_COROUTINE_COUNT ;
10351086
1036- // Notify scope that a new coroutine has been enqueued
1037- zend_async_scope_t * scope = coroutine -> scope ;
10381087 scope -> after_coroutine_enqueue (coroutine , scope );
10391088 if (UNEXPECTED (EG (exception ))) {
10401089 coroutine -> waker -> status = ZEND_ASYNC_WAKER_IGNORED ;
0 commit comments