From a31c95ff18fe0c9718209866dfcf5f31effff3e9 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 5 Jul 2025 09:41:38 +0300 Subject: [PATCH 1/2] #8: Add comprehensive bailout tests for TrueAsync API This commit introduces 15 new tests that verify the robustness of the TrueAsync API when handling critical PHP failures (bailout scenarios). Test categories: - Memory exhaustion in simple and nested async operations - Stack overflow in simple and nested async operations - Bailout during suspend/resume operations - Bailout during await/awaitAll operations - Bailout with multiple concurrent coroutines - Bailout in shutdown functions - Bailout with onFinally handlers and exception handling These tests ensure that: - Memory and stack overflow are properly handled in async contexts - Graceful shutdown mode is correctly initiated during bailout - onFinally handlers execute properly even during critical failures - Resource cleanup mechanisms work correctly under extreme conditions - Exception handlers process composite exceptions from finally blocks All tests follow the established naming pattern (001-015) and include proper expectation handling for Fatal errors and graceful shutdown warnings. --- CHANGELOG.md | 1 + async_API.c | 7 +-- coroutine.c | 52 +++++++++++------ exceptions.c | 45 +++++++++++++-- scheduler.c | 4 +- scope.c | 43 +++++++++++++- .../bailout/001-memory-exhaustion-simple.phpt | 33 +++++++++++ .../bailout/002-memory-exhaustion-nested.phpt | 41 +++++++++++++ tests/bailout/003-stack-overflow-simple.phpt | 35 ++++++++++++ tests/bailout/004-stack-overflow-nested.phpt | 43 ++++++++++++++ .../005-memory-exhaustion-during-suspend.phpt | 42 ++++++++++++++ ...memory-exhaustion-multiple-coroutines.phpt | 46 +++++++++++++++ .../007-stack-overflow-with-suspend.phpt | 44 ++++++++++++++ .../008-memory-exhaustion-in-shutdown.phpt | 39 +++++++++++++ .../009-memory-exhaustion-during-await.phpt | 37 ++++++++++++ ...010-memory-exhaustion-during-awaitAll.phpt | 48 ++++++++++++++++ .../011-stack-overflow-during-await.phpt | 39 +++++++++++++ ...12-memory-exhaustion-awaiting-process.phpt | 46 +++++++++++++++ .../013-memory-exhaustion-with-finally.phpt | 47 +++++++++++++++ .../014-stack-overflow-with-finally.phpt | 44 ++++++++++++++ ...5-memory-exhaustion-finally-exception.phpt | 57 +++++++++++++++++++ 21 files changed, 762 insertions(+), 31 deletions(-) create mode 100644 tests/bailout/001-memory-exhaustion-simple.phpt create mode 100644 tests/bailout/002-memory-exhaustion-nested.phpt create mode 100644 tests/bailout/003-stack-overflow-simple.phpt create mode 100644 tests/bailout/004-stack-overflow-nested.phpt create mode 100644 tests/bailout/005-memory-exhaustion-during-suspend.phpt create mode 100644 tests/bailout/006-memory-exhaustion-multiple-coroutines.phpt create mode 100644 tests/bailout/007-stack-overflow-with-suspend.phpt create mode 100644 tests/bailout/008-memory-exhaustion-in-shutdown.phpt create mode 100644 tests/bailout/009-memory-exhaustion-during-await.phpt create mode 100644 tests/bailout/010-memory-exhaustion-during-awaitAll.phpt create mode 100644 tests/bailout/011-stack-overflow-during-await.phpt create mode 100644 tests/bailout/012-memory-exhaustion-awaiting-process.phpt create mode 100644 tests/bailout/013-memory-exhaustion-with-finally.phpt create mode 100644 tests/bailout/014-stack-overflow-with-finally.phpt create mode 100644 tests/bailout/015-memory-exhaustion-finally-exception.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f0dffd..55879d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.3.0] - TBD ### Added +- **Bailout Tests**: Added 15 tests covering memory exhaustion and stack overflow scenarios in async operations - **Garbage Collection Support**: Implemented comprehensive GC handlers for async objects - Added `async_coroutine_object_gc()` function to track all ZVALs in coroutine structures - Added `async_scope_object_gc()` function to track ZVALs in scope structures diff --git a/async_API.c b/async_API.c index 87481ff..90b1164 100644 --- a/async_API.c +++ b/async_API.c @@ -196,11 +196,6 @@ zend_coroutine_t *spawn(zend_async_scope_t *scope, zend_object * scope_provider, return &coroutine->coroutine; } -static void graceful_shutdown(void) -{ - start_graceful_shutdown(); -} - static void engine_shutdown(void) { ZEND_ASYNC_REACTOR_SHUTDOWN(); @@ -906,7 +901,7 @@ void async_api_register(void) async_coroutine_resume, async_coroutine_cancel, async_spawn_and_throw, - graceful_shutdown, + start_graceful_shutdown, get_coroutines, add_microtask, get_awaiting_info, diff --git a/coroutine.c b/coroutine.c index c91e7fc..e7452df 100644 --- a/coroutine.c +++ b/coroutine.c @@ -626,31 +626,31 @@ void async_coroutine_finalize(zend_fiber_transfer *transfer, async_coroutine_t * async_rethrow_exception(exception); } - if (EG(exception)) { - if (!(coroutine->flags & ZEND_FIBER_FLAG_DESTROYED) - || !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception))) - ) { - coroutine->flags |= ZEND_FIBER_FLAG_THREW; - transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR; - - ZVAL_OBJ_COPY(&transfer->value, EG(exception)); - } - - zend_clear_exception(); - } } zend_catch { do_bailout = true; } zend_end_try(); + if (UNEXPECTED(EG(exception))) { + if (!(coroutine->flags & ZEND_FIBER_FLAG_DESTROYED) + || !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception))) + ) { + coroutine->flags |= ZEND_FIBER_FLAG_THREW; + transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR; + + ZVAL_OBJ_COPY(&transfer->value, EG(exception)); + } + + zend_clear_exception(); + } + if (EXPECTED(ZEND_ASYNC_SCHEDULER != &coroutine->coroutine)) { // Permanently remove the coroutine from the Scheduler. if (UNEXPECTED(zend_hash_index_del(&ASYNC_G(coroutines), coroutine->std.handle) == FAILURE)) { zend_error(E_CORE_ERROR, "Failed to remove coroutine from the list"); } - // Decrease the active coroutine count if the coroutine is not a zombie and is started. - if (ZEND_COROUTINE_IS_STARTED(&coroutine->coroutine) - && false == ZEND_COROUTINE_IS_ZOMBIE(&coroutine->coroutine)) { + // Decrease the active coroutine count if the coroutine is not a zombie. + if (false == ZEND_COROUTINE_IS_ZOMBIE(&coroutine->coroutine)) { ZEND_ASYNC_DECREASE_COROUTINE_COUNT } } @@ -731,6 +731,7 @@ ZEND_STACK_ALIGNED void async_coroutine_execute(zend_fiber_transfer *transfer) } EG(vm_stack) = NULL; + bool should_start_graceful_shutdown = false; zend_first_try { zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL); @@ -767,16 +768,31 @@ ZEND_STACK_ALIGNED void async_coroutine_execute(zend_fiber_transfer *transfer) coroutine->coroutine.internal_entry(); } - async_coroutine_finalize(transfer, coroutine); + } zend_catch { + coroutine->flags |= ZEND_FIBER_FLAG_BAILOUT; + transfer->flags = ZEND_FIBER_TRANSFER_FLAG_BAILOUT; + should_start_graceful_shutdown = true; + } zend_end_try(); + zend_first_try { + async_coroutine_finalize(transfer, coroutine); } zend_catch { coroutine->flags |= ZEND_FIBER_FLAG_BAILOUT; transfer->flags = ZEND_FIBER_TRANSFER_FLAG_BAILOUT; + should_start_graceful_shutdown = true; } zend_end_try(); coroutine->context.cleanup = &async_coroutine_cleanup; coroutine->vm_stack = EG(vm_stack); + if (UNEXPECTED(should_start_graceful_shutdown)) { + zend_first_try { + ZEND_ASYNC_SHUTDOWN(); + } zend_catch { + zend_error(E_CORE_WARNING, "A critical error was detected during the initiation of the graceful shutdown mode."); + } zend_end_try(); + } + // // The scheduler coroutine always terminates into the main execution flow. // @@ -787,7 +803,9 @@ ZEND_STACK_ALIGNED void async_coroutine_execute(zend_fiber_transfer *transfer) if (transfer != ASYNC_G(main_transfer)) { if (UNEXPECTED(Z_TYPE(transfer->value) == IS_OBJECT)) { - zval_ptr_dtor(&transfer->value); + zend_first_try { + zval_ptr_dtor(&transfer->value); + } zend_end_try(); zend_error(E_CORE_WARNING, "The transfer value must be NULL when the main coroutine is resumed"); } diff --git a/exceptions.c b/exceptions.c index 0a6ddbe..0d7e469 100644 --- a/exceptions.c +++ b/exceptions.c @@ -104,7 +104,15 @@ ZEND_API ZEND_COLD zend_object * async_throw_error(const char *format, ...) zend_string *message = zend_vstrpprintf(0, format, args); va_end(args); - zend_object *obj = zend_throw_exception(async_ce_async_exception, ZSTR_VAL(message), 0); + zend_object *obj = NULL; + + if (EXPECTED(EG(current_execute_data))) { + obj = zend_throw_exception(async_ce_async_exception, ZSTR_VAL(message), 0); + } else { + obj = async_new_exception(async_ce_async_exception, ZSTR_VAL(message)); + async_apply_exception_to_context(obj); + } + zend_string_release(message); return obj; } @@ -124,7 +132,14 @@ ZEND_API ZEND_COLD zend_object * async_throw_cancellation(const char *format, .. va_list args; va_start(args, format); - zend_object *obj = zend_throw_exception_ex(async_ce_cancellation_exception, 0, format, args); + zend_object *obj = NULL; + + if (EXPECTED(EG(current_execute_data))) { + obj = zend_throw_exception_ex(async_ce_cancellation_exception, 0, format, args); + } else { + obj = async_new_exception(async_ce_cancellation_exception, format, args); + async_apply_exception_to_context(obj); + } va_end(args); return obj; @@ -137,7 +152,14 @@ ZEND_API ZEND_COLD zend_object * async_throw_input_output(const char *format, .. va_list args; va_start(args, format); - zend_object *obj = zend_throw_exception_ex(async_ce_input_output_exception, 0, format, args); + zend_object *obj = NULL; + + if (EXPECTED(EG(current_execute_data))) { + obj = zend_throw_exception_ex(async_ce_input_output_exception, 0, format, args); + } else { + obj = async_new_exception(async_ce_input_output_exception, format, args); + async_apply_exception_to_context(obj); + } va_end(args); return obj; @@ -147,7 +169,13 @@ ZEND_API ZEND_COLD zend_object * async_throw_timeout(const char *format, const z { format = format ? format : "A timeout of %u microseconds occurred"; - return zend_throw_exception_ex(async_ce_timeout_exception, 0, format, timeout); + if (EXPECTED(EG(current_execute_data))) { + return zend_throw_exception_ex(async_ce_timeout_exception, 0, format, timeout); + } else { + zend_object *obj = async_new_exception(async_ce_timeout_exception, format, timeout); + async_apply_exception_to_context(obj); + return obj; + } } ZEND_API ZEND_COLD zend_object * async_throw_poll(const char *format, ...) @@ -155,7 +183,14 @@ ZEND_API ZEND_COLD zend_object * async_throw_poll(const char *format, ...) va_list args; va_start(args, format); - zend_object *obj = zend_throw_exception_ex(async_ce_poll_exception, 0, format, args); + zend_object *obj = NULL; + + if (EXPECTED(EG(current_execute_data))) { + obj = zend_throw_exception_ex(async_ce_poll_exception, 0, format, args); + } else { + obj = async_new_exception(async_ce_poll_exception, format, args); + async_apply_exception_to_context(obj); + } va_end(args); return obj; diff --git a/scheduler.c b/scheduler.c index 0f52b08..10d6618 100644 --- a/scheduler.c +++ b/scheduler.c @@ -458,7 +458,7 @@ void start_graceful_shutdown(void) // If the exit exception is not defined, we will define it. if (EG(exception) == NULL && ZEND_ASYNC_EXIT_EXCEPTION == NULL) { - async_throw_error("Graceful shutdown mode is activated"); + zend_error(E_CORE_WARNING, "Graceful shutdown mode was started"); } if (EG(exception) != NULL) { @@ -676,7 +676,7 @@ void async_scheduler_main_coroutine_suspend(void) } zend_end_try(); ZEND_ASYNC_CURRENT_COROUTINE = NULL; - ZEND_ASSERT(ZEND_ASYNC_ACTIVE_COROUTINE_COUNT == 0 && "The active coroutine counter must be 1 at this point"); + ZEND_ASSERT(ZEND_ASYNC_ACTIVE_COROUTINE_COUNT == 0 && "The active coroutine counter must be 0 at this point"); ZEND_ASYNC_DEACTIVATE; if (ASYNC_G(main_transfer)) { diff --git a/scope.c b/scope.c index 4f4e254..9814047 100644 --- a/scope.c +++ b/scope.c @@ -1412,12 +1412,29 @@ static zend_always_inline bool try_to_handle_exception( } } + async_scope_object_t *scope_object = NULL; + + if (UNEXPECTED(current_scope->scope.scope_object == NULL)) { + // The PHP Scope object might already be destroyed by the time the internal Scope still exists. + // To normalize this situation, we’ll create a fake Scope object that will serve as a bridge. + scope_object = ZEND_OBJECT_ALLOC_EX(sizeof(async_scope_object_t), async_ce_scope); + zend_object_std_init(&scope_object->std, async_ce_scope); + object_properties_init(&scope_object->std, async_ce_scope); + + if (UNEXPECTED(EG(exception))) { + OBJ_RELEASE(&scope_object->std); + return false; + } + + GC_ADDREF(&scope_object->std); + scope_object->scope = current_scope; + } // Prototype: function (Async\Scope $scope, Async\Coroutine $coroutine, Throwable $e) zval retval; zval parameters[3]; ZVAL_UNDEF(&retval); - ZVAL_OBJ(¶meters[0], current_scope->scope.scope_object); + ZVAL_OBJ(¶meters[0], scope_object != NULL ? &scope_object->std : current_scope->scope.scope_object); ZVAL_OBJ(¶meters[1], &coroutine->std); ZVAL_OBJ(¶meters[2], exception); @@ -1438,6 +1455,14 @@ static zend_always_inline bool try_to_handle_exception( exception_fci->params = NULL; if (result == SUCCESS && EG(exception) == NULL) { + if (UNEXPECTED(scope_object != NULL)) { + scope_object->scope = NULL; // Clear reference to avoid double release + if (GC_REFCOUNT(&scope_object->std) > 1) { + GC_DELREF(&scope_object->std); + } + OBJ_RELEASE(&scope_object->std); + } + return true; } } @@ -1458,10 +1483,26 @@ static zend_always_inline bool try_to_handle_exception( exception_fci->params = NULL; if (result == SUCCESS && EG(exception) == NULL) { + if (UNEXPECTED(scope_object != NULL)) { + scope_object->scope = NULL; + if (GC_REFCOUNT(&scope_object->std) > 1) { + GC_DELREF(&scope_object->std); + } + OBJ_RELEASE(&scope_object->std); + } + return true; } } + if (UNEXPECTED(scope_object != NULL)) { + scope_object->scope = NULL; + if (GC_REFCOUNT(&scope_object->std) > 1) { + GC_DELREF(&scope_object->std); + } + OBJ_RELEASE(&scope_object->std); + } + return false; // Exception not handled } diff --git a/tests/bailout/001-memory-exhaustion-simple.phpt b/tests/bailout/001-memory-exhaustion-simple.phpt new file mode 100644 index 0000000..bf981eb --- /dev/null +++ b/tests/bailout/001-memory-exhaustion-simple.phpt @@ -0,0 +1,33 @@ +--TEST-- +Memory exhaustion bailout in simple async operation +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Before spawn +After spawn +Before memory exhaustion + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/002-memory-exhaustion-nested.phpt b/tests/bailout/002-memory-exhaustion-nested.phpt new file mode 100644 index 0000000..d5bcc9c --- /dev/null +++ b/tests/bailout/002-memory-exhaustion-nested.phpt @@ -0,0 +1,41 @@ +--TEST-- +Memory exhaustion bailout in nested async operations +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Before spawn +After spawn +Outer async started +Outer async continues +Inner async started + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/003-stack-overflow-simple.phpt b/tests/bailout/003-stack-overflow-simple.phpt new file mode 100644 index 0000000..7b6af05 --- /dev/null +++ b/tests/bailout/003-stack-overflow-simple.phpt @@ -0,0 +1,35 @@ +--TEST-- +Stack overflow bailout in simple async operation +--FILE-- + +--EXPECTF-- +Before spawn +After spawn +Before stack overflow + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/004-stack-overflow-nested.phpt b/tests/bailout/004-stack-overflow-nested.phpt new file mode 100644 index 0000000..c164361 --- /dev/null +++ b/tests/bailout/004-stack-overflow-nested.phpt @@ -0,0 +1,43 @@ +--TEST-- +Stack overflow bailout in nested async operations +--FILE-- + +--EXPECTF-- +Before spawn +After spawn +Outer async started +Outer async continues +Inner async started + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/005-memory-exhaustion-during-suspend.phpt b/tests/bailout/005-memory-exhaustion-during-suspend.phpt new file mode 100644 index 0000000..fb0a083 --- /dev/null +++ b/tests/bailout/005-memory-exhaustion-during-suspend.phpt @@ -0,0 +1,42 @@ +--TEST-- +Memory exhaustion bailout during suspend operation +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Before spawn +After spawn +Before suspend +Other coroutine running +After suspend + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/006-memory-exhaustion-multiple-coroutines.phpt b/tests/bailout/006-memory-exhaustion-multiple-coroutines.phpt new file mode 100644 index 0000000..30a6cca --- /dev/null +++ b/tests/bailout/006-memory-exhaustion-multiple-coroutines.phpt @@ -0,0 +1,46 @@ +--TEST-- +Memory exhaustion bailout with multiple coroutines consuming memory +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Before spawn +After spawn +Coroutine 1 started +Coroutine 1 allocated 500KB +Coroutine 2 started + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/007-stack-overflow-with-suspend.phpt b/tests/bailout/007-stack-overflow-with-suspend.phpt new file mode 100644 index 0000000..a4bbb35 --- /dev/null +++ b/tests/bailout/007-stack-overflow-with-suspend.phpt @@ -0,0 +1,44 @@ +--TEST-- +Stack overflow bailout with suspend in recursion +--FILE-- + +--EXPECTF-- +Before spawn +After spawn +Before stack overflow with suspend +Other coroutine running + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/008-memory-exhaustion-in-shutdown.phpt b/tests/bailout/008-memory-exhaustion-in-shutdown.phpt new file mode 100644 index 0000000..95273c9 --- /dev/null +++ b/tests/bailout/008-memory-exhaustion-in-shutdown.phpt @@ -0,0 +1,39 @@ +--TEST-- +Memory exhaustion bailout in shutdown function with async +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Before spawn +Script ending +Regular async operation +Shutdown function started +Shutdown function continues +Async in shutdown started + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d \ No newline at end of file diff --git a/tests/bailout/009-memory-exhaustion-during-await.phpt b/tests/bailout/009-memory-exhaustion-during-await.phpt new file mode 100644 index 0000000..a09fadd --- /dev/null +++ b/tests/bailout/009-memory-exhaustion-during-await.phpt @@ -0,0 +1,37 @@ +--TEST-- +Memory exhaustion bailout during await operation +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Before spawn +Before await +Coroutine started + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/010-memory-exhaustion-during-awaitAll.phpt b/tests/bailout/010-memory-exhaustion-during-awaitAll.phpt new file mode 100644 index 0000000..aed7d0c --- /dev/null +++ b/tests/bailout/010-memory-exhaustion-during-awaitAll.phpt @@ -0,0 +1,48 @@ +--TEST-- +Memory exhaustion bailout during awaitAll operation +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Before spawn +Before awaitAll +Coroutine 1 started +Coroutine 2 started + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/011-stack-overflow-during-await.phpt b/tests/bailout/011-stack-overflow-during-await.phpt new file mode 100644 index 0000000..d380cda --- /dev/null +++ b/tests/bailout/011-stack-overflow-during-await.phpt @@ -0,0 +1,39 @@ +--TEST-- +Stack overflow bailout during await operation +--FILE-- + +--EXPECTF-- +Before spawn +Before await +Coroutine started + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/012-memory-exhaustion-awaiting-process.phpt b/tests/bailout/012-memory-exhaustion-awaiting-process.phpt new file mode 100644 index 0000000..5c93e0d --- /dev/null +++ b/tests/bailout/012-memory-exhaustion-awaiting-process.phpt @@ -0,0 +1,46 @@ +--TEST-- +Memory exhaustion bailout while awaiting coroutine processing +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Before spawn +Before await +Coroutine started +Memory exhaustion coroutine started + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called \ No newline at end of file diff --git a/tests/bailout/013-memory-exhaustion-with-finally.phpt b/tests/bailout/013-memory-exhaustion-with-finally.phpt new file mode 100644 index 0000000..33b6806 --- /dev/null +++ b/tests/bailout/013-memory-exhaustion-with-finally.phpt @@ -0,0 +1,47 @@ +--TEST-- +Memory exhaustion bailout with onFinally handlers +--INI-- +memory_limit=2M +--FILE-- +onFinally(function() { + echo "Finally handler 1 executed\n"; +}); + +$scope->onFinally(function() { + echo "Finally handler 2 executed\n"; +}); + +$coroutine = $scope->spawn(function() { + echo "Before memory exhaustion\n"; + str_repeat('x', 10000000); + echo "After memory exhaustion (should not reach)\n"; + return "result"; +}); + +echo "After spawn\n"; + +?> +--EXPECTF-- +Before scope +After spawn +Before memory exhaustion + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called +Finally handler 1 executed +Finally handler 2 executed \ No newline at end of file diff --git a/tests/bailout/014-stack-overflow-with-finally.phpt b/tests/bailout/014-stack-overflow-with-finally.phpt new file mode 100644 index 0000000..c516b18 --- /dev/null +++ b/tests/bailout/014-stack-overflow-with-finally.phpt @@ -0,0 +1,44 @@ +--TEST-- +Stack overflow bailout with onFinally handlers +--FILE-- +onFinally(function() { + echo "Finally handler executed\n"; +}); + +$coroutine = $scope->spawn(function() { + echo "Before stack overflow\n"; + deepRecursion(); + echo "After stack overflow (should not reach)\n"; + return "result"; +}); + +echo "After spawn\n"; + +?> +--EXPECTF-- +Before scope +After spawn +Before stack overflow + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called +Finally handler executed \ No newline at end of file diff --git a/tests/bailout/015-memory-exhaustion-finally-exception.phpt b/tests/bailout/015-memory-exhaustion-finally-exception.phpt new file mode 100644 index 0000000..27b249c --- /dev/null +++ b/tests/bailout/015-memory-exhaustion-finally-exception.phpt @@ -0,0 +1,57 @@ +--TEST-- +Memory exhaustion bailout with exception in onFinally handler +--INI-- +memory_limit=2M +--FILE-- +setExceptionHandler(function($scope, $coroutine, $exception) { + if ($exception instanceof CompositeException) { + echo "Caught CompositeException with " . count($exception->getExceptions()) . " exceptions\n"; + $exceptions = $exception->getExceptions(); + foreach ($exceptions as $i => $ex) { + echo "Exception " . ($i + 1) . ": " . $ex->getMessage() . "\n"; + } + } else { + echo "Caught single exception: " . $exception->getMessage() . "\n"; + } +}); + +$scope->onFinally(function() { + echo "Finally handler executed\n"; + throw new RuntimeException("Exception in finally handler"); +}); + +$coroutine = $scope->spawn(function() { + echo "Before memory exhaustion\n"; + str_repeat('x', 10000000); + echo "After memory exhaustion (should not reach)\n"; + return "result"; +}); + +echo "After spawn\n"; + +?> +--EXPECTF-- +Before scope +After spawn +Before memory exhaustion + +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + +Warning: Graceful shutdown mode was started in %s on line %d +Shutdown function called +Finally handler executed +Caught single exception: Exception in finally handler \ No newline at end of file From 9e12997b303911692023beff5a15ed185ec57491 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 5 Jul 2025 09:56:17 +0300 Subject: [PATCH 2/2] #8: USE_ZEND_ALLOC === "0" - skip --- tests/bailout/001-memory-exhaustion-simple.phpt | 7 +++++++ tests/bailout/002-memory-exhaustion-nested.phpt | 7 +++++++ tests/bailout/003-stack-overflow-simple.phpt | 7 +++++++ tests/bailout/004-stack-overflow-nested.phpt | 7 +++++++ tests/bailout/005-memory-exhaustion-during-suspend.phpt | 7 +++++++ .../bailout/006-memory-exhaustion-multiple-coroutines.phpt | 7 +++++++ tests/bailout/007-stack-overflow-with-suspend.phpt | 7 +++++++ tests/bailout/008-memory-exhaustion-in-shutdown.phpt | 7 +++++++ tests/bailout/009-memory-exhaustion-during-await.phpt | 7 +++++++ tests/bailout/010-memory-exhaustion-during-awaitAll.phpt | 7 +++++++ tests/bailout/011-stack-overflow-during-await.phpt | 7 +++++++ tests/bailout/012-memory-exhaustion-awaiting-process.phpt | 7 +++++++ tests/bailout/013-memory-exhaustion-with-finally.phpt | 7 +++++++ tests/bailout/014-stack-overflow-with-finally.phpt | 7 +++++++ tests/bailout/015-memory-exhaustion-finally-exception.phpt | 7 +++++++ 15 files changed, 105 insertions(+) diff --git a/tests/bailout/001-memory-exhaustion-simple.phpt b/tests/bailout/001-memory-exhaustion-simple.phpt index bf981eb..0f416aa 100644 --- a/tests/bailout/001-memory-exhaustion-simple.phpt +++ b/tests/bailout/001-memory-exhaustion-simple.phpt @@ -1,5 +1,12 @@ --TEST-- Memory exhaustion bailout in simple async operation +--SKIPIF-- + --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/002-memory-exhaustion-nested.phpt b/tests/bailout/002-memory-exhaustion-nested.phpt index d5bcc9c..eec8dd3 100644 --- a/tests/bailout/002-memory-exhaustion-nested.phpt +++ b/tests/bailout/002-memory-exhaustion-nested.phpt @@ -1,5 +1,12 @@ --TEST-- Memory exhaustion bailout in nested async operations +--SKIPIF-- + --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/003-stack-overflow-simple.phpt b/tests/bailout/003-stack-overflow-simple.phpt index 7b6af05..0b04fab 100644 --- a/tests/bailout/003-stack-overflow-simple.phpt +++ b/tests/bailout/003-stack-overflow-simple.phpt @@ -1,5 +1,12 @@ --TEST-- Stack overflow bailout in simple async operation +--SKIPIF-- + --FILE-- --FILE-- --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/006-memory-exhaustion-multiple-coroutines.phpt b/tests/bailout/006-memory-exhaustion-multiple-coroutines.phpt index 30a6cca..f54eb90 100644 --- a/tests/bailout/006-memory-exhaustion-multiple-coroutines.phpt +++ b/tests/bailout/006-memory-exhaustion-multiple-coroutines.phpt @@ -1,5 +1,12 @@ --TEST-- Memory exhaustion bailout with multiple coroutines consuming memory +--SKIPIF-- + --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/007-stack-overflow-with-suspend.phpt b/tests/bailout/007-stack-overflow-with-suspend.phpt index a4bbb35..d8e1b80 100644 --- a/tests/bailout/007-stack-overflow-with-suspend.phpt +++ b/tests/bailout/007-stack-overflow-with-suspend.phpt @@ -1,5 +1,12 @@ --TEST-- Stack overflow bailout with suspend in recursion +--SKIPIF-- + --FILE-- --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/009-memory-exhaustion-during-await.phpt b/tests/bailout/009-memory-exhaustion-during-await.phpt index a09fadd..ee2f687 100644 --- a/tests/bailout/009-memory-exhaustion-during-await.phpt +++ b/tests/bailout/009-memory-exhaustion-during-await.phpt @@ -1,5 +1,12 @@ --TEST-- Memory exhaustion bailout during await operation +--SKIPIF-- + --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/010-memory-exhaustion-during-awaitAll.phpt b/tests/bailout/010-memory-exhaustion-during-awaitAll.phpt index aed7d0c..9c9cf9b 100644 --- a/tests/bailout/010-memory-exhaustion-during-awaitAll.phpt +++ b/tests/bailout/010-memory-exhaustion-during-awaitAll.phpt @@ -1,5 +1,12 @@ --TEST-- Memory exhaustion bailout during awaitAll operation +--SKIPIF-- + --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/011-stack-overflow-during-await.phpt b/tests/bailout/011-stack-overflow-during-await.phpt index d380cda..d687766 100644 --- a/tests/bailout/011-stack-overflow-during-await.phpt +++ b/tests/bailout/011-stack-overflow-during-await.phpt @@ -1,5 +1,12 @@ --TEST-- Stack overflow bailout during await operation +--SKIPIF-- + --FILE-- --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/013-memory-exhaustion-with-finally.phpt b/tests/bailout/013-memory-exhaustion-with-finally.phpt index 33b6806..5cb4fb8 100644 --- a/tests/bailout/013-memory-exhaustion-with-finally.phpt +++ b/tests/bailout/013-memory-exhaustion-with-finally.phpt @@ -1,5 +1,12 @@ --TEST-- Memory exhaustion bailout with onFinally handlers +--SKIPIF-- + --INI-- memory_limit=2M --FILE-- diff --git a/tests/bailout/014-stack-overflow-with-finally.phpt b/tests/bailout/014-stack-overflow-with-finally.phpt index c516b18..f2cb76f 100644 --- a/tests/bailout/014-stack-overflow-with-finally.phpt +++ b/tests/bailout/014-stack-overflow-with-finally.phpt @@ -1,5 +1,12 @@ --TEST-- Stack overflow bailout with onFinally handlers +--SKIPIF-- + --FILE-- --INI-- memory_limit=2M --FILE--