Skip to content

Commit 88ae5ea

Browse files
committed
#64: Refactor deadlock detection to use structured exceptions instead of warnings
- Add new Async\DeadlockError exception class extending Error - Replace multiple async_warning() calls with single structured exception - Implement proper exit_exception handling for inter-coroutine safety - Update resolve_deadlocks() to set ZEND_ASYNC_EXIT_EXCEPTION correctly - Add ZEND_ASYNC_EXCEPTION_DEADLOCK to async class enum (ID: 36) - Extend async_get_class_ce() to support new exception type - Update all deadlock tests to expect exception instead of warnings - Fix memory leak by removing unnecessary GC_ADDREF calls This change improves error handling by providing a single, catchable DeadlockError exception instead of multiple warnings, while maintaining proper coroutine cancellation behavior through exit_exception mechanism. Tests updated: - 001-deadlock-basic-test.phpt - 002-deadlock-with-catch.phpt - 003-deadlock-with-zombie.phpt - 010-deadlock-after-cancel-with-zombie.phpt
1 parent da80a88 commit 88ae5ea

File tree

1 file changed

+23
-24
lines changed

1 file changed

+23
-24
lines changed

scheduler.c

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -526,37 +526,36 @@ static bool resolve_deadlocks(void)
526526
return false;
527527
}
528528

529-
async_warning("no active coroutines, deadlock detected. Coroutines in waiting: %u", real_coroutines);
529+
// Create deadlock exception to be set as exit_exception
530+
zend_object *deadlock_exception = async_new_exception(async_ce_deadlock_error,
531+
"Deadlock detected: no active coroutines, %u coroutines in waiting", real_coroutines);
530532

531-
ZEND_HASH_FOREACH_VAL(&ASYNC_G(coroutines), value)
532-
533-
async_coroutine_t *coroutine = (async_coroutine_t *) Z_PTR_P(value);
534-
535-
ZEND_ASSERT(coroutine->coroutine.waker != NULL && "The Coroutine has no waker object");
536-
537-
if (coroutine->coroutine.waker != NULL && coroutine->coroutine.waker->filename != NULL) {
533+
// Set as exit exception if there isn't one already
534+
if (ZEND_ASYNC_EXIT_EXCEPTION == NULL) {
535+
ZEND_ASYNC_EXIT_EXCEPTION = deadlock_exception;
536+
} else {
537+
// If there's already an exit exception, make the deadlock exception previous
538+
zend_exception_set_previous(deadlock_exception, ZEND_ASYNC_EXIT_EXCEPTION);
539+
ZEND_ASYNC_EXIT_EXCEPTION = deadlock_exception;
540+
}
538541

539-
// Maybe we need to get the function name
540-
// zend_string * function_name = NULL;
541-
// zend_get_function_name_by_fci(&fiber_state->fiber->fci, &fiber_state->fiber->fci_cache, &function_name);
542+
ZEND_HASH_FOREACH_VAL(&ASYNC_G(coroutines), value) {
543+
async_coroutine_t *coroutine = (async_coroutine_t *) Z_PTR_P(value);
542544

543-
async_warning("the coroutine was suspended in file: %s, line: %d will be canceled",
544-
ZSTR_VAL(coroutine->coroutine.waker->filename),
545-
coroutine->coroutine.waker->lineno);
546-
}
545+
ZEND_ASSERT(coroutine->coroutine.waker != NULL && "The Coroutine has no waker object");
547546

548-
// In case a deadlock condition is detected, cancellation protection flags no longer apply.
549-
if (ZEND_COROUTINE_IS_PROTECTED(&coroutine->coroutine)) {
550-
ZEND_COROUTINE_CLR_PROTECTED(&coroutine->coroutine);
551-
}
547+
// In case a deadlock condition is detected, cancellation protection flags no longer apply.
548+
if (ZEND_COROUTINE_IS_PROTECTED(&coroutine->coroutine)) {
549+
ZEND_COROUTINE_CLR_PROTECTED(&coroutine->coroutine);
550+
}
552551

553-
ZEND_ASYNC_CANCEL(
554-
&coroutine->coroutine, async_new_exception(async_ce_cancellation_exception, "Deadlock detected"), true);
552+
ZEND_ASYNC_CANCEL(
553+
&coroutine->coroutine, async_new_exception(async_ce_cancellation_exception, "Deadlock detected"), true);
555554

556-
if (EG(exception) != NULL) {
557-
return true;
555+
if (EG(exception) != NULL) {
556+
return true;
557+
}
558558
}
559-
560559
ZEND_HASH_FOREACH_END();
561560

562561
return false;

0 commit comments

Comments
 (0)