From fb08bd816d9aa51f31d1c5266c2e2991d0423794 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 24 May 2025 13:49:00 +0300 Subject: [PATCH 001/137] + ZendAsync API version 1.0.0-dev Initial version of the asynchronous API for PHP. Includes only the API itself and the necessary core changes required for the API to function. --- Zend/zend_async_API.c | 666 +++++++++++++++++++++++++++ Zend/zend_async_API.h | 988 +++++++++++++++++++++++++++++++++++++++++ Zend/zend_gc.c | 156 ++++++- configure.ac | 14 + main/main.c | 20 +- win32/build/config.w32 | 7 + 6 files changed, 1848 insertions(+), 3 deletions(-) create mode 100644 Zend/zend_async_API.c create mode 100644 Zend/zend_async_API.h diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c new file mode 100644 index 0000000000000..e13b4f21be2d2 --- /dev/null +++ b/Zend/zend_async_API.c @@ -0,0 +1,666 @@ +/* ++----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Edmond | + +----------------------------------------------------------------------+ +*/ +#include "zend_async_API.h" +#include "zend_exceptions.h" + +#ifdef ZTS +int zend_async_globals_id = 0; +size_t zend_async_globals_offset = 0; +#else +// The default data structure that is used when no other extensions are present. +zend_async_globals_t zend_async_globals = {0}; +#endif + +#define ASYNC_THROW_ERROR(error) zend_throw_error(NULL, error); + +static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_provider) +{ + ASYNC_THROW_ERROR("Async API is not enabled"); + return NULL; +} + +static void suspend(bool from_main) {} + +static zend_class_entry * get_exception_ce(zend_async_exception_type type) +{ + return zend_ce_exception; +} + +static zend_string * scheduler_module_name = NULL; +zend_async_spawn_t zend_async_spawn_fn = spawn; +zend_async_new_coroutine_t zend_async_new_coroutine_fn = NULL; +zend_async_new_scope_t zend_async_new_scope_fn = NULL; +zend_async_suspend_t zend_async_suspend_fn = suspend; +zend_async_resume_t zend_async_resume_fn = NULL; +zend_async_cancel_t zend_async_cancel_fn = NULL; +zend_async_shutdown_t zend_async_shutdown_fn = NULL; +zend_async_get_coroutines_t zend_async_get_coroutines_fn = NULL; +zend_async_add_microtask_t zend_async_add_microtask_fn = NULL; +zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = NULL; +zend_async_get_exception_ce_t zend_async_get_exception_ce_fn = get_exception_ce; + +static zend_string * reactor_module_name = NULL; +zend_async_reactor_startup_t zend_async_reactor_startup_fn = NULL; +zend_async_reactor_shutdown_t zend_async_reactor_shutdown_fn = NULL; +zend_async_reactor_execute_t zend_async_reactor_execute_fn = NULL; +zend_async_reactor_loop_alive_t zend_async_reactor_loop_alive_fn = NULL; +zend_async_new_socket_event_t zend_async_new_socket_event_fn = NULL; +zend_async_new_poll_event_t zend_async_new_poll_event_fn = NULL; +zend_async_new_timer_event_t zend_async_new_timer_event_fn = NULL; +zend_async_new_signal_event_t zend_async_new_signal_event_fn = NULL; +zend_async_new_process_event_t zend_async_new_process_event_fn = NULL; +zend_async_new_thread_event_t zend_async_new_thread_event_fn = NULL; +zend_async_new_filesystem_event_t zend_async_new_filesystem_event_fn = NULL; + +zend_async_getnameinfo_t zend_async_getnameinfo_fn = NULL; +zend_async_getaddrinfo_t zend_async_getaddrinfo_fn = NULL; + +zend_async_new_exec_event_t zend_async_new_exec_event_fn = NULL; +zend_async_exec_t zend_async_exec_fn = NULL; + +static zend_string * thread_pool_module_name = NULL; +zend_async_queue_task_t zend_async_queue_task_fn = NULL; + +ZEND_API bool zend_async_is_enabled(void) +{ + return scheduler_module_name != NULL && reactor_module_name != NULL; +} + +ZEND_API bool zend_scheduler_is_enabled(void) +{ + return scheduler_module_name != NULL; +} + +ZEND_API bool zend_async_reactor_is_enabled(void) +{ + return reactor_module_name != NULL; +} + +static void ts_globals_ctor(zend_async_globals_t * globals) +{ + globals->state = ZEND_ASYNC_OFF; + zend_atomic_bool_store(&globals->heartbeat, false); + globals->in_scheduler_context = false; + globals->graceful_shutdown = false; + globals->active_coroutine_count = 0; + globals->active_event_count = 0; + globals->coroutine = NULL; + globals->scope = NULL; + globals->scheduler = NULL; + globals->exit_exception = NULL; +} + +static void ts_globals_dtor(zend_async_globals_t * globals) +{ + +} + +void zend_async_globals_ctor(void) +{ +#ifdef ZTS + ts_allocate_fast_id( + &zend_async_globals_id, + &zend_async_globals_offset, + sizeof(zend_async_globals_t), + (ts_allocate_ctor) ts_globals_ctor, + (ts_allocate_dtor) ts_globals_dtor + ); + + ZEND_ASSERT(zend_async_globals_id != 0 && "zend_async_globals allocation failed"); + +#else + ts_globals_ctor(zend_async_globals); +#endif +} + +void zend_async_init(void) +{ +} + +void zend_async_globals_dtor(void) +{ +#ifdef ZTS +#else + ts_globals_dtor(zend_async_globals); +#endif +} + +void zend_async_shutdown(void) +{ + zend_async_globals_dtor(); +} + +ZEND_API int zend_async_get_api_version_number(void) +{ + return ZEND_ASYNC_API_VERSION_NUMBER; +} + +ZEND_API void zend_async_scheduler_register( + zend_string *module, + bool allow_override, + zend_async_new_coroutine_t new_coroutine_fn, + zend_async_new_scope_t new_scope_fn, + zend_async_spawn_t spawn_fn, + zend_async_suspend_t suspend_fn, + zend_async_resume_t resume_fn, + zend_async_cancel_t cancel_fn, + zend_async_shutdown_t shutdown_fn, + zend_async_get_coroutines_t get_coroutines_fn, + zend_async_add_microtask_t add_microtask_fn, + zend_async_get_awaiting_info_t get_awaiting_info_fn, + zend_async_get_exception_ce_t get_exception_ce_fn +) +{ + if (scheduler_module_name != NULL && false == allow_override) { + zend_error( + E_CORE_ERROR, "The module %s is trying to override Scheduler, which was registered by the module %s.", + ZSTR_VAL(module), ZSTR_VAL(scheduler_module_name) + ); + return; + } + + if (scheduler_module_name != NULL) { + zend_string_release(scheduler_module_name); + } + + scheduler_module_name = zend_string_copy(module); + + zend_async_new_coroutine_fn = new_coroutine_fn; + zend_async_new_scope_fn = new_scope_fn; + zend_async_spawn_fn = spawn_fn; + zend_async_suspend_fn = suspend_fn; + zend_async_resume_fn = resume_fn; + zend_async_cancel_fn = cancel_fn; + zend_async_shutdown_fn = shutdown_fn; + zend_async_get_coroutines_fn = get_coroutines_fn; + zend_async_add_microtask_fn = add_microtask_fn; + zend_async_get_awaiting_info_fn = get_awaiting_info_fn; + zend_async_get_exception_ce_fn = get_exception_ce_fn; +} + +ZEND_API void zend_async_reactor_register( + zend_string *module, + bool allow_override, + zend_async_reactor_startup_t reactor_startup_fn, + zend_async_reactor_shutdown_t reactor_shutdown_fn, + zend_async_reactor_execute_t reactor_execute_fn, + zend_async_reactor_loop_alive_t reactor_loop_alive_fn, + zend_async_new_socket_event_t new_socket_event_fn, + zend_async_new_poll_event_t new_poll_event_fn, + zend_async_new_timer_event_t new_timer_event_fn, + zend_async_new_signal_event_t new_signal_event_fn, + zend_async_new_process_event_t new_process_event_fn, + zend_async_new_thread_event_t new_thread_event_fn, + zend_async_new_filesystem_event_t new_filesystem_event_fn, + zend_async_getnameinfo_t getnameinfo_fn, + zend_async_getaddrinfo_t getaddrinfo_fn, + zend_async_new_exec_event_t new_exec_event_fn, + zend_async_exec_t exec_fn +) +{ + if (reactor_module_name != NULL && false == allow_override) { + zend_error( + E_CORE_ERROR, "The module %s is trying to override Reactor, which was registered by the module %s.", + ZSTR_VAL(module), ZSTR_VAL(reactor_module_name) + ); + return; + } + + if (reactor_module_name != NULL) { + zend_string_release(reactor_module_name); + } + + reactor_module_name = zend_string_copy(module); + + zend_async_reactor_startup_fn = reactor_startup_fn; + zend_async_reactor_shutdown_fn = reactor_shutdown_fn; + zend_async_reactor_execute_fn = reactor_execute_fn; + zend_async_reactor_loop_alive_fn = reactor_loop_alive_fn; + + zend_async_new_socket_event_fn = new_socket_event_fn; + zend_async_new_poll_event_fn = new_poll_event_fn; + zend_async_new_timer_event_fn = new_timer_event_fn; + zend_async_new_signal_event_fn = new_signal_event_fn; + + zend_async_new_process_event_fn = new_process_event_fn; + zend_async_new_thread_event_fn = new_thread_event_fn; + zend_async_new_filesystem_event_fn = new_filesystem_event_fn; + + zend_async_getnameinfo_fn = getnameinfo_fn; + zend_async_getaddrinfo_fn = getaddrinfo_fn; + + zend_async_new_exec_event_fn = new_exec_event_fn; + zend_async_exec_fn = exec_fn; +} + +ZEND_API void zend_async_thread_pool_register(zend_string *module, bool allow_override, zend_async_queue_task_t queue_task_fn) +{ + if (thread_pool_module_name != NULL && false == allow_override) { + zend_error( + E_CORE_ERROR, "The module %s is trying to override Thread Pool, which was registered by the module %s.", + ZSTR_VAL(module), ZSTR_VAL(thread_pool_module_name) + ); + } + + if (thread_pool_module_name != NULL) { + zend_string_release(thread_pool_module_name); + } + + thread_pool_module_name = zend_string_copy(module); + zend_async_queue_task_fn = queue_task_fn; +} + +ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char *zend_coroutine_name) +{ + if (zend_coroutine_name == NULL) { + zend_coroutine_name = ""; + } + + if (coroutine->waker != NULL) { + return zend_strpprintf(0, + "Coroutine spawned at %s:%d, suspended at %s:%d (%s)", + coroutine->filename ? ZSTR_VAL(coroutine->filename) : "", + coroutine->lineno, + coroutine->waker->filename ? ZSTR_VAL(coroutine->waker->filename) : "", + coroutine->waker->lineno, + zend_coroutine_name + ); + } else { + return zend_strpprintf(0, + "Coroutine spawned at %s:%d (%s)", + coroutine->filename ? ZSTR_VAL(coroutine->filename) : "", + coroutine->lineno, + zend_coroutine_name + ); + } +} + +////////////////////////////////////////////////////////////////////// +/* Waker API */ +////////////////////////////////////////////////////////////////////// + +static void waker_events_dtor(zval *item) +{ + zend_async_waker_trigger_t * trigger = Z_PTR_P(item); + + trigger->event->del_callback(trigger->event, trigger->callback); + + ZEND_ASYNC_EVENT_RELEASE(trigger->event); + + efree(trigger); +} + +static void waker_triggered_events_dtor(zval *item) +{ + zend_async_event_t * event = Z_PTR_P(item); + + ZEND_ASYNC_EVENT_RELEASE(event); +} + +ZEND_API zend_async_waker_t *zend_async_waker_define(zend_coroutine_t *coroutine) +{ + if (coroutine == NULL) { + coroutine = ZEND_ASYNC_CURRENT_COROUTINE; + } + + if (UNEXPECTED(coroutine == NULL)) { + zend_error(E_CORE_ERROR, "Cannot create waker for a coroutine that is not running"); + return NULL; + } + + if (UNEXPECTED(coroutine->waker != NULL)) { + return coroutine->waker; + } + + return zend_async_waker_new(coroutine); +} + +ZEND_API zend_async_waker_t *zend_async_waker_new(zend_coroutine_t *coroutine) +{ + if (coroutine == NULL) { + coroutine = ZEND_ASYNC_CURRENT_COROUTINE; + } + + if (UNEXPECTED(coroutine == NULL)) { + zend_error(E_CORE_ERROR, "Cannot create waker for a coroutine that is not running"); + return NULL; + } + + if (UNEXPECTED(coroutine->waker != NULL)) { + coroutine->waker->dtor(coroutine); + coroutine->waker = NULL; + } + + zend_async_waker_t *waker = pecalloc(1, sizeof(zend_async_waker_t), 0); + + waker->triggered_events = NULL; + waker->error = NULL; + waker->dtor = NULL; + ZVAL_UNDEF(&waker->result); + + zend_hash_init(&waker->events, 2, NULL, waker_events_dtor, 0); + + coroutine->waker = waker; + + return waker; +} + +ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine) +{ + if (UNEXPECTED(coroutine->waker == NULL)) { + return; + } + + if (coroutine->waker->dtor != NULL) { + coroutine->waker->dtor(coroutine); + } + + zend_async_waker_t * waker = coroutine->waker; + coroutine->waker = NULL; + + // default dtor + if (waker->error != NULL) { + zend_object_release(waker->error); + waker->error = NULL; + } + + if (waker->triggered_events != NULL) { + zend_array_release(waker->triggered_events); + waker->triggered_events = NULL; + } + + if (waker->filename != NULL) { + zend_string_release(waker->filename); + waker->filename = NULL; + waker->lineno = 0; + } + + zval_ptr_dtor(&waker->result); + zend_hash_destroy(&waker->events); + efree(waker); +} + +static void event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) +{ + if (callback->ref_count != 0) { + return; + } + + zend_async_waker_t * waker = ((zend_coroutine_event_callback_t *) callback)->coroutine->waker; + + if (event != NULL && waker != NULL) { + // remove the event from the waker + zend_hash_index_del(&waker->events, (zend_ulong)event); + + if (waker->triggered_events != NULL) { + zend_hash_index_del(waker->triggered_events, (zend_ulong)event); + } + } + + efree(callback); +} + +ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, zend_async_event_t *event) +{ + if (UNEXPECTED(coroutine->waker == NULL)) { + return; + } + + if (coroutine->waker->triggered_events == NULL) { + coroutine->waker->triggered_events = (HashTable *) malloc(sizeof(HashTable)); + zend_hash_init(coroutine->waker->triggered_events, 2, NULL, waker_triggered_events_dtor, 0); + } + + if (EXPECTED(zend_hash_index_add_ptr(coroutine->waker->triggered_events, (zend_ulong)event, event) != NULL)) { + ZEND_ASYNC_EVENT_ADD_REF(event); + } +} + +ZEND_API void zend_async_resume_when( + zend_coroutine_t *coroutine, + zend_async_event_t *event, + const bool trans_event, + zend_async_event_callback_fn callback, + zend_coroutine_event_callback_t *event_callback + ) +{ + if (UNEXPECTED(ZEND_ASYNC_EVENT_IS_CLOSED(event))) { + zend_throw_error(NULL, "The event cannot be used after it has been terminated"); + return; + } + + if (UNEXPECTED(callback == NULL && event_callback == NULL)) { + zend_error(E_WARNING, "Callback cannot be NULL"); + + if (trans_event) { + event->dispose(event); + } + + return; + } + + if (event_callback == NULL) { + event_callback = emalloc(sizeof(zend_coroutine_event_callback_t)); + event_callback->base.ref_count = 1; + event_callback->base.callback = callback; + event_callback->base.dispose = event_callback_dispose; + } + + // Set up the default dispose function if not set + if (event_callback->base.dispose == NULL) { + event_callback->base.dispose = event_callback_dispose; + } + + event_callback->coroutine = coroutine; + event->add_callback(event, &event_callback->base); + + if (UNEXPECTED(EG(exception) != NULL)) { + if (trans_event) { + event->dispose(event); + } + + return; + } + + if (EXPECTED(coroutine->waker != NULL)) { + zend_async_waker_trigger_t *trigger = emalloc(sizeof(zend_async_waker_trigger_t)); + trigger->event = event; + trigger->callback = &event_callback->base; + + if (UNEXPECTED(zend_hash_index_add_ptr(&coroutine->waker->events, (zend_ulong)event, trigger) == NULL)) { + zend_throw_error(NULL, "Failed to add event to the waker"); + return; + } + } + + if (false == trans_event) { + ZEND_ASYNC_EVENT_ADD_REF(event); + } +} + +ZEND_API void zend_async_waker_callback_resolve( + zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception +) +{ + zend_coroutine_t * coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; + + if (exception == NULL && coroutine->waker != NULL) { + + if (coroutine->waker->triggered_events == NULL) { + coroutine->waker->triggered_events = (HashTable *) malloc(sizeof(HashTable)); + zend_hash_init(coroutine->waker->triggered_events, 2, NULL, waker_triggered_events_dtor, 0); + } + + if (EXPECTED(zend_hash_index_add_ptr(coroutine->waker->triggered_events, (zend_ulong)event, event) != NULL)) { + ZEND_ASYNC_EVENT_ADD_REF(event); + } + + // Copy the result to the waker if it is not NULL + if (ZEND_ASYNC_EVENT_WILL_RESULT_USED(event) && result != NULL) { + ZVAL_COPY(&coroutine->waker->result, result); + } + } + + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, false); +} + +ZEND_API void zend_async_waker_callback_cancel( + zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception +) +{ + zend_coroutine_t * coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; + + if (UNEXPECTED(exception != NULL)) { + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, false); + } else { + + if (result == NULL) { + exception = zend_async_new_exception( + ZEND_ASYNC_EXCEPTION_CANCELLATION, "Operation has been cancelled" + ); + } else { + exception = result; + } + + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, false); + } +} + +ZEND_API void zend_async_waker_callback_timeout( + zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception +) +{ + if (UNEXPECTED(exception != NULL)) { + ZEND_ASYNC_RESUME_WITH_ERROR(((zend_coroutine_event_callback_t *) callback)->coroutine, exception, false); + } else { + exception = zend_async_new_exception( + ZEND_ASYNC_EXCEPTION_TIMEOUT, "Operation has been cancelled by timeout" + ); + + ZEND_ASYNC_RESUME_WITH_ERROR(((zend_coroutine_event_callback_t *) callback)->coroutine, exception, true); + } +} + +ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( + zend_coroutine_t * coroutine, const zend_ulong timeout, zend_async_event_t *cancellation +) +{ + if (coroutine == NULL) { + coroutine = ZEND_ASYNC_CURRENT_COROUTINE; + } + + if (UNEXPECTED(coroutine == NULL)) { + zend_error(E_CORE_ERROR, "Cannot create waker for a coroutine that is not running"); + return NULL; + } + + zend_async_waker_t *waker = zend_async_waker_new(coroutine); + + if (timeout > 0) { + zend_async_resume_when( + coroutine, + &ZEND_ASYNC_NEW_TIMER_EVENT(timeout, false)->base, + true, + zend_async_waker_callback_resolve, + NULL + ); + } + + if (cancellation != NULL) { + zend_async_resume_when(coroutine, cancellation, false, zend_async_waker_callback_cancel, NULL); + } + + return waker; +} + +////////////////////////////////////////////////////////////////////// +/* Waker API end */ +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// +/* Exception API */ +////////////////////////////////////////////////////////////////////// + +ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_exception_type type, const char *format, ...) +{ + zend_class_entry *exception_ce = ZEND_ASYNC_GET_EXCEPTION_CE(type); + zval exception, message_val; + + ZEND_ASSERT(instanceof_function(exception_ce, zend_ce_throwable) + && "Exceptions must implement Throwable"); + + object_init_ex(&exception, exception_ce); + + va_list args; + va_start(args, format); + zend_string *message = zend_vstrpprintf(0, format, args); + va_end(args); + + if (message) { + ZVAL_STR(&message_val, message); + zend_update_property_ex(exception_ce, Z_OBJ(exception), ZSTR_KNOWN(ZEND_STR_MESSAGE), &message_val); + } + + zend_string_release(message); + + return Z_OBJ(exception); +} + +ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_exception_type type, const char *format, ...) +{ + va_list args; + va_start(args, format); + zend_string *message = zend_vstrpprintf(0, format, args); + va_end(args); + + zend_object *obj = zend_throw_exception(ZEND_ASYNC_GET_EXCEPTION_CE(type), ZSTR_VAL(message), 0); + zend_string_release(message); + return obj; +} + +ZEND_API ZEND_COLD zend_object * zend_async_throw_cancellation(const char *format, ...) +{ + const zend_object *previous = EG(exception); + + if (format == NULL + && previous != NULL + && instanceof_function(previous->ce, ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_TIMEOUT))) { + format = "The operation was canceled by timeout"; + } else { + format = format ? format : "The operation was canceled"; + } + + va_list args; + va_start(args, format); + + zend_object *obj = zend_throw_exception_ex( + ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_CANCELLATION), 0, format, args + ); + + va_end(args); + return obj; +} + +ZEND_API ZEND_COLD zend_object * zend_async_throw_timeout(const char *format, const zend_long timeout) +{ + format = format ? format : "A timeout of %u microseconds occurred"; + + return zend_throw_exception_ex(ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_TIMEOUT), 0, format, timeout); +} + +////////////////////////////////////////////////////////////////////// +/* Exception API end */ +////////////////////////////////////////////////////////////////////// diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h new file mode 100644 index 0000000000000..c2fcff89002f6 --- /dev/null +++ b/Zend/zend_async_API.h @@ -0,0 +1,988 @@ +/* ++----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Edmond | + +----------------------------------------------------------------------+ +*/ +#ifndef ZEND_ASYNC_API_H +#define ZEND_ASYNC_API_H + +#include "zend_fibers.h" +#include "zend_globals.h" + +#define ZEND_ASYNC_API "ZendAsync API v1.0.0-dev" +#define ZEND_ASYNC_API_VERSION_MAJOR 1 +#define ZEND_ASYNC_API_VERSION_MINOR 0 +#define ZEND_ASYNC_API_VERSION_PATCH 0 + +#define ZEND_ASYNC_API_VERSION_NUMBER \ + ((ZEND_ASYNC_API_VERSION_MAJOR << 16) | \ + (ZEND_ASYNC_API_VERSION_MINOR << 8) | \ + (ZEND_ASYNC_API_VERSION_PATCH)) + +#ifndef PHP_WIN32 +#include +#endif + +/* Reactor Poll API */ +typedef enum { + ASYNC_READABLE = 1, + ASYNC_WRITABLE = 2, + ASYNC_DISCONNECT = 4, + ASYNC_PRIORITIZED = 8 +} async_poll_event; + +/* Signal Constants */ +#define ZEND_ASYNC_SIGHUP 1 +#define ZEND_ASYNC_SIGINT 2 +#define ZEND_ASYNC_SIGQUIT 3 +#define ZEND_ASYNC_SIGILL 4 +#define ZEND_ASYNC_SIGABRT_COMPAT 6 +#define ZEND_ASYNC_SIGFPE 8 +#define ZEND_ASYNC_SIGKILL 9 +#define ZEND_ASYNC_SIGSEGV 11 +#define ZEND_ASYNC_SIGTERM 15 +#define ZEND_ASYNC_SIGBREAK 21 +#define ZEND_ASYNC_SIGABRT 22 +#define ZEND_ASYNC_SIGWINCH 28 + +// +// Definitions compatibles with proc_open() +// +#ifdef PHP_WIN32 +typedef HANDLE zend_file_descriptor_t; +#define ZEND_FD_NULL NULL +typedef DWORD zend_process_id_t; +typedef HANDLE zend_process_t; +typedef SOCKET zend_socket_t; +#else +typedef int zend_file_descriptor_t; +typedef pid_t zend_process_id_t; +typedef pid_t zend_process_t; +typedef int zend_socket_t; +#define ZEND_FD_NULL 0 +#endif + +/** + * php_exec + * If type==0, only last line of output is returned (exec) + * If type==1, all lines will be printed and last lined returned (system) + * If type==2, all lines will be saved to given array (exec with &$array) + * If type==3, output will be printed binary, no lines will be saved or returned (passthru) + * If type==4, output will be saved to a memory buffer (shell_exec) + */ +typedef enum { + ZEND_ASYNC_EXEC_MODE_EXEC = 0, + ZEND_ASYNC_EXEC_MODE_SYSTEM = 1, + ZEND_ASYNC_EXEC_MODE_EXEC_ARRAY = 2, + ZEND_ASYNC_EXEC_MODE_PASSTHRU = 3, + ZEND_ASYNC_EXEC_MODE_SHELL_EXEC = 4 +} zend_async_exec_mode; + +typedef enum +{ + ZEND_ASYNC_EXCEPTION_DEFAULT = 0, + ZEND_ASYNC_EXCEPTION_CANCELLATION = 1, + ZEND_ASYNC_EXCEPTION_TIMEOUT = 2, + ZEND_ASYNC_EXCEPTION_INPUT_OUTPUT = 3, + ZEND_ASYNC_EXCEPTION_POLL = 4 +} zend_async_exception_type; + +/** + * zend_coroutine_t is a Basic data structure that represents a coroutine in the Zend Engine. + */ +typedef struct _zend_coroutine_s zend_coroutine_t; +typedef struct _zend_async_waker_s zend_async_waker_t; +typedef struct _zend_async_microtask_s zend_async_microtask_t; +typedef struct _zend_async_scope_s zend_async_scope_t; +typedef struct _zend_fcall_s zend_fcall_t; +typedef void (*zend_coroutine_entry_t)(void); + +typedef struct _zend_async_event_s zend_async_event_t; +typedef struct _zend_async_event_callback_s zend_async_event_callback_t; +typedef struct _zend_async_waker_trigger_s zend_async_waker_trigger_t; +typedef struct _zend_coroutine_event_callback_s zend_coroutine_event_callback_t; +typedef void (*zend_async_event_callback_fn) +( + zend_async_event_t *event, + zend_async_event_callback_t *callback, + void * result, + zend_object *exception +); +typedef void (*zend_async_event_callback_dispose_fn)(zend_async_event_callback_t *callback, zend_async_event_t * event); +typedef void (*zend_async_event_add_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); +typedef void (*zend_async_event_del_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); +typedef void (*zend_async_event_start_t) (zend_async_event_t *event); +typedef void (*zend_async_event_stop_t) (zend_async_event_t *event); +typedef void (*zend_async_event_dispose_t) (zend_async_event_t *event); +typedef zend_string* (*zend_async_event_info_t) (zend_async_event_t *event); + +typedef struct _zend_async_poll_event_s zend_async_poll_event_t; +typedef struct _zend_async_timer_event_s zend_async_timer_event_t; +typedef struct _zend_async_signal_event_s zend_async_signal_event_t; +typedef struct _zend_async_filesystem_event_s zend_async_filesystem_event_t; + +typedef struct _zend_async_process_event_s zend_async_process_event_t; +typedef struct _zend_async_thread_event_s zend_async_thread_event_t; + +typedef struct _zend_async_dns_nameinfo_s zend_async_dns_nameinfo_t; +typedef struct _zend_async_dns_addrinfo_s zend_async_dns_addrinfo_t; + +typedef struct _zend_async_exec_event_s zend_async_exec_event_t; + +typedef struct _zend_async_task_s zend_async_task_t; + +typedef zend_coroutine_t * (*zend_async_new_coroutine_t)(zend_async_scope_t *scope); +typedef zend_async_scope_t * (*zend_async_new_scope_t)(zend_async_scope_t * parent_scope); +typedef zend_coroutine_t * (*zend_async_spawn_t)(zend_async_scope_t *scope, zend_object *scope_provider); +typedef void (*zend_async_suspend_t)(bool from_main); +typedef void (*zend_async_resume_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error); +typedef void (*zend_async_cancel_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error, const bool is_safely); +typedef void (*zend_async_shutdown_t)(void); +typedef zend_array* (*zend_async_get_coroutines_t)(void); +typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); +typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); +typedef zend_class_entry* (*zend_async_get_exception_ce_t)(zend_async_exception_type type); + +typedef void (*zend_async_reactor_startup_t)(void); +typedef void (*zend_async_reactor_shutdown_t)(void); +typedef bool (*zend_async_reactor_execute_t)(bool no_wait); +typedef bool (*zend_async_reactor_loop_alive_t)(void); + +typedef zend_async_poll_event_t* (*zend_async_new_socket_event_t)(zend_socket_t socket, async_poll_event events, size_t size); +typedef zend_async_poll_event_t* (*zend_async_new_poll_event_t)( + zend_file_descriptor_t fh, zend_socket_t socket, async_poll_event events, size_t size +); +typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)(const zend_ulong timeout, const bool is_periodic, size_t size); +typedef zend_async_signal_event_t* (*zend_async_new_signal_event_t)(int signum, size_t size); +typedef zend_async_process_event_t* (*zend_async_new_process_event_t)(zend_process_t process_handle, size_t size); +typedef void (*zend_async_thread_entry_t)(void *arg, size_t size); +typedef zend_async_thread_event_t* (*zend_async_new_thread_event_t)(zend_async_thread_entry_t entry, void *arg, size_t size); +typedef zend_async_filesystem_event_t* (*zend_async_new_filesystem_event_t)(zend_string * path, const unsigned int flags, size_t size); + +typedef zend_async_dns_nameinfo_t* (*zend_async_getnameinfo_t)(const struct sockaddr* addr, int flags, size_t size); +typedef zend_async_dns_addrinfo_t* (*zend_async_getaddrinfo_t)( + const char *node, const char *service, const struct addrinfo *hints, size_t size +); + +typedef zend_async_exec_event_t* (*zend_async_new_exec_event_t) ( + zend_async_exec_mode exec_mode, + const char *cmd, + zval *return_buffer, + zval *return_value, + zval *std_error, + const char *cwd, + const char *env, + size_t size +); + +typedef int (* zend_async_exec_t) ( + zend_async_exec_mode exec_mode, + const char *cmd, + zval *return_buffer, + zval *return_value, + zval *std_error, + const char *cwd, + const char *env, + const zend_ulong timeout +); + +typedef void (*zend_async_queue_task_t)(zend_async_task_t *task); + +typedef void (*zend_async_microtask_handler_t)(zend_async_microtask_t *microtask); + +struct _zend_fcall_s { + zend_fcall_info fci; + zend_fcall_info_cache fci_cache; +}; + +struct _zend_async_microtask_s { + zend_async_microtask_handler_t handler; + zend_async_microtask_handler_t dtor; + bool is_cancelled; + uint32_t ref_count; +}; + +/////////////////////////////////////////////////////////////////// +/// Event Structures +/////////////////////////////////////////////////////////////////// + +struct _zend_async_event_callback_s { + uint32_t ref_count; + zend_async_event_callback_fn callback; + zend_async_event_callback_dispose_fn dispose; +}; + +struct _zend_coroutine_event_callback_s { + zend_async_event_callback_t base; + zend_coroutine_t *coroutine; +}; + +struct _zend_async_waker_trigger_s { + zend_async_event_t *event; + zend_async_event_callback_t *callback; +}; + +/* Dynamic array of async event callbacks */ +typedef struct _zend_async_callbacks_vector_s { + uint32_t length; /* current number of callbacks */ + uint32_t capacity; /* allocated slots in the array */ + zend_async_event_callback_t **data; /* dynamically allocated callback array */ +} zend_async_callbacks_vector_t; + +/** + * Basic structure for representing events. + * An event can be either an internal C object or a Zend object implementing the Awaitable interface. + * In that case, the ZEND_ASYNC_EVENT_F_ZEND_OBJ flag is set to TRUE, + * and the zend_object_offset field points to the offset of the zend_object structure. + * + * To manage the reference counter, use the macros: + * ZEND_ASYNC_EVENT_ADD_REF, ZEND_ASYNC_EVENT_DEL_REF, ZEND_ASYNC_EVENT_RELEASE. + * + */ +struct _zend_async_event_s { + /* If event is closed, it cannot be started or stopped. */ + uint32_t flags; + union + { + /* The refcount of the event. */ + uint32_t ref_count; + /* The offset of Zend object structure. */ + uint32_t zend_object_offset; + }; + /* The Event loop reference count. */ + uint32_t loop_ref_count; + zend_async_callbacks_vector_t callbacks; + /* Methods */ + zend_async_event_add_callback_t add_callback; + zend_async_event_del_callback_t del_callback; + zend_async_event_start_t start; + zend_async_event_stop_t stop; + zend_async_event_dispose_t dispose; + /* Event info: can be NULL */ + zend_async_event_info_t info; +}; + +#define ZEND_ASYNC_EVENT_F_CLOSED (1u << 0) /* event was closed */ +#define ZEND_ASYNC_EVENT_F_RESULT_USED (1u << 1) /* result will be used in exception handler */ +#define ZEND_ASYNC_EVENT_F_EXC_CAUGHT (1u << 2) /* error was caught in exception handler */ +/* Indicates that the event produces a ZVAL pointer during the callback. */ +#define ZEND_ASYNC_EVENT_F_ZVAL_RESULT (1u << 3) +#define ZEND_ASYNC_EVENT_F_ZEND_OBJ (1u << 4) /* event is a zend object */ +#define ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY (1u << 5) /* event will not free memory in dispose handler */ + +#define ZEND_ASYNC_EVENT_IS_CLOSED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) +#define ZEND_ASYNC_EVENT_WILL_RESULT_USED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_RESULT_USED) != 0) +#define ZEND_ASYNC_EVENT_WILL_EXC_CAUGHT(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_EXC_CAUGHT) != 0) +#define ZEND_ASYNC_EVENT_WILL_ZVAL_RESULT(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_ZVAL_RESULT) != 0) +#define ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_ZEND_OBJ) != 0) +#define ZEND_ASYNC_EVENT_IS_NO_FREE_MEMORY(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY) != 0) + +#define ZEND_ASYNC_EVENT_SET_CLOSED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_CLOSED) +#define ZEND_ASYNC_EVENT_CLR_CLOSED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_CLOSED) + +#define ZEND_ASYNC_EVENT_SET_RESULT_USED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_RESULT_USED) +#define ZEND_ASYNC_EVENT_CLR_RESULT_USED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_RESULT_USED) + +#define ZEND_ASYNC_EVENT_SET_EXC_CAUGHT(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_EXC_CAUGHT) +#define ZEND_ASYNC_EVENT_CLR_EXC_CAUGHT(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_EXC_CAUGHT) + +#define ZEND_ASYNC_EVENT_SET_ZVAL_RESULT(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_ZVAL_RESULT) +#define ZEND_ASYNC_EVENT_CLR_ZVAL_RESULT(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_ZVAL_RESULT) + +#define ZEND_ASYNC_EVENT_SET_ZEND_OBJ(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_ZEND_OBJ) +#define ZEND_ASYNC_EVENT_SET_ZEND_OBJ_OFFSET(ev, offset) ((ev)->zend_object_offset = (unsigned int) (offset)) + +#define ZEND_ASYNC_EVENT_SET_NO_FREE_MEMORY(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY) + +// Convert awaitable Zend object to zend_async_event_t pointer +#define ZEND_ASYNC_OBJECT_TO_EVENT(obj) ((zend_async_event_t *)((char *)(obj) - (obj)->handlers->offset)) +#define ZEND_ASYNC_EVENT_TO_OBJECT(event) ((zend_object *)((char *)(event) + (event)->zend_object_offset)) + +// Get refcount of the event object +#define ZEND_ASYNC_EVENT_REF(ev) (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? \ + GC_REFCOUNT(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) : \ + (ev)->ref_count) + +// Proper increment of the event object's reference count. +#define ZEND_ASYNC_EVENT_ADD_REF(ev) (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? \ + GC_ADDREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) : \ + ++(ev)->ref_count) + +// Proper decrement of the event object's reference count. +#define ZEND_ASYNC_EVENT_DEL_REF(ev) (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? \ + GC_DELREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) : \ + --(ev)->ref_count) + +/* Properly release the event object */ +#define ZEND_ASYNC_EVENT_RELEASE(ev) do { \ + if (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev)) { \ + OBJ_RELEASE(ZEND_ASYNC_EVENT_TO_OBJECT(ev)); \ + } else { \ + if ((ev)->ref_count == 1) { \ + (ev)->ref_count = 0; \ + (ev)->dispose(ev); \ + } else { \ + (ev)->ref_count--; \ + } \ + } \ +} while (0) + +/* Append a callback; grows the buffer when needed */ +static zend_always_inline void +zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t *callback) +{ + if (event->callbacks.data == NULL) { + event->callbacks.data = safe_emalloc(4, sizeof(zend_async_event_callback_t *), 0); + event->callbacks.capacity = 4; + } + + zend_async_callbacks_vector_t *vector = &event->callbacks; + + if (vector->length == vector->capacity) { + vector->capacity = vector->capacity ? vector->capacity * 2 : 4; + vector->data = safe_erealloc(vector->data, + vector->capacity, + sizeof(zend_async_event_callback_t *), + 0); + } + + vector->data[vector->length++] = callback; +} + +/* Remove a specific callback; order is NOT preserved */ +static zend_always_inline void +zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_callback_t *callback) +{ + zend_async_callbacks_vector_t *vector = &event->callbacks; + + for (uint32_t i = 0; i < vector->length; ++i) { + if (vector->data[i] == callback) { + vector->data[i] = vector->data[--vector->length]; /* O(1) removal */ + callback->dispose(vector->data[i], event); + return; + } + } +} + +/* Call all callbacks */ +static zend_always_inline void +zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception) +{ + if (event->callbacks.data == NULL) { + return; + } + + // TODO: Consider the case when the callback is removed during iteration! + + const zend_async_callbacks_vector_t *vector = &event->callbacks; + + for (uint32_t i = 0; i < vector->length; ++i) { + vector->data[i]->callback(event, vector->data[i], result, exception); + if (UNEXPECTED(EG(exception) != NULL)) { + break; + } + } +} + +/* Call all callbacks and close the event (Like future) */ +static zend_always_inline void +zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception) +{ + ZEND_ASYNC_EVENT_SET_CLOSED(event); + zend_async_callbacks_notify(event, result, exception); +} + +/* Free the vector’s memory */ +static zend_always_inline void +zend_async_callbacks_free(zend_async_event_t *event) +{ + if (event->callbacks.data != NULL) { + for (uint32_t i = 0; i < event->callbacks.length; ++i) { + event->callbacks.data[i]->dispose(event->callbacks.data[i], event); + } + + efree(event->callbacks.data); + } + + event->callbacks.data = NULL; + event->callbacks.length = 0; + event->callbacks.capacity = 0; +} + +struct _zend_async_poll_event_s { + zend_async_event_t base; + bool is_socket; + union { + zend_file_descriptor_t file; + zend_socket_t socket; + }; + async_poll_event events; + async_poll_event triggered_events; +}; + +struct _zend_async_timer_event_s { + zend_async_event_t base; + /* The timeout in milliseconds. */ + unsigned int timeout; + /* The timer is periodic. */ + bool is_periodic; +}; + +struct _zend_async_signal_event_s { + zend_async_event_t base; + int signal; +}; + +struct _zend_async_process_event_s { + zend_async_event_t base; + zend_process_t process; + zend_long exit_code; +}; + +struct _zend_async_thread_event_s { + zend_async_event_t base; +}; + +struct _zend_async_filesystem_event_s { + zend_async_event_t base; + zend_string *path; + unsigned int flags; + unsigned int triggered_events; + zend_string *triggered_filename; +}; + +struct _zend_async_dns_nameinfo_s { + zend_async_event_t base; + const char *hostname; + const char *service; +}; + +struct _zend_async_dns_addrinfo_s { + zend_async_event_t base; + const char *node; + const char *service; + const struct addrinfo *hints; + int flags; +}; + +struct _zend_async_exec_event_s { + zend_async_event_t base; + zend_async_exec_mode exec_mode; + bool terminated; + char * cmd; + zval * return_value; + zval * result_buffer; + size_t output_len; + char * output_buffer; + zval * std_error; +}; + +struct _zend_async_task_s { + zend_async_event_t base; +}; + +/////////////////////////////////////////////////////////////////// +/// Scope Structures +/////////////////////////////////////////////////////////////////// + +typedef void (*zend_async_before_coroutine_enqueue_t)(zend_coroutine_t *coroutine, zend_async_scope_t *scope, zval *result); +typedef void (*zend_async_after_coroutine_enqueue_t)(zend_coroutine_t *coroutine, zend_async_scope_t *scope); +typedef void (*zend_async_scope_dispose_t)(zend_async_scope_t *scope); + +/* Dynamic array of async event callbacks */ +typedef struct _zend_async_scopes_vector_s { + uint32_t length; /* current number of items */ + uint32_t capacity; /* allocated slots in the array */ + zend_async_scope_t **data; /* dynamically allocated array */ +} zend_async_scopes_vector_t; + +/** + * The internal Scope structure and the Zend object Scope are different data structures. + * This separation is intentional to manage their lifetimes independently. + * The internal Scope structure can outlive the Zend object. + * When the Zend object triggers the dtor_obj method, + * it initiates the disposal process of the Scope. + * + * However, the internal Scope structure remains in memory until the last coroutine has completed. + * + */ +struct _zend_async_scope_s { + /* The scope ZEND_ASYNC_SCOPE_F flags */ + uint32_t flags; + /* The link to the zend_object structure */ + zend_object * scope_object; + + zend_async_scopes_vector_t scopes; + zend_async_scope_t *parent_scope; + + zend_async_before_coroutine_enqueue_t before_coroutine_enqueue; + zend_async_after_coroutine_enqueue_t after_coroutine_enqueue; + zend_async_scope_dispose_t dispose; +}; + +#define ZEND_ASYNC_SCOPE_F_CLOSED (1u << 0) /* scope was closed */ +#define ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY (1u << 1) /* scope will not free memory in dispose handler */ +#define ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY (1u << 2) /* scope will be disposed safely */ + +#define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) (((scope)->flags & ZEND_ASYNC_SCOPE_F_CLOSED) != 0) +#define ZEND_ASYNC_SCOPE_IS_NO_FREE_MEMORY(scope) (((scope)->flags & ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) != 0) +#define ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(scope) (((scope)->flags & ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) != 0) + +#define ZEND_ASYNC_SCOPE_SET_CLOSED(scope) ((scope)->flags |= ZEND_ASYNC_SCOPE_F_CLOSED) +#define ZEND_ASYNC_SCOPE_CLR_CLOSED(scope) ((scope)->flags &= ~ZEND_ASYNC_SCOPE_F_CLOSED) + +#define ZEND_ASYNC_SCOPE_SET_NO_FREE_MEMORY(scope) ((scope)->flags |= ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) +#define ZEND_ASYNC_SCOPE_CLR_NO_FREE_MEMORY(scope) ((scope)->flags &= ~ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) + +#define ZEND_ASYNC_SCOPE_SET_DISPOSE(scope) ((scope)->flags |= ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) +#define ZEND_ASYNC_SCOPE_CLR_DISPOSE(scope) ((scope)->flags &= ~ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) + +static zend_always_inline void +zend_async_scope_add_child(zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) +{ + zend_async_scopes_vector_t *vector = &parent_scope->scopes; + + child_scope->parent_scope = parent_scope; + + if (vector->data == NULL) { + vector->data = safe_emalloc(4, sizeof(zend_async_scope_t *), 0); + vector->capacity = 4; + } + + if (vector->length == vector->capacity) { + vector->capacity *= 2; + vector->data = safe_erealloc(vector->data, vector->capacity, sizeof(zend_async_scope_t *), 0); + } + + vector->data[vector->length++] = child_scope; +} + +static zend_always_inline void +zend_async_scope_remove_child(zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) +{ + zend_async_scopes_vector_t *vector = &parent_scope->scopes; + for (uint32_t i = 0; i < vector->length; ++i) { + if (vector->data[i] == child_scope) { + vector->data[i] = vector->data[--vector->length]; + child_scope->parent_scope = NULL; + + // Try to dispose the parent scope if it is empty + if (parent_scope->scopes.length == 0) { + parent_scope->dispose(parent_scope); + } + + return; + } + } +} + +static zend_always_inline void +zend_async_scope_free_children(zend_async_scope_t *parent_scope) +{ + zend_async_scopes_vector_t *vector = &parent_scope->scopes; + + if (vector->data != NULL) { + efree(vector->data); + } + + vector->data = NULL; + vector->length = 0; + vector->capacity = 0; +} + +/////////////////////////////////////////////////////////////////// +/// Waker Structures +/////////////////////////////////////////////////////////////////// + +typedef void (*zend_async_waker_dtor)(zend_coroutine_t *coroutine); + +typedef enum { + ZEND_ASYNC_WAKER_NO_STATUS = 0, + ZEND_ASYNC_WAKER_WAITING = 1, + ZEND_ASYNC_WAKER_QUEUED = 2, + ZEND_ASYNC_WAKER_IGNORED = 3 +} ZEND_ASYNC_WAKER_STATUS; + +struct _zend_async_waker_s { + /* The waker status. */ + ZEND_ASYNC_WAKER_STATUS status; + /* The array of zend_async_trigger_callback_t. */ + HashTable events; + /* A list of events objects (zend_async_event_t) that occurred during the last iteration of the event loop. */ + HashTable *triggered_events; + /* Result of the waker. */ + zval result; + /* Error object. */ + zend_object *error; + /* Filename of the waker object creation point. */ + zend_string *filename; + /* Line number of the waker object creation point. */ + uint32_t lineno; + /* The waker destructor. */ + zend_async_waker_dtor dtor; +}; + +/** + * Coroutine destructor. Called when the coroutine needs to clean up all its data. + */ +typedef void (*zend_async_coroutine_dispose)(zend_coroutine_t *coroutine); + +struct _zend_coroutine_s { + zend_async_event_t event; + /* + * Callback and info / cache to be used when coroutine is started. + * If NULL, the coroutine is not a userland coroutine and internal_entry is used. + */ + zend_fcall_t *fcall; + + /* + * The internal entry point of the coroutine. + * If NULL, the coroutine is a userland coroutine and fcall is used. + */ + zend_coroutine_entry_t internal_entry; + + /* The custom data for the coroutine. Can be NULL */ + void *extended_data; + + /* Coroutine waker */ + zend_async_waker_t *waker; + /* Coroutine scope */ + zend_async_scope_t *scope; + + /* Storage for return value. */ + zval result; + + /* Spawned file and line number */ + zend_string *filename; + uint32_t lineno; + + /* Extended dispose handler */ + zend_async_coroutine_dispose extended_dispose; +}; + +/* Coroutine flags */ +#define ZEND_COROUTINE_F_STARTED (1u << 10) /* coroutine is started */ +#define ZEND_COROUTINE_F_CANCELLED (1u << 11) /* coroutine is cancelled */ +#define ZEND_COROUTINE_F_ZOMBIE (1u << 12) /* coroutine is a zombie */ +#define ZEND_COROUTINE_F_PROTECTED (1u << 13) /* coroutine is protected */ +#define ZEND_COROUTINE_F_EXCEPTION_HANDLED (1u << 14) /* exception has been caught and processed */ +#define ZEND_COROUTINE_F_MAIN (1u << 15) /* coroutine is a main coroutine */ + +#define ZEND_COROUTINE_IS_ZOMBIE(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_ZOMBIE) != 0) +#define ZEND_COROUTINE_SET_ZOMBIE(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_ZOMBIE) +#define ZEND_COROUTINE_IS_STARTED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_STARTED) != 0) +#define ZEND_COROUTINE_IS_CANCELLED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_CANCELLED) != 0) +#define ZEND_COROUTINE_IS_FINISHED(coroutine) (((coroutine)->event.flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) +#define ZEND_COROUTINE_IS_PROTECTED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_PROTECTED) != 0) +#define ZEND_COROUTINE_IS_EXCEPTION_HANDLED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_EXCEPTION_HANDLED) != 0) +#define ZEND_COROUTINE_IS_MAIN(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_MAIN) != 0) +#define ZEND_COROUTINE_SET_STARTED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_STARTED) +#define ZEND_COROUTINE_SET_CANCELLED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_CANCELLED) +#define ZEND_COROUTINE_SET_FINISHED(coroutine) ((coroutine)->event.flags |= ZEND_ASYNC_EVENT_F_CLOSED) +#define ZEND_COROUTINE_SET_PROTECTED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_PROTECTED) +#define ZEND_COROUTINE_SET_MAIN(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_MAIN) +#define ZEND_COROUTINE_CLR_PROTECTED(coroutine) ((coroutine)->event.flags &= ~ZEND_COROUTINE_F_PROTECTED) +#define ZEND_COROUTINE_SET_EXCEPTION_HANDLED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_EXCEPTION_HANDLED) +#define ZEND_COROUTINE_CLR_EXCEPTION_HANDLED(coroutine) ((coroutine)->event.flags &= ~ZEND_COROUTINE_F_EXCEPTION_HANDLED) + +static zend_always_inline zend_string *zend_coroutine_callable_name(const zend_coroutine_t *coroutine) +{ + if (ZEND_COROUTINE_IS_MAIN(coroutine)) { + return zend_string_init("main", sizeof("main") - 1, 0); + } + + if (coroutine->fcall) { + return zend_get_callable_name_ex(&coroutine->fcall->fci.function_name, NULL); + } + + return zend_string_init("internal function", sizeof("internal function") - 1, 0); +} + +/////////////////////////////////////////////////////////////// +/// Global Macros +/////////////////////////////////////////////////////////////// +/* + * Async module state + */ +typedef enum { + // The module is inactive. + ZEND_ASYNC_OFF = 0, + // The module is ready for use but has not been activated yet. + ZEND_ASYNC_READY = 1, + // The module is active and can be used. + ZEND_ASYNC_ACTIVE = 2 +} zend_async_state_t; + +typedef struct { + zend_async_state_t state; + /* The flag is TRUE if the Scheduler was able to gain control. */ + zend_atomic_bool heartbeat; + /* Equal TRUE if the scheduler executed now */ + bool in_scheduler_context; + /* Equal TRUE if the reactor is in the process of shutting down */ + bool graceful_shutdown; + /* Number of active coroutines */ + unsigned int active_coroutine_count; + /* Number of active event handles */ + unsigned int active_event_count; + /* The current coroutine context. */ + zend_coroutine_t *coroutine; + /* The current async scope. */ + zend_async_scope_t *scope; + /* Scheduler coroutine */ + zend_coroutine_t *scheduler; + /* Exit exception object */ + zend_object *exit_exception; +} zend_async_globals_t; + +BEGIN_EXTERN_C() +#ifdef ZTS +ZEND_API extern int zend_async_globals_id; +ZEND_API extern size_t zend_async_globals_offset; +#define ZEND_ASYNC_G(v) ZEND_TSRMG_FAST(zend_async_globals_offset, zend_async_globals_t *, v) +#else +#define ZEND_ASYNC_G(v) (zend_async_globals.v) +ZEND_API extern zend_async_globals_t zend_async_globals; +#endif +END_EXTERN_C() + +#define ZEND_ASYNC_ON (ZEND_ASYNC_G(state) > ZEND_ASYNC_OFF) +#define ZEND_ASYNC_IS_ACTIVE (ZEND_ASYNC_G(state) == ZEND_ASYNC_ACTIVE) +#define ZEND_ASYNC_OFF (ZEND_ASYNC_G(state) == ZEND_ASYNC_OFF) +#define ZEND_ASYNC_IS_READY (ZEND_ASYNC_G(state) == ZEND_ASYNC_READY) +#define ZEND_ASYNC_ACTIVATE ZEND_ASYNC_G(state) = ZEND_ASYNC_ACTIVE +#define ZEND_ASYNC_READY ZEND_ASYNC_G(state) = ZEND_ASYNC_READY +#define ZEND_ASYNC_DEACTIVATE ZEND_ASYNC_G(state) = ZEND_ASYNC_OFF +#define ZEND_ASYNC_SCHEDULER_ALIVE (zend_atomic_bool_load(&ZEND_ASYNC_G(heartbeat)) == true) +#define ZEND_ASYNC_SCHEDULER_HEARTBEAT zend_atomic_bool_store(&ZEND_ASYNC_G(heartbeat), true) +#define ZEND_ASYNC_SCHEDULER_WAIT zend_atomic_bool_store(&ZEND_ASYNC_G(heartbeat), false) +#define ZEND_ASYNC_SCHEDULER_CONTEXT ZEND_ASYNC_G(in_scheduler_context) +#define ZEND_ASYNC_IS_SCHEDULER_CONTEXT (ZEND_ASYNC_G(in_scheduler_context) == true) +#define ZEND_ASYNC_ACTIVE_COROUTINE_COUNT ZEND_ASYNC_G(active_coroutine_count) +#define ZEND_ASYNC_ACTIVE_EVENT_COUNT ZEND_ASYNC_G(active_event_count) +#define ZEND_ASYNC_GRACEFUL_SHUTDOWN ZEND_ASYNC_G(graceful_shutdown) +#define ZEND_ASYNC_EXIT_EXCEPTION ZEND_ASYNC_G(exit_exception) +#define ZEND_ASYNC_CURRENT_COROUTINE ZEND_ASYNC_G(coroutine) +#define ZEND_ASYNC_CURRENT_SCOPE ZEND_ASYNC_G(scope) +#define ZEND_ASYNC_SCHEDULER ZEND_ASYNC_G(scheduler) + +#define ZEND_ASYNC_INCREASE_EVENT_COUNT if (ZEND_ASYNC_G(active_event_count) < UINT_MAX) { \ + ZEND_ASYNC_G(active_event_count)++; \ + } else { \ + ZEND_ASSERT("The event count is already max."); \ + } + +#define ZEND_ASYNC_DECREASE_EVENT_COUNT if (ZEND_ASYNC_G(active_event_count) > 0) { \ + ZEND_ASYNC_G(active_event_count)--; \ + } else { \ + ZEND_ASSERT("The event count is already zero."); \ + } + +#define ZEND_ASYNC_INCREASE_COROUTINE_COUNT if (ZEND_ASYNC_G(active_coroutine_count) < UINT_MAX) { \ + ZEND_ASYNC_G(active_coroutine_count)++; \ + } else { \ + ZEND_ASSERT("The coroutine count is already max."); \ + } + +#define ZEND_ASYNC_DECREASE_COROUTINE_COUNT if (ZEND_ASYNC_G(active_coroutine_count) > 0) { \ + ZEND_ASYNC_G(active_coroutine_count)--; \ + } else { \ + ZEND_ASSERT("The coroutine count is already zero."); \ + } + + +BEGIN_EXTERN_C() + +ZEND_API bool zend_async_is_enabled(void); +ZEND_API bool zend_scheduler_is_enabled(void); + +void zend_async_init(void); +void zend_async_shutdown(void); +void zend_async_globals_ctor(void); +void zend_async_globals_dtor(void); + +ZEND_API const char * zend_async_get_api_version(void); +ZEND_API int zend_async_get_api_version_number(void); + +ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_exception_type type, const char *format, ...); +ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_exception_type type, const char *format, ...); +ZEND_API ZEND_COLD zend_object * zend_async_throw_cancellation(const char *format, ...); +ZEND_API ZEND_COLD zend_object * zend_async_throw_timeout(const char *format, const zend_long timeout); + +/* Scheduler API */ + +ZEND_API extern zend_async_spawn_t zend_async_spawn_fn; +ZEND_API extern zend_async_new_coroutine_t zend_async_new_coroutine_fn; +ZEND_API extern zend_async_new_scope_t zend_async_new_scope_fn; +ZEND_API extern zend_async_suspend_t zend_async_suspend_fn; +ZEND_API extern zend_async_resume_t zend_async_resume_fn; +ZEND_API extern zend_async_cancel_t zend_async_cancel_fn; +ZEND_API extern zend_async_shutdown_t zend_async_shutdown_fn; +ZEND_API extern zend_async_get_coroutines_t zend_async_get_coroutines_fn; +ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; +ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; +ZEND_API extern zend_async_get_exception_ce_t zend_async_get_exception_ce_fn; + +/* Reactor API */ + +ZEND_API bool zend_async_reactor_is_enabled(void); +ZEND_API extern zend_async_reactor_startup_t zend_async_reactor_startup_fn; +ZEND_API extern zend_async_reactor_shutdown_t zend_async_reactor_shutdown_fn; +ZEND_API extern zend_async_reactor_execute_t zend_async_reactor_execute_fn; +ZEND_API extern zend_async_reactor_loop_alive_t zend_async_reactor_loop_alive_fn; +ZEND_API extern zend_async_new_socket_event_t zend_async_new_socket_event_fn; +ZEND_API extern zend_async_new_poll_event_t zend_async_new_poll_event_fn; +ZEND_API extern zend_async_new_timer_event_t zend_async_new_timer_event_fn; +ZEND_API extern zend_async_new_signal_event_t zend_async_new_signal_event_fn; +ZEND_API extern zend_async_new_process_event_t zend_async_new_process_event_fn; +ZEND_API extern zend_async_new_thread_event_t zend_async_new_thread_event_fn; +ZEND_API extern zend_async_new_filesystem_event_t zend_async_new_filesystem_event_fn; + +/* DNS API */ + +ZEND_API extern zend_async_getnameinfo_t zend_async_getnameinfo_fn; +ZEND_API extern zend_async_getaddrinfo_t zend_async_getaddrinfo_fn; + +/* Exec API */ +ZEND_API extern zend_async_new_exec_event_t zend_async_new_exec_event_fn; +ZEND_API extern zend_async_exec_t zend_async_exec_fn; + +/* Thread pool API */ +ZEND_API bool zend_async_thread_pool_is_enabled(void); +ZEND_API extern zend_async_queue_task_t zend_async_queue_task_fn; + +ZEND_API void zend_async_scheduler_register( + zend_string *module, + bool allow_override, + zend_async_new_coroutine_t new_coroutine_fn, + zend_async_new_scope_t new_scope_fn, + zend_async_spawn_t spawn_fn, + zend_async_suspend_t suspend_fn, + zend_async_resume_t resume_fn, + zend_async_cancel_t cancel_fn, + zend_async_shutdown_t shutdown_fn, + zend_async_get_coroutines_t get_coroutines_fn, + zend_async_add_microtask_t add_microtask_fn, + zend_async_get_awaiting_info_t get_awaiting_info_fn, + zend_async_get_exception_ce_t get_exception_ce_fn +); + +ZEND_API void zend_async_reactor_register( + zend_string *module, + bool allow_override, + zend_async_reactor_startup_t reactor_startup_fn, + zend_async_reactor_shutdown_t reactor_shutdown_fn, + zend_async_reactor_execute_t reactor_execute_fn, + zend_async_reactor_loop_alive_t reactor_loop_alive_fn, + zend_async_new_socket_event_t new_socket_event_fn, + zend_async_new_poll_event_t new_poll_event_fn, + zend_async_new_timer_event_t new_timer_event_fn, + zend_async_new_signal_event_t new_signal_event_fn, + zend_async_new_process_event_t new_process_event_fn, + zend_async_new_thread_event_t new_thread_event_fn, + zend_async_new_filesystem_event_t new_filesystem_event_fn, + zend_async_getnameinfo_t getnameinfo_fn, + zend_async_getaddrinfo_t getaddrinfo_fn, + zend_async_new_exec_event_t new_exec_event_fn, + zend_async_exec_t exec_fn +); + +ZEND_API void zend_async_thread_pool_register( + zend_string *module, bool allow_override, zend_async_queue_task_t queue_task_fn +); + +ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char *zend_coroutine_name); + +/* Waker API */ +ZEND_API zend_async_waker_t *zend_async_waker_new(zend_coroutine_t *coroutine); +ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( + zend_coroutine_t * coroutine, const zend_ulong timeout, zend_async_event_t *cancellation +); +ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine); +ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, zend_async_event_t *event); + +ZEND_API void zend_async_resume_when( + zend_coroutine_t *coroutine, + zend_async_event_t *event, + const bool trans_event, + zend_async_event_callback_fn callback, + zend_coroutine_event_callback_t *event_callback + ); + +ZEND_API void zend_async_waker_callback_resolve( + zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception +); + +ZEND_API void zend_async_waker_callback_cancel( + zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception +); + +ZEND_API void zend_async_waker_callback_timeout( + zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception +); + +END_EXTERN_C() + +#define ZEND_ASYNC_IS_ENABLED() zend_async_is_enabled() +#define ZEND_ASYNC_SPAWN() zend_async_spawn_fn(NULL, NULL) +#define ZEND_ASYNC_SPAWN_WITH(scope) zend_async_spawn_fn(scope, NULL) +#define ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider) zend_async_spawn_fn(NULL, scope_provider) +#define ZEND_ASYNC_NEW_COROUTINE(scope) zend_async_new_coroutine_fn(scope) +#define ZEND_ASYNC_NEW_SCOPE(parent) zend_async_new_scope_fn(parent) +#define ZEND_ASYNC_SUSPEND() zend_async_suspend_fn(false) +#define ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN() zend_async_suspend_fn(true) +#define ZEND_ASYNC_RESUME(coroutine) zend_async_resume_fn(coroutine, NULL, false) +#define ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error) zend_async_resume_fn(coroutine, error, transfer_error) +#define ZEND_ASYNC_CANCEL(coroutine, error, transfer_error) zend_async_cancel_fn(coroutine, error, transfer_error, false) +#define ZEND_ASYNC_CANCEL_EX(coroutine, error, transfer_error, is_safely) zend_async_cancel_fn(coroutine, error, transfer_error, is_safely) +#define ZEND_ASYNC_SHUTDOWN() zend_async_shutdown_fn() +#define ZEND_ASYNC_GET_COROUTINES() zend_async_get_coroutines_fn() +#define ZEND_ASYNC_ADD_MICROTASK(microtask) zend_async_add_microtask_fn(microtask) +#define ZEND_ASYNC_GET_AWAITING_INFO(coroutine) zend_async_get_awaiting_info_fn(coroutine) +#define ZEND_ASYNC_GET_EXCEPTION_CE(type) zend_async_get_exception_ce_fn(type) + +#define ZEND_ASYNC_REACTOR_IS_ENABLED() zend_async_reactor_is_enabled() +#define ZEND_ASYNC_REACTOR_STARTUP() zend_async_reactor_startup_fn() +#define ZEND_ASYNC_REACTOR_SHUTDOWN() zend_async_reactor_shutdown_fn() + +#define ZEND_ASYNC_REACTOR_EXECUTE(no_wait) zend_async_reactor_execute_fn(no_wait) +#define ZEND_ASYNC_REACTOR_LOOP_ALIVE() zend_async_reactor_loop_alive_fn() + +#define ZEND_ASYNC_NEW_SOCKET_EVENT(socket, events) zend_async_new_socket_event_fn(socket, events, 0) +#define ZEND_ASYNC_NEW_SOCKET_EVENT_EX(socket, events, size) zend_async_new_socket_event_fn(socket, events, size) +#define ZEND_ASYNC_NEW_POLL_EVENT(fh, socket, events) zend_async_new_poll_event_fn(fh, socket, events, 0) +#define ZEND_ASYNC_NEW_POLL_EVENT_EX(fh, socket, events, size) zend_async_new_poll_event_fn(fh, socket, events, size) +#define ZEND_ASYNC_NEW_TIMER_EVENT(timeout, is_periodic) zend_async_new_timer_event_fn(timeout, is_periodic, 0) +#define ZEND_ASYNC_NEW_TIMER_EVENT_EX(timeout, is_periodic, size) zend_async_new_timer_event_fn(timeout, is_periodic, size) +#define ZEND_ASYNC_NEW_SIGNAL_EVENT(signum) zend_async_new_signal_event_fn(signum, 0) +#define ZEND_ASYNC_NEW_SIGNAL_EVENT_EX(signum, size) zend_async_new_signal_event_fn(signum, size) +#define ZEND_ASYNC_NEW_PROCESS_EVENT() zend_async_new_process_event_fn() +#define ZEND_ASYNC_NEW_PROCESS_EVENT_EX() zend_async_new_process_event_fn() +#define ZEND_ASYNC_NEW_THREAD_EVENT() zend_async_new_thread_event_fn() +#define ZEND_ASYNC_NEW_THREAD_EVENT_EX(entry, arg) zend_async_new_thread_event_fn(entry, arg, 0) +#define ZEND_ASYNC_NEW_FILESYSTEM_EVENT(path, flags) zend_async_new_filesystem_event_fn(path, flags, 0) +#define ZEND_ASYNC_NEW_FILESYSTEM_EVENT_EX(path, flags, size) zend_async_new_filesystem_event_fn(path, flags, size) + +#define ZEND_ASYNC_GETNAMEINFO(addr, flags) zend_async_getnameinfo_fn(addr, flags, 0) +#define ZEND_ASYNC_GETNAMEINFO_EX(addr, flags, size) zend_async_getnameinfo_fn(addr, flags, size) +#define ZEND_ASYNC_GETADDRINFO(node, service, hints, flags) zend_async_getaddrinfo_fn(node, service, hints, flags, 0) +#define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, flags, size) zend_async_getaddrinfo_fn(node, service, hints, flags, size) + +#define ZEND_ASYNC_NEW_EXEC_EVENT(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env) \ + zend_async_new_exec_event_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, 0) +#define ZEND_ASYNC_EXEC(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, timeout) \ + zend_async_exec_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, timeout) + +#define ZEND_ASYNC_QUEUE_TASK(task) zend_async_queue_task_fn(task) + +#endif //ZEND_ASYNC_API_H \ No newline at end of file diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 8da55fcd48f66..cbcb38c8178bb 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -68,6 +68,9 @@ */ #include "zend.h" #include "zend_API.h" +#ifdef PHP_ASYNC_API +#include "zend_async_API.h" +#endif #include "zend_compile.h" #include "zend_errors.h" #include "zend_fibers.h" @@ -274,6 +277,11 @@ typedef struct _zend_gc_globals { uint32_t dtor_end; zend_fiber *dtor_fiber; bool dtor_fiber_running; +#ifdef PHP_ASYNC_API + zend_coroutine_t * dtor_coroutine; + zend_async_scope_t * dtor_scope; + zend_async_microtask_t * microtask; +#endif #if GC_BENCH uint32_t root_buf_length; @@ -504,6 +512,12 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals) gc_globals->dtor_fiber = NULL; gc_globals->dtor_fiber_running = false; +#ifdef PHP_ASYNC_API + gc_globals->dtor_coroutine = NULL; + gc_globals->dtor_scope = NULL; + gc_globals->microtask = NULL; +#endif + #if GC_BENCH gc_globals->root_buf_length = 0; gc_globals->root_buf_peak = 0; @@ -1813,6 +1827,15 @@ static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t gc_root_buffer *current; zend_refcounted *p; +#ifdef PHP_ASYNC_API + const bool in_coroutine = GC_G(dtor_coroutine) != NULL; + +#define RESUMED_AFTER_SUSPENSION (fiber != NULL && GC_G(dtor_fiber) != fiber) \ + || (in_coroutine && GC_G(dtor_coroutine) != ZEND_ASYNC_CURRENT_COROUTINE) +#else +#define RESUMED_AFTER_SUSPENSION fiber != NULL && GC_G(dtor_fiber) != fiber +#endif + /* The root buffer might be reallocated during destructors calls, * make sure to reload pointers as necessary. */ while (idx != end) { @@ -1825,17 +1848,23 @@ static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t * could have already been invoked indirectly by some other * destructor. */ if (!(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) { - if (fiber != NULL) { + if (fiber != NULL || in_coroutine) { GC_G(dtor_idx) = idx; } zend_object *obj = (zend_object*)p; GC_TRACE_REF(obj, "calling destructor"); GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); GC_ADDREF(obj); +#ifdef PHP_ASYNC_API + ZEND_ASYNC_CURRENT_COROUTINE->extended_data = obj; +#endif obj->handlers->dtor_obj(obj); +#ifdef PHP_ASYNC_API + ZEND_ASYNC_CURRENT_COROUTINE->extended_data = NULL; +#endif GC_TRACE_REF(obj, "returned from destructor"); GC_DELREF(obj); - if (UNEXPECTED(fiber != NULL && GC_G(dtor_fiber) != fiber)) { + if (UNEXPECTED(RESUMED_AFTER_SUSPENSION)) { /* We resumed after suspension */ gc_check_possible_root((zend_refcounted*)&obj->gc); return FAILURE; @@ -1910,8 +1939,131 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end) } } +#ifdef PHP_ASYNC_API +static void zend_gc_collect_cycles_microtask(zend_async_microtask_t *task); + +static void zend_gc_collect_cycles_microtask_dtor(zend_async_microtask_t *task) +{ + if (task->ref_count > 1) { + task->ref_count--; + return; + } + + if (GC_G(microtask) == task) { + GC_G(microtask) = NULL; + } + + efree(task); +} + +static void zend_gc_collect_cycles_coroutine(void) +{ + GC_TRACE("GC coroutine started"); + + if (GC_G(microtask) == NULL) { + zend_async_microtask_t *task = ecalloc(1, sizeof(zend_async_microtask_t)); + task->handler = zend_gc_collect_cycles_microtask; + task->dtor = zend_gc_collect_cycles_microtask_dtor; + task->ref_count = 1; + GC_G(microtask) = task; + } + + ZEND_ASYNC_ADD_MICROTASK(GC_G(microtask)); + zend_gc_collect_cycles(); +} + +static zend_string* coroutine_info(zend_async_event_t *event) +{ + zend_coroutine_t *coroutine = (zend_coroutine_t *) event; + + if (coroutine->extended_data == NULL) { + return zend_coroutine_gen_info(coroutine, "zend_gc_collect_cycles"); + } + + zend_object *obj = coroutine->extended_data; + const char *class_name = ZSTR_VAL(obj->ce->name); + const char *method_name = "__destructor"; + + zend_string *tmp = zend_strpprintf(0, "%s::%s", class_name, method_name); + zend_string *result = zend_coroutine_gen_info(coroutine, ZSTR_VAL(tmp)); + zend_string_release(tmp); + + return result; +} + +static void coroutine_dispose(zend_coroutine_t *coroutine) +{ + if (coroutine == GC_G(dtor_coroutine)) { + GC_TRACE("GC coroutine finished"); + GC_G(dtor_coroutine) = NULL; + + if (GC_G(microtask) != NULL) { + GC_G(microtask)->is_cancelled = true; + zend_gc_collect_cycles_microtask_dtor(GC_G(microtask)); + } + } +} + +static zend_always_inline zend_coroutine_t* new_gc_coroutine(void) +{ + zend_coroutine_t * coroutine = ZEND_ASYNC_SPAWN_WITH(GC_G(dtor_scope)); + + if (UNEXPECTED(coroutine == NULL)) { + return NULL; + } + + GC_G(dtor_coroutine) = coroutine; + + coroutine->internal_entry = zend_gc_collect_cycles_coroutine; + coroutine->extended_data = NULL; + coroutine->extended_dispose = coroutine_dispose; + coroutine->event.info = coroutine_info; + + return coroutine; +} + +static void zend_gc_collect_cycles_microtask(zend_async_microtask_t *task) +{ + if (UNEXPECTED(new_gc_coroutine() == NULL)) { + task->is_cancelled = true; + return; + } +} + +static zend_always_inline void start_gc_in_coroutine(void) +{ + GC_TRACE("Try to start GC in coroutine"); + + if (GC_G(dtor_scope) == NULL) { + GC_G(dtor_scope) = ZEND_ASYNC_NEW_SCOPE(NULL); + + if (UNEXPECTED(GC_G(dtor_scope) == NULL)) { + zend_error_noreturn(E_ERROR, "Unable to create destructor scope"); + } + } + + if (UNEXPECTED(new_gc_coroutine() == NULL)) { + zend_error_noreturn(E_ERROR, "Unable to spawn destructor coroutine"); + } +} +#endif + ZEND_API int zend_gc_collect_cycles(void) { +#ifdef PHP_ASYNC_API + + if (UNEXPECTED(ZEND_ASYNC_IS_ACTIVE && ZEND_ASYNC_CURRENT_COROUTINE != GC_G(dtor_coroutine))) { + + if (GC_G(dtor_coroutine)) { + return 0; + } + + start_gc_in_coroutine(); + return 0; + } + +#endif + int total_count = 0; bool should_rerun_gc = 0; bool did_rerun_gc = 0; diff --git a/configure.ac b/configure.ac index 61d1c350a82be..73ed385f27d3a 100644 --- a/configure.ac +++ b/configure.ac @@ -1052,6 +1052,15 @@ PHP_ARG_ENABLE([undefined-sanitizer],, [Enable undefined sanitizer])], [no], [no]) +PHP_ARG_ENABLE([experimental-async-api],, + [AS_HELP_STRING([--enable-experimental-async-api], + [Enable experimental async API support])], + [no], + [no]) + +if test "$PHP_EXPERIMENTAL_ASYNC_API" = "yes"; then + AC_DEFINE([PHP_ASYNC_API], 1, [Enable async API support]) +fi dnl Extension configuration. dnl ---------------------------------------------------------------------------- @@ -1788,6 +1797,11 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ ]), [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $fiber_asm_cflag]) +if test "$PHP_EXPERIMENTAL_ASYNC_API" = "yes"; then + PHP_ADD_SOURCES([Zend], m4_normalize([zend_async_API.c]), + [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $fiber_asm_cflag]) +fi + PHP_ADD_MAKEFILE_FRAGMENT([$abs_srcdir/scripts/Makefile.frag], [$abs_srcdir/scripts], [scripts]) diff --git a/main/main.c b/main/main.c index 18c8e2dfac7ec..cb50e9c8f89ac 100644 --- a/main/main.c +++ b/main/main.c @@ -85,6 +85,10 @@ #include "rfc1867.h" #include "main_arginfo.h" + +#ifdef PHP_ASYNC_API +#include "zend_async_API.h" +#endif /* }}} */ PHPAPI int (*php_register_internal_extensions_func)(void) = php_register_internal_extensions; @@ -141,6 +145,9 @@ PHPAPI char *php_get_version(sapi_module_struct *sapi_module) #endif #ifdef HAVE_GCOV " GCOV" +#endif +#ifdef PHP_ASYNC_API + " " ZEND_ASYNC_API #endif ); smart_string_appends(&version_info, "Copyright (c) The PHP Group\n"); @@ -149,7 +156,6 @@ PHPAPI char *php_get_version(sapi_module_struct *sapi_module) } smart_string_appends(&version_info, get_zend_version()); smart_string_0(&version_info); - return version_info.c; } @@ -2176,6 +2182,9 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi php_startup_ticks(); #endif gc_globals_ctor(); +#ifdef PHP_ASYNC_API + zend_async_globals_ctor(); +#endif zend_observer_startup(); #if ZEND_DEBUG @@ -2504,6 +2513,9 @@ void php_module_shutdown(void) #ifndef ZTS core_globals_dtor(&core_globals); gc_globals_dtor(); + #ifdef PHP_ASYNC_API + zend_async_globals_dtor(); + #endif #else ts_free_id(core_globals_id); #endif @@ -2596,6 +2608,9 @@ PHPAPI bool php_execute_script_ex(zend_file_handle *primary_file, zval *retval) if (append_file_p && result) { result = zend_execute_script(ZEND_REQUIRE, NULL, append_file_p) == SUCCESS; } + #ifdef PHP_ASYNC_API + ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN(); + #endif } zend_catch { result = false; } zend_end_try(); @@ -2768,6 +2783,9 @@ PHPAPI void php_reserve_tsrm_memory(void) TSRM_ALIGNED_SIZE(zend_gc_globals_size()) + TSRM_ALIGNED_SIZE(sizeof(php_core_globals)) + TSRM_ALIGNED_SIZE(sizeof(sapi_globals_struct)) +#ifdef PHP_ASYNC_API + + TSRM_ALIGNED_SIZE(sizeof(zend_async_globals_t)) +#endif ); } /* }}} */ diff --git a/win32/build/config.w32 b/win32/build/config.w32 index f509e4eae9337..6306ca482675f 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -422,3 +422,10 @@ toolset_setup_intrinsic_cflags(); ARG_ENABLE('snapshot-build', 'Build a snapshot: turns on everything it can and ignores build errors', 'no'); ARG_ENABLE('vs-link-compat', 'Allow linking of libraries built with compatible versions of VS toolset', 'yes'); + +ARG_ENABLE("experimental-async-api", "Enable experimental async API support", "no"); + +if (PHP_EXPERIMENTAL_ASYNC_API == "yes") { + AC_DEFINE('PHP_ASYNC_API', 1, 'Enable experimental async API support'); + ADD_SOURCES("Zend", "zend_async_API.c"); +} \ No newline at end of file From e41889d02481dbd4854812087929bd0cb74c5474 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 24 May 2025 14:40:10 +0300 Subject: [PATCH 002/137] * add ext/async to gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 55c441323cf15..bd16ac90bbc74 100644 --- a/.gitignore +++ b/.gitignore @@ -309,3 +309,8 @@ tmp-php.ini !/ext/dom/lexbor/patches/*.patch !/ext/pcre/pcre2lib/config.h !/win32/build/Makefile + +# ------------------------------------------------------------------------------ +# True Asynchronous extensions +# ------------------------------------------------------------------------------ +/ext/async \ No newline at end of file From 29784505e85fb9457128a37d63ff6e724a5ec39c Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 24 May 2025 16:37:09 +0300 Subject: [PATCH 003/137] + Add CancellationException class for async API support --- Zend/zend_cancellation_exception.stub.php | 63 +++++++++++++ Zend/zend_cancellation_exception_arginfo.h | 102 +++++++++++++++++++++ Zend/zend_exceptions.c | 37 +++++++- Zend/zend_exceptions.h | 3 + 4 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 Zend/zend_cancellation_exception.stub.php create mode 100644 Zend/zend_cancellation_exception_arginfo.h diff --git a/Zend/zend_cancellation_exception.stub.php b/Zend/zend_cancellation_exception.stub.php new file mode 100644 index 0000000000000..8ffdd2a0fdc33 --- /dev/null +++ b/Zend/zend_cancellation_exception.stub.php @@ -0,0 +1,63 @@ +parent; } if (zend_string_equals_literal(root->name, "Exception") - || zend_string_equals_literal(root->name, "Error")) { + || zend_string_equals_literal(root->name, "Error") +#ifdef PHP_ASYNC_API + || zend_string_equals_literal(root->name, "CancellationException") +#endif + ) { return SUCCESS; } @@ -91,7 +101,27 @@ static int zend_implement_throwable(zend_class_entry *interface, zend_class_entr static inline zend_class_entry *i_get_exception_base(zend_object *object) /* {{{ */ { +#ifndef PHP_ASYNC_API return instanceof_function(object->ce, zend_ce_exception) ? zend_ce_exception : zend_ce_error; +#else + zend_class_entry *instance_ce = object->ce; + + do + { + if (instance_ce == zend_ce_exception) { + return zend_ce_exception; + } else if (instance_ce == zend_ce_error) { + return zend_ce_error; + } else if (instance_ce == zend_ce_cancellation_exception) { + return zend_ce_cancellation_exception; + } + + instance_ce = instance_ce->parent; + + } while (instance_ce != NULL); + + return NULL; +#endif } /* }}} */ @@ -794,6 +824,11 @@ void zend_register_default_exception(void) /* {{{ */ zend_ce_error = register_class_Error(zend_ce_throwable); zend_init_exception_class_entry(zend_ce_error); +#ifdef PHP_ASYNC_API + zend_ce_cancellation_exception = register_class_CancellationException(zend_ce_throwable); + zend_init_exception_class_entry(zend_ce_cancellation_exception); +#endif + zend_ce_compile_error = register_class_CompileError(zend_ce_error); zend_init_exception_class_entry(zend_ce_compile_error); diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index d0138021d1ea3..358d6b0e56540 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -30,6 +30,9 @@ extern ZEND_API zend_class_entry *zend_ce_throwable; extern ZEND_API zend_class_entry *zend_ce_exception; extern ZEND_API zend_class_entry *zend_ce_error_exception; extern ZEND_API zend_class_entry *zend_ce_error; +#ifdef PHP_ASYNC_API +extern ZEND_API zend_class_entry *zend_ce_cancellation_exception; +#endif extern ZEND_API zend_class_entry *zend_ce_compile_error; extern ZEND_API zend_class_entry *zend_ce_parse_error; extern ZEND_API zend_class_entry *zend_ce_type_error; From 3c7e4c08e549b06b70b3c6924d4c0d8877d3c75c Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 25 May 2025 16:17:29 +0300 Subject: [PATCH 004/137] % Rename ZendAsync => TrueAsync --- Zend/zend_async_API.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index c2fcff89002f6..16880ff787ea8 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -19,7 +19,7 @@ #include "zend_fibers.h" #include "zend_globals.h" -#define ZEND_ASYNC_API "ZendAsync API v1.0.0-dev" +#define ZEND_ASYNC_API "TrueAsync API v1.0.0-dev" #define ZEND_ASYNC_API_VERSION_MAJOR 1 #define ZEND_ASYNC_API_VERSION_MINOR 0 #define ZEND_ASYNC_API_VERSION_PATCH 0 From e799d31d67219329f41483cebe4e7c4ab14b0888 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 25 May 2025 17:13:17 +0300 Subject: [PATCH 005/137] % The method for retrieving exceptions for TrueAsync has been generalized into a method for retrieving any ClassEntry. Now, using this API function, you can obtain the required class entry by a type descriptor type. --- Zend/zend_async_API.c | 32 +++++++++++++++++++++++++------- Zend/zend_async_API.h | 38 ++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index e13b4f21be2d2..bb18b8127ee10 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -34,9 +34,19 @@ static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_pr static void suspend(bool from_main) {} -static zend_class_entry * get_exception_ce(zend_async_exception_type type) +static zend_class_entry * get_class_ce(zend_async_class type) { - return zend_ce_exception; + if (type == ZEND_ASYNC_EXCEPTION_DEFAULT + || type == ZEND_ASYNC_EXCEPTION_DNS + || type == ZEND_ASYNC_EXCEPTION_POLL + || type == ZEND_ASYNC_EXCEPTION_TIMEOUT + || type == ZEND_ASYNC_EXCEPTION_INPUT_OUTPUT) { + return zend_ce_exception; + } else if (type == ZEND_ASYNC_EXCEPTION_CANCELLATION) { + return zend_ce_cancellation_exception; + } + + return NULL; } static zend_string * scheduler_module_name = NULL; @@ -50,7 +60,7 @@ zend_async_shutdown_t zend_async_shutdown_fn = NULL; zend_async_get_coroutines_t zend_async_get_coroutines_fn = NULL; zend_async_add_microtask_t zend_async_add_microtask_fn = NULL; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = NULL; -zend_async_get_exception_ce_t zend_async_get_exception_ce_fn = get_exception_ce; +zend_async_get_class_ce_t zend_async_get_class_ce_fn = get_class_ce; static zend_string * reactor_module_name = NULL; zend_async_reactor_startup_t zend_async_reactor_startup_fn = NULL; @@ -161,7 +171,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_exception_ce_t get_exception_ce_fn + zend_async_get_class_ce_t get_class_ce_fn ) { if (scheduler_module_name != NULL && false == allow_override) { @@ -188,7 +198,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_coroutines_fn = get_coroutines_fn; zend_async_add_microtask_fn = add_microtask_fn; zend_async_get_awaiting_info_fn = get_awaiting_info_fn; - zend_async_get_exception_ce_fn = get_exception_ce_fn; + zend_async_get_class_ce_fn = get_class_ce_fn; } ZEND_API void zend_async_reactor_register( @@ -594,8 +604,12 @@ ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( /* Exception API */ ////////////////////////////////////////////////////////////////////// -ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_exception_type type, const char *format, ...) +ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_class type, const char *format, ...) { + if (type == ZEND_ASYNC_CLASS_NO) { + type = ZEND_ASYNC_EXCEPTION_DEFAULT; + } + zend_class_entry *exception_ce = ZEND_ASYNC_GET_EXCEPTION_CE(type); zval exception, message_val; @@ -619,8 +633,12 @@ ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_exception_t return Z_OBJ(exception); } -ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_exception_type type, const char *format, ...) +ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_class type, const char *format, ...) { + if (type == ZEND_ASYNC_CLASS_NO) { + type = ZEND_ASYNC_EXCEPTION_DEFAULT; + } + va_list args; va_start(args, format); zend_string *message = zend_vstrpprintf(0, format, args); diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 16880ff787ea8..e9edc944b82b0 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -90,12 +90,25 @@ typedef enum { typedef enum { - ZEND_ASYNC_EXCEPTION_DEFAULT = 0, - ZEND_ASYNC_EXCEPTION_CANCELLATION = 1, - ZEND_ASYNC_EXCEPTION_TIMEOUT = 2, - ZEND_ASYNC_EXCEPTION_INPUT_OUTPUT = 3, - ZEND_ASYNC_EXCEPTION_POLL = 4 -} zend_async_exception_type; + ZEND_ASYNC_CLASS_NO = 0, + ZEND_ASYNC_CLASS_AWAITABLE = 1, + ZEND_ASYNC_CLASS_COROUTINE = 2, + ZEND_ASYNC_CLASS_SCOPE = 3, + ZEND_ASYNC_CLASS_CONTEXT = 4, + ZEND_ASYNC_CLASS_SCOPE_PROVIDER = 5, + ZEND_ASYNC_CLASS_SPAWN_STRATEGY = 6, + ZEND_ASYNC_CLASS_TIMEOUT = 7, + + ZEND_ASYNC_CLASS_CHANNEL = 10, + ZEND_ASYNC_CLASS_FUTURE = 11, + + ZEND_ASYNC_EXCEPTION_DEFAULT = 30, + ZEND_ASYNC_EXCEPTION_CANCELLATION = 31, + ZEND_ASYNC_EXCEPTION_TIMEOUT = 32, + ZEND_ASYNC_EXCEPTION_INPUT_OUTPUT = 33, + ZEND_ASYNC_EXCEPTION_POLL = 34, + ZEND_ASYNC_EXCEPTION_DNS = 35, +} zend_async_class; /** * zend_coroutine_t is a Basic data structure that represents a coroutine in the Zend Engine. @@ -151,7 +164,7 @@ typedef void (*zend_async_shutdown_t)(void); typedef zend_array* (*zend_async_get_coroutines_t)(void); typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); -typedef zend_class_entry* (*zend_async_get_exception_ce_t)(zend_async_exception_type type); +typedef zend_class_entry* (*zend_async_get_class_ce_t)(zend_async_class type); typedef void (*zend_async_reactor_startup_t)(void); typedef void (*zend_async_reactor_shutdown_t)(void); @@ -814,8 +827,8 @@ void zend_async_globals_dtor(void); ZEND_API const char * zend_async_get_api_version(void); ZEND_API int zend_async_get_api_version_number(void); -ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_exception_type type, const char *format, ...); -ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_exception_type type, const char *format, ...); +ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_class type, const char *format, ...); +ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_class type, const char *format, ...); ZEND_API ZEND_COLD zend_object * zend_async_throw_cancellation(const char *format, ...); ZEND_API ZEND_COLD zend_object * zend_async_throw_timeout(const char *format, const zend_long timeout); @@ -831,7 +844,7 @@ ZEND_API extern zend_async_shutdown_t zend_async_shutdown_fn; ZEND_API extern zend_async_get_coroutines_t zend_async_get_coroutines_fn; ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; -ZEND_API extern zend_async_get_exception_ce_t zend_async_get_exception_ce_fn; +ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; /* Reactor API */ @@ -874,7 +887,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_exception_ce_t get_exception_ce_fn + zend_async_get_class_ce_t get_class_ce_fn ); ZEND_API void zend_async_reactor_register( @@ -949,7 +962,8 @@ END_EXTERN_C() #define ZEND_ASYNC_GET_COROUTINES() zend_async_get_coroutines_fn() #define ZEND_ASYNC_ADD_MICROTASK(microtask) zend_async_add_microtask_fn(microtask) #define ZEND_ASYNC_GET_AWAITING_INFO(coroutine) zend_async_get_awaiting_info_fn(coroutine) -#define ZEND_ASYNC_GET_EXCEPTION_CE(type) zend_async_get_exception_ce_fn(type) +#define ZEND_ASYNC_GET_CE(type) zend_async_get_class_ce_fn(type) +#define ZEND_ASYNC_GET_EXCEPTION_CE(type) zend_async_get_class_ce_fn(type) #define ZEND_ASYNC_REACTOR_IS_ENABLED() zend_async_reactor_is_enabled() #define ZEND_ASYNC_REACTOR_STARTUP() zend_async_reactor_startup_fn() From 7c0f79751ef0f76b9d684b96b7d19226d89f4a2b Mon Sep 17 00:00:00 2001 From: Edmond Date: Sun, 25 May 2025 18:11:10 +0300 Subject: [PATCH 006/137] + Add Context API for coroutine key-value storage Added API functions for coroutine context management: - zend_async_context_set_t: set value by string or object key - zend_async_context_get_t: get value by string or object key - zend_async_context_has_t: check if key exists - zend_async_context_delete_t: delete key-value pair Includes convenience macros for easier usage: ZEND_ASYNC_CONTEXT_SET_STR/OBJ, GET_STR/OBJ, HAS_STR/OBJ, DELETE_STR/OBJ --- Zend/zend_async_API.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index e9edc944b82b0..e0210d0f2aad3 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -166,6 +166,12 @@ typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); typedef zend_class_entry* (*zend_async_get_class_ce_t)(zend_async_class type); +/* Context API - Key-Value map for coroutines */ +typedef bool (*zend_async_context_set_t)(const char *str_key, zend_object *obj_key, zval *value); +typedef bool (*zend_async_context_get_t)(const char *str_key, zend_object *obj_key, zval *result); +typedef bool (*zend_async_context_has_t)(const char *str_key, zend_object *obj_key); +typedef bool (*zend_async_context_delete_t)(const char *str_key, zend_object *obj_key); + typedef void (*zend_async_reactor_startup_t)(void); typedef void (*zend_async_reactor_shutdown_t)(void); typedef bool (*zend_async_reactor_execute_t)(bool no_wait); @@ -846,6 +852,14 @@ ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; +/* Context API */ +ZEND_API extern zend_async_context_set_t zend_async_context_set_fn; +ZEND_API extern zend_async_context_get_t zend_async_context_get_fn; +ZEND_API extern zend_async_context_has_t zend_async_context_has_fn; +ZEND_API extern zend_async_context_delete_t zend_async_context_delete_fn; + + + /* Reactor API */ ZEND_API bool zend_async_reactor_is_enabled(void); @@ -999,4 +1013,14 @@ END_EXTERN_C() #define ZEND_ASYNC_QUEUE_TASK(task) zend_async_queue_task_fn(task) +/* Context API Macros */ +#define ZEND_ASYNC_CONTEXT_SET_STR(str_key, value) zend_async_context_set_fn(str_key, NULL, value) +#define ZEND_ASYNC_CONTEXT_SET_OBJ(obj_key, value) zend_async_context_set_fn(NULL, obj_key, value) +#define ZEND_ASYNC_CONTEXT_GET_STR(str_key, result) zend_async_context_get_fn(str_key, NULL, result) +#define ZEND_ASYNC_CONTEXT_GET_OBJ(obj_key, result) zend_async_context_get_fn(NULL, obj_key, result) +#define ZEND_ASYNC_CONTEXT_HAS_STR(str_key) zend_async_context_has_fn(str_key, NULL) +#define ZEND_ASYNC_CONTEXT_HAS_OBJ(obj_key) zend_async_context_has_fn(NULL, obj_key) +#define ZEND_ASYNC_CONTEXT_DELETE_STR(str_key) zend_async_context_delete_fn(str_key, NULL) +#define ZEND_ASYNC_CONTEXT_DELETE_OBJ(obj_key) zend_async_context_delete_fn(NULL, obj_key) + #endif //ZEND_ASYNC_API_H \ No newline at end of file From a5d445d60f183ad3f6f8202123d8ddd3caefa1e6 Mon Sep 17 00:00:00 2001 From: Edmond Date: Sun, 25 May 2025 18:36:29 +0300 Subject: [PATCH 007/137] + Implement Context API for coroutine key-value storage Added complete Context API implementation: - Updated zend_async_scheduler_register signature to accept context functions - Added context function pointers and registration in zend_async_API.c - Context API supports both string and object keys - Includes convenience macros for easy usage This completes the core API infrastructure for coroutine context management. --- Zend/zend_async_API.c | 16 +++++++++++++++- Zend/zend_async_API.h | 6 +++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index bb18b8127ee10..c6f44a3570f69 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -62,6 +62,12 @@ zend_async_add_microtask_t zend_async_add_microtask_fn = NULL; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = NULL; zend_async_get_class_ce_t zend_async_get_class_ce_fn = get_class_ce; +/* Context API */ +zend_async_context_set_t zend_async_context_set_fn = NULL; +zend_async_context_get_t zend_async_context_get_fn = NULL; +zend_async_context_has_t zend_async_context_has_fn = NULL; +zend_async_context_delete_t zend_async_context_delete_fn = NULL; + static zend_string * reactor_module_name = NULL; zend_async_reactor_startup_t zend_async_reactor_startup_fn = NULL; zend_async_reactor_shutdown_t zend_async_reactor_shutdown_fn = NULL; @@ -171,7 +177,11 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_class_ce_t get_class_ce_fn + zend_async_get_class_ce_t get_class_ce_fn, + zend_async_context_set_t context_set_fn, + zend_async_context_get_t context_get_fn, + zend_async_context_has_t context_has_fn, + zend_async_context_delete_t context_delete_fn ) { if (scheduler_module_name != NULL && false == allow_override) { @@ -199,6 +209,10 @@ ZEND_API void zend_async_scheduler_register( zend_async_add_microtask_fn = add_microtask_fn; zend_async_get_awaiting_info_fn = get_awaiting_info_fn; zend_async_get_class_ce_fn = get_class_ce_fn; + zend_async_context_set_fn = context_set_fn; + zend_async_context_get_fn = context_get_fn; + zend_async_context_has_fn = context_has_fn; + zend_async_context_delete_fn = context_delete_fn; } ZEND_API void zend_async_reactor_register( diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index e0210d0f2aad3..73539d71cbc52 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -901,7 +901,11 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_class_ce_t get_class_ce_fn + zend_async_get_class_ce_t get_class_ce_fn, + zend_async_context_set_t context_set_fn, + zend_async_context_get_t context_get_fn, + zend_async_context_has_t context_has_fn, + zend_async_context_delete_t context_delete_fn ); ZEND_API void zend_async_reactor_register( From 18c94dccd236d3fb8ddc2290599415ae6474b3f5 Mon Sep 17 00:00:00 2001 From: Edmond Date: Mon, 26 May 2025 21:56:37 +0300 Subject: [PATCH 008/137] Refactor Context API to use single new_context function - Remove separate context_set, context_get, context_has, context_delete functions - Add single zend_async_new_context_fn function to create context instances - Move context implementation to ext/async module using zend_async_context_t structure - Update scheduler registration to include new_context_fn parameter - Add context field to zend_async_globals_t and zend_coroutine_s - Simplify Context API macros to ZEND_ASYNC_NEW_CONTEXT and ZEND_ASYNC_CURRENT_CONTEXT --- Zend/zend_async_API.c | 26 ++++++++++------------ Zend/zend_async_API.h | 52 +++++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index c6f44a3570f69..97507003842a5 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -34,6 +34,12 @@ static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_pr static void suspend(bool from_main) {} +static zend_async_context_t * new_context(zend_async_context_t *parent_context) +{ + ASYNC_THROW_ERROR("Context API is not enabled"); + return NULL; +} + static zend_class_entry * get_class_ce(zend_async_class type) { if (type == ZEND_ASYNC_EXCEPTION_DEFAULT @@ -62,12 +68,6 @@ zend_async_add_microtask_t zend_async_add_microtask_fn = NULL; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = NULL; zend_async_get_class_ce_t zend_async_get_class_ce_fn = get_class_ce; -/* Context API */ -zend_async_context_set_t zend_async_context_set_fn = NULL; -zend_async_context_get_t zend_async_context_get_fn = NULL; -zend_async_context_has_t zend_async_context_has_fn = NULL; -zend_async_context_delete_t zend_async_context_delete_fn = NULL; - static zend_string * reactor_module_name = NULL; zend_async_reactor_startup_t zend_async_reactor_startup_fn = NULL; zend_async_reactor_shutdown_t zend_async_reactor_shutdown_fn = NULL; @@ -90,6 +90,9 @@ zend_async_exec_t zend_async_exec_fn = NULL; static zend_string * thread_pool_module_name = NULL; zend_async_queue_task_t zend_async_queue_task_fn = NULL; +/* Context API */ +zend_async_new_context_t zend_async_new_context_fn = new_context; + ZEND_API bool zend_async_is_enabled(void) { return scheduler_module_name != NULL && reactor_module_name != NULL; @@ -115,6 +118,7 @@ static void ts_globals_ctor(zend_async_globals_t * globals) globals->active_event_count = 0; globals->coroutine = NULL; globals->scope = NULL; + globals->context = NULL; globals->scheduler = NULL; globals->exit_exception = NULL; } @@ -178,10 +182,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, zend_async_get_class_ce_t get_class_ce_fn, - zend_async_context_set_t context_set_fn, - zend_async_context_get_t context_get_fn, - zend_async_context_has_t context_has_fn, - zend_async_context_delete_t context_delete_fn + zend_async_new_context_t new_context_fn ) { if (scheduler_module_name != NULL && false == allow_override) { @@ -209,10 +210,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_add_microtask_fn = add_microtask_fn; zend_async_get_awaiting_info_fn = get_awaiting_info_fn; zend_async_get_class_ce_fn = get_class_ce_fn; - zend_async_context_set_fn = context_set_fn; - zend_async_context_get_fn = context_get_fn; - zend_async_context_has_fn = context_has_fn; - zend_async_context_delete_fn = context_delete_fn; + zend_async_new_context_fn = new_context_fn; } ZEND_API void zend_async_reactor_register( diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 73539d71cbc52..55f3fd83cf4df 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -114,6 +114,7 @@ typedef enum * zend_coroutine_t is a Basic data structure that represents a coroutine in the Zend Engine. */ typedef struct _zend_coroutine_s zend_coroutine_t; +typedef struct _zend_async_context_s zend_async_context_t; typedef struct _zend_async_waker_s zend_async_waker_t; typedef struct _zend_async_microtask_s zend_async_microtask_t; typedef struct _zend_async_scope_s zend_async_scope_t; @@ -166,12 +167,6 @@ typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); typedef zend_class_entry* (*zend_async_get_class_ce_t)(zend_async_class type); -/* Context API - Key-Value map for coroutines */ -typedef bool (*zend_async_context_set_t)(const char *str_key, zend_object *obj_key, zval *value); -typedef bool (*zend_async_context_get_t)(const char *str_key, zend_object *obj_key, zval *result); -typedef bool (*zend_async_context_has_t)(const char *str_key, zend_object *obj_key); -typedef bool (*zend_async_context_delete_t)(const char *str_key, zend_object *obj_key); - typedef void (*zend_async_reactor_startup_t)(void); typedef void (*zend_async_reactor_shutdown_t)(void); typedef bool (*zend_async_reactor_execute_t)(bool no_wait); @@ -681,6 +676,9 @@ struct _zend_coroutine_s { /* Storage for return value. */ zval result; + /* Coroutine context object */ + zend_async_context_t *context; + /* Spawned file and line number */ zend_string *filename; uint32_t lineno; @@ -727,6 +725,24 @@ static zend_always_inline zend_string *zend_coroutine_callable_name(const zend_c return zend_string_init("internal function", sizeof("internal function") - 1, 0); } +/////////////////////////////////////////////////////////////// +/// Async Context Structures +/////////////////////////////////////////////////////////////// + +typedef zend_async_context_t * (*zend_async_new_context_t)(zend_async_context_t *parent_context); +typedef void (*zend_async_context_find_t)(zend_async_context_t *context, zval *key, zval *result); +typedef void (*zend_async_context_set_t)(zend_async_context_t *context, zval *key, zval *value); +typedef void (*zend_async_context_unset_t)(zend_async_context_t *context, zval *key); +typedef void (*zend_async_context_dispose_t)(zend_async_context_t *context); + +struct _zend_async_context_s { + zend_async_context_find_t find; + zend_async_context_set_t set; + zend_async_context_unset_t unset; + zend_async_context_dispose_t dispose; + uint32_t offset; /* offset of the context zend object */ +}; + /////////////////////////////////////////////////////////////// /// Global Macros /////////////////////////////////////////////////////////////// @@ -758,6 +774,8 @@ typedef struct { zend_coroutine_t *coroutine; /* The current async scope. */ zend_async_scope_t *scope; + /* The current async context. */ + zend_async_context_t *context; /* Scheduler coroutine */ zend_coroutine_t *scheduler; /* Exit exception object */ @@ -853,12 +871,7 @@ ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; /* Context API */ -ZEND_API extern zend_async_context_set_t zend_async_context_set_fn; -ZEND_API extern zend_async_context_get_t zend_async_context_get_fn; -ZEND_API extern zend_async_context_has_t zend_async_context_has_fn; -ZEND_API extern zend_async_context_delete_t zend_async_context_delete_fn; - - +ZEND_API extern zend_async_new_context_t zend_async_new_context_fn; /* Reactor API */ @@ -902,10 +915,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, zend_async_get_class_ce_t get_class_ce_fn, - zend_async_context_set_t context_set_fn, - zend_async_context_get_t context_get_fn, - zend_async_context_has_t context_has_fn, - zend_async_context_delete_t context_delete_fn + zend_async_new_context_t new_context_fn ); ZEND_API void zend_async_reactor_register( @@ -1018,13 +1028,7 @@ END_EXTERN_C() #define ZEND_ASYNC_QUEUE_TASK(task) zend_async_queue_task_fn(task) /* Context API Macros */ -#define ZEND_ASYNC_CONTEXT_SET_STR(str_key, value) zend_async_context_set_fn(str_key, NULL, value) -#define ZEND_ASYNC_CONTEXT_SET_OBJ(obj_key, value) zend_async_context_set_fn(NULL, obj_key, value) -#define ZEND_ASYNC_CONTEXT_GET_STR(str_key, result) zend_async_context_get_fn(str_key, NULL, result) -#define ZEND_ASYNC_CONTEXT_GET_OBJ(obj_key, result) zend_async_context_get_fn(NULL, obj_key, result) -#define ZEND_ASYNC_CONTEXT_HAS_STR(str_key) zend_async_context_has_fn(str_key, NULL) -#define ZEND_ASYNC_CONTEXT_HAS_OBJ(obj_key) zend_async_context_has_fn(NULL, obj_key) -#define ZEND_ASYNC_CONTEXT_DELETE_STR(str_key) zend_async_context_delete_fn(str_key, NULL) -#define ZEND_ASYNC_CONTEXT_DELETE_OBJ(obj_key) zend_async_context_delete_fn(NULL, obj_key) +#define ZEND_ASYNC_NEW_CONTEXT(parent) zend_async_new_context_fn(parent) +#define ZEND_ASYNC_CURRENT_CONTEXT ZEND_ASYNC_G(context) #endif //ZEND_ASYNC_API_H \ No newline at end of file From b5a7b73e9d0929e7730c2ce5e2d9a92832303555 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 27 May 2025 09:27:58 +0300 Subject: [PATCH 009/137] * Fixes in coroutine context implementation. --- Zend/zend_async_API.c | 9 ++++----- Zend/zend_async_API.h | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 97507003842a5..a9f012fd97f36 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -34,7 +34,7 @@ static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_pr static void suspend(bool from_main) {} -static zend_async_context_t * new_context(zend_async_context_t *parent_context) +static zend_async_context_t * new_context(void) { ASYNC_THROW_ERROR("Context API is not enabled"); return NULL; @@ -118,7 +118,6 @@ static void ts_globals_ctor(zend_async_globals_t * globals) globals->active_event_count = 0; globals->coroutine = NULL; globals->scope = NULL; - globals->context = NULL; globals->scheduler = NULL; globals->exit_exception = NULL; } @@ -173,6 +172,7 @@ ZEND_API void zend_async_scheduler_register( bool allow_override, zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, + zend_async_new_context_t new_context_fn, zend_async_spawn_t spawn_fn, zend_async_suspend_t suspend_fn, zend_async_resume_t resume_fn, @@ -181,8 +181,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_class_ce_t get_class_ce_fn, - zend_async_new_context_t new_context_fn + zend_async_get_class_ce_t get_class_ce_fn ) { if (scheduler_module_name != NULL && false == allow_override) { @@ -201,6 +200,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_new_coroutine_fn = new_coroutine_fn; zend_async_new_scope_fn = new_scope_fn; + zend_async_new_context_fn = new_context_fn; zend_async_spawn_fn = spawn_fn; zend_async_suspend_fn = suspend_fn; zend_async_resume_fn = resume_fn; @@ -210,7 +210,6 @@ ZEND_API void zend_async_scheduler_register( zend_async_add_microtask_fn = add_microtask_fn; zend_async_get_awaiting_info_fn = get_awaiting_info_fn; zend_async_get_class_ce_fn = get_class_ce_fn; - zend_async_new_context_fn = new_context_fn; } ZEND_API void zend_async_reactor_register( diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 55f3fd83cf4df..34e5569ac349f 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -538,6 +538,8 @@ struct _zend_async_scope_s { zend_async_scopes_vector_t scopes; zend_async_scope_t *parent_scope; + /* Scope context object */ + zend_async_context_t *context; zend_async_before_coroutine_enqueue_t before_coroutine_enqueue; zend_async_after_coroutine_enqueue_t after_coroutine_enqueue; @@ -729,18 +731,21 @@ static zend_always_inline zend_string *zend_coroutine_callable_name(const zend_c /// Async Context Structures /////////////////////////////////////////////////////////////// -typedef zend_async_context_t * (*zend_async_new_context_t)(zend_async_context_t *parent_context); -typedef void (*zend_async_context_find_t)(zend_async_context_t *context, zval *key, zval *result); +typedef zend_async_context_t * (*zend_async_new_context_t)(void); +typedef bool (*zend_async_context_find_t)(zend_async_context_t *context, zval *key, zval *result, bool include_parent); typedef void (*zend_async_context_set_t)(zend_async_context_t *context, zval *key, zval *value); -typedef void (*zend_async_context_unset_t)(zend_async_context_t *context, zval *key); +typedef bool (*zend_async_context_unset_t)(zend_async_context_t *context, zval *key); typedef void (*zend_async_context_dispose_t)(zend_async_context_t *context); struct _zend_async_context_s { + /* flags for the context: reserved */ + uint32_t flags; + /* offset of the context zend object */ + uint32_t offset; zend_async_context_find_t find; zend_async_context_set_t set; zend_async_context_unset_t unset; zend_async_context_dispose_t dispose; - uint32_t offset; /* offset of the context zend object */ }; /////////////////////////////////////////////////////////////// @@ -774,8 +779,6 @@ typedef struct { zend_coroutine_t *coroutine; /* The current async scope. */ zend_async_scope_t *scope; - /* The current async context. */ - zend_async_context_t *context; /* Scheduler coroutine */ zend_coroutine_t *scheduler; /* Exit exception object */ @@ -906,6 +909,7 @@ ZEND_API void zend_async_scheduler_register( bool allow_override, zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, + zend_async_new_context_t new_context_fn, zend_async_spawn_t spawn_fn, zend_async_suspend_t suspend_fn, zend_async_resume_t resume_fn, @@ -914,8 +918,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_class_ce_t get_class_ce_fn, - zend_async_new_context_t new_context_fn + zend_async_get_class_ce_t get_class_ce_fn ); ZEND_API void zend_async_reactor_register( @@ -1029,6 +1032,6 @@ END_EXTERN_C() /* Context API Macros */ #define ZEND_ASYNC_NEW_CONTEXT(parent) zend_async_new_context_fn(parent) -#define ZEND_ASYNC_CURRENT_CONTEXT ZEND_ASYNC_G(context) +#define ZEND_ASYNC_CURRENT_CONTEXT ZEND_ASYNC_G(scope)->context #endif //ZEND_ASYNC_API_H \ No newline at end of file From a937b6371b248934866b7b85ea767969d10657d8 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 27 May 2025 11:29:14 +0300 Subject: [PATCH 010/137] + config-true-async.yml --- .circleci/config-true-async.yml | 191 ++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 .circleci/config-true-async.yml diff --git a/.circleci/config-true-async.yml b/.circleci/config-true-async.yml new file mode 100644 index 0000000000000..34ff94b2bd39c --- /dev/null +++ b/.circleci/config-true-async.yml @@ -0,0 +1,191 @@ +version: 2.1 + +jobs: + arm: + resource_class: arm.medium + docker: + - image: cimg/base:current-22.04 + - image: mysql:8.3 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: true + MYSQL_ROOT_PASSWORD: '' + MYSQL_DATABASE: test + - image: postgres:16 + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + environment: + LANGUAGE: '' + LANG: en_US.UTF-8 + MYSQL_TEST_HOST: '127.0.0.1' + MYSQL_TEST_PASSWD: '' + MYSQL_TEST_USER: root + PDO_MYSQL_TEST_DSN: 'mysql:host=127.0.0.1;dbname=test' + PDO_MYSQL_TEST_PASS: '' + PDO_MYSQL_TEST_USER: root + PDO_PGSQL_TEST_DSN: 'pgsql:host=127.0.0.1 port=5432 dbname=test user=postgres password=postgres' + steps: + - checkout + - run: + name: Install system dependencies (including libuv) + command: | + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update -y + sudo apt-get install -y \ + gcc \ + g++ \ + autoconf \ + bison \ + re2c \ + locales \ + locales-all \ + ldap-utils \ + openssl \ + slapd \ + libgmp-dev \ + libicu-dev \ + libtidy-dev \ + libenchant-2-dev \ + libsasl2-dev \ + libxpm-dev \ + libzip-dev \ + libbz2-dev \ + libsqlite3-dev \ + libwebp-dev \ + libonig-dev \ + libcurl4-openssl-dev \ + libxml2-dev \ + libxslt1-dev \ + libpq-dev \ + libreadline-dev \ + libldap2-dev \ + libsodium-dev \ + libargon2-0-dev \ + libmm-dev \ + libsnmp-dev \ + snmpd \ + freetds-dev \ + dovecot-core \ + dovecot-pop3d \ + dovecot-imapd \ + sendmail \ + firebird-dev \ + liblmdb-dev \ + libtokyocabinet-dev \ + libdb-dev \ + libqdbm-dev \ + libjpeg-dev \ + libpng-dev \ + libfreetype6-dev \ + libuv1-dev # <-- ДОБАВЛЕНО + - run: + name: Download php-async (main) into ext/ + command: | + git clone --depth=1 --branch=main https://github.com/true-async/php-async /tmp/php-async + cp -r /tmp/php-async/* ./ext/ + - run: + name: ./configure + command: | + ./buildconf -f + ./configure \ + --enable-debug \ + --enable-zts \ + --enable-option-checking=fatal \ + --prefix=/usr \ + --enable-phpdbg \ + --enable-fpm \ + --enable-opcache \ + --with-pdo-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pgsql \ + --with-pdo-pgsql \ + --with-pdo-sqlite \ + --enable-intl \ + --without-pear \ + --enable-gd \ + --with-jpeg \ + --with-webp \ + --with-freetype \ + --with-xpm \ + --enable-exif \ + --with-zip \ + --with-zlib \ + --enable-soap \ + --enable-xmlreader \ + --with-xsl \ + --with-tidy \ + --enable-sysvsem \ + --enable-sysvshm \ + --enable-shmop \ + --enable-pcntl \ + --with-readline \ + --enable-mbstring \ + --with-curl \ + --with-gettext \ + --enable-sockets \ + --with-bz2 \ + --with-openssl \ + --with-gmp \ + --enable-bcmath \ + --enable-calendar \ + --enable-ftp \ + --with-enchant=/usr \ + --enable-sysvmsg \ + --with-ffi \ + --enable-zend-test \ + --enable-dl-test=shared \ + --with-ldap \ + --with-ldap-sasl \ + --with-password-argon2 \ + --with-mhash \ + --with-sodium \ + --enable-dba \ + --with-cdb \ + --enable-flatfile \ + --enable-inifile \ + --with-tcadb \ + --with-lmdb \ + --with-qdbm \ + --with-snmp \ + --with-config-file-path=/etc \ + --with-config-file-scan-dir=/etc/php.d \ + --with-pdo-firebird \ + --enable-experimental-async-api \ # <-- PHP true async API support + --enable-async \ # <-- PHP true async extension + --disable-phpdbg + - run: + name: make + no_output_timeout: 30m + command: make -j2 > /dev/null + - run: + name: make install + command: | + sudo make install + sudo mkdir -p /etc/php.d + sudo chmod 777 /etc/php.d + echo opcache.enable_cli=1 > /etc/php.d/opcache.ini + echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini + - run: + name: Test + no_output_timeout: 30m + command: | + sapi/cli/php run-tests.php \ + -d zend_extension=opcache.so \ + -d opcache.enable_cli=1 \ + -d opcache.jit_buffer_size=64M \ + -d opcache.jit=tracing \ + -d zend_test.observer.enabled=1 \ + -d zend_test.observer.show_output=0 \ + -P -q -x -j2 \ + -g FAIL,BORK,LEAK,XLEAK \ + --no-progress \ + --offline \ + --show-diff \ + --show-slow 1000 \ + --set-timeout 120 \ + --repeat 2 + +workflows: + push-workflow: + jobs: + - arm From d7132fd3cf6324625337c907d95c8e08d2ae9ab6 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 27 May 2025 11:35:52 +0300 Subject: [PATCH 011/137] + config-true-async.yml --- .circleci/config-true-async.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config-true-async.yml b/.circleci/config-true-async.yml index 34ff94b2bd39c..ae2f7458e2e18 100644 --- a/.circleci/config-true-async.yml +++ b/.circleci/config-true-async.yml @@ -27,7 +27,7 @@ jobs: steps: - checkout - run: - name: Install system dependencies (including libuv) + name: Install system dependencies (including true async + libuv) command: | export DEBIAN_FRONTEND=noninteractive sudo apt-get update -y @@ -77,7 +77,7 @@ jobs: libjpeg-dev \ libpng-dev \ libfreetype6-dev \ - libuv1-dev # <-- ДОБАВЛЕНО + libuv1-dev - run: name: Download php-async (main) into ext/ command: | From f915035d7fa1071d3aa55e5d8ced95c53ffc814e Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 27 May 2025 17:12:40 +0300 Subject: [PATCH 012/137] + Enhance coroutine creation and scheduler scope handling % removal of the current Scope from the global structure --- Zend/zend_async_API.c | 1 - Zend/zend_async_API.h | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index a9f012fd97f36..7a4da9b58d0c6 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -117,7 +117,6 @@ static void ts_globals_ctor(zend_async_globals_t * globals) globals->active_coroutine_count = 0; globals->active_event_count = 0; globals->coroutine = NULL; - globals->scope = NULL; globals->scheduler = NULL; globals->exit_exception = NULL; } diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 34e5569ac349f..5b9b276ebdc65 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -813,7 +813,7 @@ END_EXTERN_C() #define ZEND_ASYNC_GRACEFUL_SHUTDOWN ZEND_ASYNC_G(graceful_shutdown) #define ZEND_ASYNC_EXIT_EXCEPTION ZEND_ASYNC_G(exit_exception) #define ZEND_ASYNC_CURRENT_COROUTINE ZEND_ASYNC_G(coroutine) -#define ZEND_ASYNC_CURRENT_SCOPE ZEND_ASYNC_G(scope) +#define ZEND_ASYNC_CURRENT_SCOPE ZEND_ASYNC_G(coroutine)->scope #define ZEND_ASYNC_SCHEDULER ZEND_ASYNC_G(scheduler) #define ZEND_ASYNC_INCREASE_EVENT_COUNT if (ZEND_ASYNC_G(active_event_count) < UINT_MAX) { \ @@ -1032,6 +1032,6 @@ END_EXTERN_C() /* Context API Macros */ #define ZEND_ASYNC_NEW_CONTEXT(parent) zend_async_new_context_fn(parent) -#define ZEND_ASYNC_CURRENT_CONTEXT ZEND_ASYNC_G(scope)->context +#define ZEND_ASYNC_CURRENT_CONTEXT (ZEND_ASYNC_G(coroutine) != NULL ? ZEND_ASYNC_G(coroutine)->scope->context : NULL) #endif //ZEND_ASYNC_API_H \ No newline at end of file From cf5233520549b5b7b185fb74cb450c8fde756235 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 27 May 2025 20:18:02 +0300 Subject: [PATCH 013/137] * Code related to Scope has been fixed. A main_scope was added, as it always needs to be explicitly known. Macros like ZEND_ASYNC_CURRENT_SCOPE were updated. A new macro ZEND_ASYNC_MAIN_SCOPE was added for the main Scope. --- Zend/zend_async_API.c | 1 + Zend/zend_async_API.h | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 7a4da9b58d0c6..59aa86313124f 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -117,6 +117,7 @@ static void ts_globals_ctor(zend_async_globals_t * globals) globals->active_coroutine_count = 0; globals->active_event_count = 0; globals->coroutine = NULL; + globals->main_scope = NULL; globals->scheduler = NULL; globals->exit_exception = NULL; } diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 5b9b276ebdc65..1524af9686817 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -777,8 +777,8 @@ typedef struct { unsigned int active_event_count; /* The current coroutine context. */ zend_coroutine_t *coroutine; - /* The current async scope. */ - zend_async_scope_t *scope; + /* The main async scope. */ + zend_async_scope_t *main_scope; /* Scheduler coroutine */ zend_coroutine_t *scheduler; /* Exit exception object */ @@ -813,7 +813,8 @@ END_EXTERN_C() #define ZEND_ASYNC_GRACEFUL_SHUTDOWN ZEND_ASYNC_G(graceful_shutdown) #define ZEND_ASYNC_EXIT_EXCEPTION ZEND_ASYNC_G(exit_exception) #define ZEND_ASYNC_CURRENT_COROUTINE ZEND_ASYNC_G(coroutine) -#define ZEND_ASYNC_CURRENT_SCOPE ZEND_ASYNC_G(coroutine)->scope +#define ZEND_ASYNC_CURRENT_SCOPE (ZEND_ASYNC_G(coroutine) ? ZEND_ASYNC_G(coroutine)->scope : NULL) +#define ZEND_ASYNC_MAIN_SCOPE ZEND_ASYNC_G(main_scope) #define ZEND_ASYNC_SCHEDULER ZEND_ASYNC_G(scheduler) #define ZEND_ASYNC_INCREASE_EVENT_COUNT if (ZEND_ASYNC_G(active_event_count) < UINT_MAX) { \ From 2896aecc116f9235b89a860ee5f407f7e493d6a0 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 28 May 2025 14:56:43 +0300 Subject: [PATCH 014/137] + enqueue_coroutine --- Zend/zend_async_API.c | 8 ++++++++ Zend/zend_async_API.h | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 59aa86313124f..6b50a24a87b88 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -34,6 +34,11 @@ static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_pr static void suspend(bool from_main) {} +static void enqueue_coroutine(zend_coroutine_t *coroutine) +{ + ASYNC_THROW_ERROR("Async API is not enabled"); +} + static zend_async_context_t * new_context(void) { ASYNC_THROW_ERROR("Context API is not enabled"); @@ -60,6 +65,7 @@ zend_async_spawn_t zend_async_spawn_fn = spawn; zend_async_new_coroutine_t zend_async_new_coroutine_fn = NULL; zend_async_new_scope_t zend_async_new_scope_fn = NULL; zend_async_suspend_t zend_async_suspend_fn = suspend; +zend_async_enqueue_coroutine_t zend_async_enqueue_coroutine_fn = enqueue_coroutine; zend_async_resume_t zend_async_resume_fn = NULL; zend_async_cancel_t zend_async_cancel_fn = NULL; zend_async_shutdown_t zend_async_shutdown_fn = NULL; @@ -175,6 +181,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_new_context_t new_context_fn, zend_async_spawn_t spawn_fn, zend_async_suspend_t suspend_fn, + zend_async_enqueue_coroutine_t enqueue_coroutine_fn, zend_async_resume_t resume_fn, zend_async_cancel_t cancel_fn, zend_async_shutdown_t shutdown_fn, @@ -203,6 +210,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_new_context_fn = new_context_fn; zend_async_spawn_fn = spawn_fn; zend_async_suspend_fn = suspend_fn; + zend_async_enqueue_coroutine_fn = enqueue_coroutine_fn; zend_async_resume_fn = resume_fn; zend_async_cancel_fn = cancel_fn; zend_async_shutdown_fn = shutdown_fn; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 1524af9686817..26cf95802380e 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -159,6 +159,7 @@ typedef zend_coroutine_t * (*zend_async_new_coroutine_t)(zend_async_scope_t *sco typedef zend_async_scope_t * (*zend_async_new_scope_t)(zend_async_scope_t * parent_scope); typedef zend_coroutine_t * (*zend_async_spawn_t)(zend_async_scope_t *scope, zend_object *scope_provider); typedef void (*zend_async_suspend_t)(bool from_main); +typedef void (*zend_async_enqueue_coroutine_t)(zend_coroutine_t *coroutine); typedef void (*zend_async_resume_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error); typedef void (*zend_async_cancel_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error, const bool is_safely); typedef void (*zend_async_shutdown_t)(void); @@ -866,6 +867,7 @@ ZEND_API extern zend_async_spawn_t zend_async_spawn_fn; ZEND_API extern zend_async_new_coroutine_t zend_async_new_coroutine_fn; ZEND_API extern zend_async_new_scope_t zend_async_new_scope_fn; ZEND_API extern zend_async_suspend_t zend_async_suspend_fn; +ZEND_API extern zend_async_enqueue_coroutine_t zend_async_enqueue_coroutine_fn; ZEND_API extern zend_async_resume_t zend_async_resume_fn; ZEND_API extern zend_async_cancel_t zend_async_cancel_fn; ZEND_API extern zend_async_shutdown_t zend_async_shutdown_fn; @@ -913,6 +915,7 @@ ZEND_API void zend_async_scheduler_register( zend_async_new_context_t new_context_fn, zend_async_spawn_t spawn_fn, zend_async_suspend_t suspend_fn, + zend_async_enqueue_coroutine_t enqueue_coroutine_fn, zend_async_resume_t resume_fn, zend_async_cancel_t cancel_fn, zend_async_shutdown_t shutdown_fn, @@ -986,6 +989,7 @@ END_EXTERN_C() #define ZEND_ASYNC_NEW_SCOPE(parent) zend_async_new_scope_fn(parent) #define ZEND_ASYNC_SUSPEND() zend_async_suspend_fn(false) #define ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN() zend_async_suspend_fn(true) +#define ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine) zend_async_enqueue_coroutine_fn(coroutine) #define ZEND_ASYNC_RESUME(coroutine) zend_async_resume_fn(coroutine, NULL, false) #define ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error) zend_async_resume_fn(coroutine, error, transfer_error) #define ZEND_ASYNC_CANCEL(coroutine, error, transfer_error) zend_async_cancel_fn(coroutine, error, transfer_error, false) From 29e0cd6c23666b0baf092764f5f65f6fad9594ff Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 28 May 2025 23:13:18 +0300 Subject: [PATCH 015/137] % Refactoring the coroutine context state for switching, so that the main coroutine can correctly return control. --- Zend/zend_async_API.c | 2 +- Zend/zend_async_API.h | 33 +++++++++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 6b50a24a87b88..928d24718219a 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -298,7 +298,7 @@ ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char zend_coroutine_name = ""; } - if (coroutine->waker != NULL) { + if (ZEND_COROUTINE_SUSPENDED(coroutine)) { return zend_strpprintf(0, "Coroutine spawned at %s:%d, suspended at %s:%d (%s)", coroutine->filename ? ZSTR_VAL(coroutine->filename) : "", diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 26cf95802380e..3548cc69dd4b5 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -81,11 +81,11 @@ typedef int zend_socket_t; * If type==4, output will be saved to a memory buffer (shell_exec) */ typedef enum { - ZEND_ASYNC_EXEC_MODE_EXEC = 0, - ZEND_ASYNC_EXEC_MODE_SYSTEM = 1, - ZEND_ASYNC_EXEC_MODE_EXEC_ARRAY = 2, - ZEND_ASYNC_EXEC_MODE_PASSTHRU = 3, - ZEND_ASYNC_EXEC_MODE_SHELL_EXEC = 4 + ZEND_ASYNC_EXEC_MODE_EXEC, + ZEND_ASYNC_EXEC_MODE_SYSTEM, + ZEND_ASYNC_EXEC_MODE_EXEC_ARRAY, + ZEND_ASYNC_EXEC_MODE_PASSTHRU, + ZEND_ASYNC_EXEC_MODE_SHELL_EXEC } zend_async_exec_mode; typedef enum @@ -624,10 +624,11 @@ zend_async_scope_free_children(zend_async_scope_t *parent_scope) typedef void (*zend_async_waker_dtor)(zend_coroutine_t *coroutine); typedef enum { - ZEND_ASYNC_WAKER_NO_STATUS = 0, - ZEND_ASYNC_WAKER_WAITING = 1, - ZEND_ASYNC_WAKER_QUEUED = 2, - ZEND_ASYNC_WAKER_IGNORED = 3 + ZEND_ASYNC_WAKER_NO_STATUS, + ZEND_ASYNC_WAKER_WAITING, + ZEND_ASYNC_WAKER_QUEUED, + ZEND_ASYNC_WAKER_IGNORED, + ZEND_ASYNC_WAKER_RESULT } ZEND_ASYNC_WAKER_STATUS; struct _zend_async_waker_s { @@ -649,6 +650,8 @@ struct _zend_async_waker_s { zend_async_waker_dtor dtor; }; +#define ZEND_ASYNC_WAKER_WAITING(waker) ((waker)->status < ZEND_ASYNC_WAKER_RESULT) + /** * Coroutine destructor. Called when the coroutine needs to clean up all its data. */ @@ -690,6 +693,12 @@ struct _zend_coroutine_s { zend_async_coroutine_dispose extended_dispose; }; +/** + * The macro evaluates to TRUE if the coroutine is in a waiting state — + * either waiting for events or waiting in the execution queue. + */ +#define ZEND_COROUTINE_SUSPENDED(coroutine) ((coroutine)->waker != NULL && ZEND_ASYNC_WAKER_WAITING((coroutine)->waker)) + /* Coroutine flags */ #define ZEND_COROUTINE_F_STARTED (1u << 10) /* coroutine is started */ #define ZEND_COROUTINE_F_CANCELLED (1u << 11) /* coroutine is cancelled */ @@ -757,11 +766,11 @@ struct _zend_async_context_s { */ typedef enum { // The module is inactive. - ZEND_ASYNC_OFF = 0, + ZEND_ASYNC_OFF, // The module is ready for use but has not been activated yet. - ZEND_ASYNC_READY = 1, + ZEND_ASYNC_READY, // The module is active and can be used. - ZEND_ASYNC_ACTIVE = 2 + ZEND_ASYNC_ACTIVE } zend_async_state_t; typedef struct { From 6fa485b13ce10d11721662230acc9145fd0a65d9 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 29 May 2025 15:35:53 +0300 Subject: [PATCH 016/137] * Unit test await\001-await_basic.phpt --- Zend/zend_async_API.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 928d24718219a..3462b94323368 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -407,7 +407,8 @@ ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine) } if (waker->triggered_events != NULL) { - zend_array_release(waker->triggered_events); + zend_hash_destroy(waker->triggered_events); + efree(waker->triggered_events); waker->triggered_events = NULL; } @@ -424,10 +425,14 @@ ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine) static void event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) { - if (callback->ref_count != 0) { + if (callback->ref_count > 1) { + // If the callback is still referenced, we cannot dispose it yet + callback->ref_count--; return; } + callback->ref_count = 0; + zend_async_waker_t * waker = ((zend_coroutine_event_callback_t *) callback)->coroutine->waker; if (event != NULL && waker != NULL) { @@ -449,7 +454,7 @@ ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, } if (coroutine->waker->triggered_events == NULL) { - coroutine->waker->triggered_events = (HashTable *) malloc(sizeof(HashTable)); + coroutine->waker->triggered_events = (HashTable *) emalloc(sizeof(HashTable)); zend_hash_init(coroutine->waker->triggered_events, 2, NULL, waker_triggered_events_dtor, 0); } @@ -529,7 +534,7 @@ ZEND_API void zend_async_waker_callback_resolve( if (exception == NULL && coroutine->waker != NULL) { if (coroutine->waker->triggered_events == NULL) { - coroutine->waker->triggered_events = (HashTable *) malloc(sizeof(HashTable)); + coroutine->waker->triggered_events = (HashTable *) emalloc(sizeof(HashTable)); zend_hash_init(coroutine->waker->triggered_events, 2, NULL, waker_triggered_events_dtor, 0); } @@ -538,7 +543,7 @@ ZEND_API void zend_async_waker_callback_resolve( } // Copy the result to the waker if it is not NULL - if (ZEND_ASYNC_EVENT_WILL_RESULT_USED(event) && result != NULL) { + if (ZEND_ASYNC_EVENT_WILL_ZVAL_RESULT(event) && result != NULL) { ZVAL_COPY(&coroutine->waker->result, result); } } From f4597e15400e4f511f1a38e3d62a7f130b4b3919 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 29 May 2025 17:04:50 +0300 Subject: [PATCH 017/137] * fix bug with waker dtor * add macro START_REACTOR_OR_RETURN for reactor autostart --- Zend/zend_async_API.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 3462b94323368..2e7448c92b968 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -369,8 +369,7 @@ ZEND_API zend_async_waker_t *zend_async_waker_new(zend_coroutine_t *coroutine) } if (UNEXPECTED(coroutine->waker != NULL)) { - coroutine->waker->dtor(coroutine); - coroutine->waker = NULL; + zend_async_waker_destroy(coroutine); } zend_async_waker_t *waker = pecalloc(1, sizeof(zend_async_waker_t), 0); From 4cff86f2c381a2ee8bf8006aca8f5f1eff1099a4 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 29 May 2025 22:48:25 +0300 Subject: [PATCH 018/137] % Refactoring the extension mechanism for Event-type objects. --- Zend/zend_async_API.h | 57 ++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 3548cc69dd4b5..577da84f4a159 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -173,20 +173,28 @@ typedef void (*zend_async_reactor_shutdown_t)(void); typedef bool (*zend_async_reactor_execute_t)(bool no_wait); typedef bool (*zend_async_reactor_loop_alive_t)(void); -typedef zend_async_poll_event_t* (*zend_async_new_socket_event_t)(zend_socket_t socket, async_poll_event events, size_t size); +typedef zend_async_poll_event_t* (*zend_async_new_socket_event_t)( + zend_socket_t socket, async_poll_event events, size_t extra_size +); typedef zend_async_poll_event_t* (*zend_async_new_poll_event_t)( - zend_file_descriptor_t fh, zend_socket_t socket, async_poll_event events, size_t size + zend_file_descriptor_t fh, zend_socket_t socket, async_poll_event events, size_t extra_size +); +typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)( + const zend_ulong timeout, const bool is_periodic, size_t extra_size +); +typedef zend_async_signal_event_t* (*zend_async_new_signal_event_t)(int signum, size_t extra_size); +typedef zend_async_process_event_t* (*zend_async_new_process_event_t)(zend_process_t process_handle, size_t extra_size); +typedef void (*zend_async_thread_entry_t)(void *arg, size_t extra_size); +typedef zend_async_thread_event_t* (*zend_async_new_thread_event_t)( + zend_async_thread_entry_t entry, void *arg, size_t extra_size ); -typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)(const zend_ulong timeout, const bool is_periodic, size_t size); -typedef zend_async_signal_event_t* (*zend_async_new_signal_event_t)(int signum, size_t size); -typedef zend_async_process_event_t* (*zend_async_new_process_event_t)(zend_process_t process_handle, size_t size); -typedef void (*zend_async_thread_entry_t)(void *arg, size_t size); -typedef zend_async_thread_event_t* (*zend_async_new_thread_event_t)(zend_async_thread_entry_t entry, void *arg, size_t size); -typedef zend_async_filesystem_event_t* (*zend_async_new_filesystem_event_t)(zend_string * path, const unsigned int flags, size_t size); - -typedef zend_async_dns_nameinfo_t* (*zend_async_getnameinfo_t)(const struct sockaddr* addr, int flags, size_t size); +typedef zend_async_filesystem_event_t* (*zend_async_new_filesystem_event_t)( + zend_string * path, const unsigned int flags, size_t extra_size +); + +typedef zend_async_dns_nameinfo_t* (*zend_async_getnameinfo_t)(const struct sockaddr* addr, int flags, size_t extra_size); typedef zend_async_dns_addrinfo_t* (*zend_async_getaddrinfo_t)( - const char *node, const char *service, const struct addrinfo *hints, size_t size + const char *node, const char *service, const struct addrinfo *hints, size_t extra_size ); typedef zend_async_exec_event_t* (*zend_async_new_exec_event_t) ( @@ -197,7 +205,7 @@ typedef zend_async_exec_event_t* (*zend_async_new_exec_event_t) ( zval *std_error, const char *cwd, const char *env, - size_t size + size_t extra_size ); typedef int (* zend_async_exec_t) ( @@ -267,6 +275,8 @@ typedef struct _zend_async_callbacks_vector_s { struct _zend_async_event_s { /* If event is closed, it cannot be started or stopped. */ uint32_t flags; + /* Offset to the beginning of additional data associated with the event (used for extensions) */ + uint32_t extra_offset; union { /* The refcount of the event. */ @@ -276,6 +286,7 @@ struct _zend_async_event_s { }; /* The Event loop reference count. */ uint32_t loop_ref_count; + /* Events callbacks */ zend_async_callbacks_vector_t callbacks; /* Methods */ zend_async_event_add_callback_t add_callback; @@ -1018,27 +1029,29 @@ END_EXTERN_C() #define ZEND_ASYNC_REACTOR_LOOP_ALIVE() zend_async_reactor_loop_alive_fn() #define ZEND_ASYNC_NEW_SOCKET_EVENT(socket, events) zend_async_new_socket_event_fn(socket, events, 0) -#define ZEND_ASYNC_NEW_SOCKET_EVENT_EX(socket, events, size) zend_async_new_socket_event_fn(socket, events, size) +#define ZEND_ASYNC_NEW_SOCKET_EVENT_EX(socket, events, extra_size) zend_async_new_socket_event_fn(socket, events, extra_size) #define ZEND_ASYNC_NEW_POLL_EVENT(fh, socket, events) zend_async_new_poll_event_fn(fh, socket, events, 0) -#define ZEND_ASYNC_NEW_POLL_EVENT_EX(fh, socket, events, size) zend_async_new_poll_event_fn(fh, socket, events, size) +#define ZEND_ASYNC_NEW_POLL_EVENT_EX(fh, socket, events, extra_size) zend_async_new_poll_event_fn(fh, socket, events, extra_size) #define ZEND_ASYNC_NEW_TIMER_EVENT(timeout, is_periodic) zend_async_new_timer_event_fn(timeout, is_periodic, 0) -#define ZEND_ASYNC_NEW_TIMER_EVENT_EX(timeout, is_periodic, size) zend_async_new_timer_event_fn(timeout, is_periodic, size) +#define ZEND_ASYNC_NEW_TIMER_EVENT_EX(timeout, is_periodic, extra_size) zend_async_new_timer_event_fn(timeout, is_periodic, extra_size) #define ZEND_ASYNC_NEW_SIGNAL_EVENT(signum) zend_async_new_signal_event_fn(signum, 0) -#define ZEND_ASYNC_NEW_SIGNAL_EVENT_EX(signum, size) zend_async_new_signal_event_fn(signum, size) -#define ZEND_ASYNC_NEW_PROCESS_EVENT() zend_async_new_process_event_fn() -#define ZEND_ASYNC_NEW_PROCESS_EVENT_EX() zend_async_new_process_event_fn() -#define ZEND_ASYNC_NEW_THREAD_EVENT() zend_async_new_thread_event_fn() +#define ZEND_ASYNC_NEW_SIGNAL_EVENT_EX(signum, extra_size) zend_async_new_signal_event_fn(signum, extra_size) +#define ZEND_ASYNC_NEW_PROCESS_EVENT(process_handle) zend_async_new_process_event_fn(process_handle, 0) +#define ZEND_ASYNC_NEW_PROCESS_EVENT_EX(process_handle, extra_size) zend_async_new_process_event_fn(process_handle, extra_size) +#define ZEND_ASYNC_NEW_THREAD_EVENT(entry, arg) zend_async_new_thread_event_fn(entry, arg, 0) #define ZEND_ASYNC_NEW_THREAD_EVENT_EX(entry, arg) zend_async_new_thread_event_fn(entry, arg, 0) #define ZEND_ASYNC_NEW_FILESYSTEM_EVENT(path, flags) zend_async_new_filesystem_event_fn(path, flags, 0) -#define ZEND_ASYNC_NEW_FILESYSTEM_EVENT_EX(path, flags, size) zend_async_new_filesystem_event_fn(path, flags, size) +#define ZEND_ASYNC_NEW_FILESYSTEM_EVENT_EX(path, flags, extra_size) zend_async_new_filesystem_event_fn(path, flags, extra_size) #define ZEND_ASYNC_GETNAMEINFO(addr, flags) zend_async_getnameinfo_fn(addr, flags, 0) -#define ZEND_ASYNC_GETNAMEINFO_EX(addr, flags, size) zend_async_getnameinfo_fn(addr, flags, size) +#define ZEND_ASYNC_GETNAMEINFO_EX(addr, flags, extra_size) zend_async_getnameinfo_fn(addr, flags, extra_size) #define ZEND_ASYNC_GETADDRINFO(node, service, hints, flags) zend_async_getaddrinfo_fn(node, service, hints, flags, 0) -#define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, flags, size) zend_async_getaddrinfo_fn(node, service, hints, flags, size) +#define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, flags, extra_size) zend_async_getaddrinfo_fn(node, service, hints, flags, extra_size) #define ZEND_ASYNC_NEW_EXEC_EVENT(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env) \ zend_async_new_exec_event_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, 0) +#define ZEND_ASYNC_NEW_EXEC_EVENT_EX(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, extra_size) \ + zend_async_new_exec_event_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, extra_size) #define ZEND_ASYNC_EXEC(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, timeout) \ zend_async_exec_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, timeout) From 9537c428e692da2ae7f2dd2bc0c93a5b63298630 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 30 May 2025 13:09:50 +0300 Subject: [PATCH 019/137] =?UTF-8?q?%=20Added=20code=20to=20catch=20excepti?= =?UTF-8?q?ons=20in=20the=20main=20coroutine.=20The=20exception=20is=20now?= =?UTF-8?q?=20passed=20to=20the=20main=20coroutine=E2=80=99s=20finalize=20?= =?UTF-8?q?method=20instead=20of=20being=20displayed=20on=20screen.=20*=20?= =?UTF-8?q?Fixed=20an=20issue=20with=20correctly=20passing=20the=20excepti?= =?UTF-8?q?on=20into=20the=20coroutine.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Zend/zend.c | 12 ++++++++++++ Zend/zend_async_API.c | 13 +++---------- Zend/zend_async_API.h | 11 +++++++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index 2d8a0f455f8b4..580a765ae5ba0 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -41,6 +41,9 @@ #include "Optimizer/zend_optimizer.h" #include "php.h" #include "php_globals.h" +#ifdef PHP_ASYNC_API +#include "zend_async_API.h" +#endif // FIXME: Breaks the declaration of the function below #undef zenderror @@ -1946,9 +1949,18 @@ ZEND_API zend_result zend_execute_script(int type, zval *retval, zend_file_handl if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) { zend_user_exception_handler(); } +#ifdef PHP_ASYNC_API + // If we are inside a coroutine, + // we do not call the final error handler, + // as the exception will be handled higher up in the method ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN + if (false == ZEND_ASYNC_CURRENT_COROUTINE && EG(exception)) { + ret = zend_exception_error(EG(exception), E_ERROR); + } +#else if (EG(exception)) { ret = zend_exception_error(EG(exception), E_ERROR); } +#endif } zend_destroy_static_vars(op_array); destroy_op_array(op_array); diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 2e7448c92b968..e46059f9684f8 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -559,16 +559,9 @@ ZEND_API void zend_async_waker_callback_cancel( if (UNEXPECTED(exception != NULL)) { ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, false); } else { - - if (result == NULL) { - exception = zend_async_new_exception( - ZEND_ASYNC_EXCEPTION_CANCELLATION, "Operation has been cancelled" - ); - } else { - exception = result; - } - - ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, false); + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, zend_async_new_exception( + ZEND_ASYNC_EXCEPTION_CANCELLATION, "Operation has been cancelled" + ), true); } } diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 577da84f4a159..6f7be69bea42a 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -135,6 +135,7 @@ typedef void (*zend_async_event_callback_fn) typedef void (*zend_async_event_callback_dispose_fn)(zend_async_event_callback_t *callback, zend_async_event_t * event); typedef void (*zend_async_event_add_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); typedef void (*zend_async_event_del_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); +typedef bool (*zend_async_event_callbacks_notify_t)(zend_async_event_t *event, void *result, zend_object *exception); typedef void (*zend_async_event_start_t) (zend_async_event_t *event); typedef void (*zend_async_event_stop_t) (zend_async_event_t *event); typedef void (*zend_async_event_dispose_t) (zend_async_event_t *event); @@ -291,6 +292,11 @@ struct _zend_async_event_s { /* Methods */ zend_async_event_add_callback_t add_callback; zend_async_event_del_callback_t del_callback; + /* + * Handler that is invoked before all event listeners are notified. + * May be NULL. + */ + zend_async_event_callbacks_notify_t before_notify; zend_async_event_start_t start; zend_async_event_stop_t stop; zend_async_event_dispose_t dispose; @@ -404,6 +410,11 @@ zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_ca static zend_always_inline void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception) { + // If pre-notify returns false, we stop notifying callbacks + if (event->before_notify != NULL && false == event->before_notify(event, result, exception)) { + return; + } + if (event->callbacks.data == NULL) { return; } From 152c8773b17d97536d14272383e77ac6e82d417e Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 30 May 2025 16:44:14 +0300 Subject: [PATCH 020/137] * Fixed bugs in the Timeout class. --- Zend/zend_async_API.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 6f7be69bea42a..7b66c88f6f3b3 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -135,7 +135,7 @@ typedef void (*zend_async_event_callback_fn) typedef void (*zend_async_event_callback_dispose_fn)(zend_async_event_callback_t *callback, zend_async_event_t * event); typedef void (*zend_async_event_add_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); typedef void (*zend_async_event_del_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); -typedef bool (*zend_async_event_callbacks_notify_t)(zend_async_event_t *event, void *result, zend_object *exception); +typedef bool (*zend_async_event_callbacks_notify_t)(zend_async_event_t *event, void **result, zend_object **exception); typedef void (*zend_async_event_start_t) (zend_async_event_t *event); typedef void (*zend_async_event_stop_t) (zend_async_event_t *event); typedef void (*zend_async_event_dispose_t) (zend_async_event_t *event); @@ -411,8 +411,10 @@ static zend_always_inline void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception) { // If pre-notify returns false, we stop notifying callbacks - if (event->before_notify != NULL && false == event->before_notify(event, result, exception)) { - return; + if (event->before_notify != NULL) { + if (false == event->before_notify(event, &result, &exception)) { + return; + } } if (event->callbacks.data == NULL) { From 95b12f77a9eb9bba5998480c2801cdae689844f5 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 30 May 2025 17:11:30 +0300 Subject: [PATCH 021/137] * fix zend_async_waker_callback_resolve. The method now automatically captures the exception, marking it as handled in another coroutine. --- Zend/zend_async_API.c | 8 ++++++++ Zend/zend_async_API.h | 14 +++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index e46059f9684f8..cd8df59a47969 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -547,6 +547,14 @@ ZEND_API void zend_async_waker_callback_resolve( } } + if (exception != NULL) { + // + // This handler always captures the exception as handled because it passes it to another coroutine. + // As a result, the exception will not propagate further. + // + ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(event); + } + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, false); } diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 7b66c88f6f3b3..f1935b51496a7 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -311,6 +311,7 @@ struct _zend_async_event_s { #define ZEND_ASYNC_EVENT_F_ZVAL_RESULT (1u << 3) #define ZEND_ASYNC_EVENT_F_ZEND_OBJ (1u << 4) /* event is a zend object */ #define ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY (1u << 5) /* event will not free memory in dispose handler */ +#define ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED (1u << 6) /* exception has been caught and processed */ #define ZEND_ASYNC_EVENT_IS_CLOSED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) #define ZEND_ASYNC_EVENT_WILL_RESULT_USED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_RESULT_USED) != 0) @@ -318,6 +319,7 @@ struct _zend_async_event_s { #define ZEND_ASYNC_EVENT_WILL_ZVAL_RESULT(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_ZVAL_RESULT) != 0) #define ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_ZEND_OBJ) != 0) #define ZEND_ASYNC_EVENT_IS_NO_FREE_MEMORY(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY) != 0) +#define ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) != 0) #define ZEND_ASYNC_EVENT_SET_CLOSED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_CLOSED) #define ZEND_ASYNC_EVENT_CLR_CLOSED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_CLOSED) @@ -336,6 +338,9 @@ struct _zend_async_event_s { #define ZEND_ASYNC_EVENT_SET_NO_FREE_MEMORY(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY) +#define ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) +#define ZEND_ASYNC_EVENT_CLR_EXCEPTION_HANDLED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) + // Convert awaitable Zend object to zend_async_event_t pointer #define ZEND_ASYNC_OBJECT_TO_EVENT(obj) ((zend_async_event_t *)((char *)(obj) - (obj)->handlers->offset)) #define ZEND_ASYNC_EVENT_TO_OBJECT(event) ((zend_object *)((char *)(event) + (event)->zend_object_offset)) @@ -728,8 +733,7 @@ struct _zend_coroutine_s { #define ZEND_COROUTINE_F_CANCELLED (1u << 11) /* coroutine is cancelled */ #define ZEND_COROUTINE_F_ZOMBIE (1u << 12) /* coroutine is a zombie */ #define ZEND_COROUTINE_F_PROTECTED (1u << 13) /* coroutine is protected */ -#define ZEND_COROUTINE_F_EXCEPTION_HANDLED (1u << 14) /* exception has been caught and processed */ -#define ZEND_COROUTINE_F_MAIN (1u << 15) /* coroutine is a main coroutine */ +#define ZEND_COROUTINE_F_MAIN (1u << 14) /* coroutine is a main coroutine */ #define ZEND_COROUTINE_IS_ZOMBIE(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_ZOMBIE) != 0) #define ZEND_COROUTINE_SET_ZOMBIE(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_ZOMBIE) @@ -737,7 +741,7 @@ struct _zend_coroutine_s { #define ZEND_COROUTINE_IS_CANCELLED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_CANCELLED) != 0) #define ZEND_COROUTINE_IS_FINISHED(coroutine) (((coroutine)->event.flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) #define ZEND_COROUTINE_IS_PROTECTED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_PROTECTED) != 0) -#define ZEND_COROUTINE_IS_EXCEPTION_HANDLED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_EXCEPTION_HANDLED) != 0) +#define ZEND_COROUTINE_IS_EXCEPTION_HANDLED(coroutine) ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED(&(coroutine)->event) #define ZEND_COROUTINE_IS_MAIN(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_MAIN) != 0) #define ZEND_COROUTINE_SET_STARTED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_STARTED) #define ZEND_COROUTINE_SET_CANCELLED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_CANCELLED) @@ -745,8 +749,8 @@ struct _zend_coroutine_s { #define ZEND_COROUTINE_SET_PROTECTED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_PROTECTED) #define ZEND_COROUTINE_SET_MAIN(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_MAIN) #define ZEND_COROUTINE_CLR_PROTECTED(coroutine) ((coroutine)->event.flags &= ~ZEND_COROUTINE_F_PROTECTED) -#define ZEND_COROUTINE_SET_EXCEPTION_HANDLED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_EXCEPTION_HANDLED) -#define ZEND_COROUTINE_CLR_EXCEPTION_HANDLED(coroutine) ((coroutine)->event.flags &= ~ZEND_COROUTINE_F_EXCEPTION_HANDLED) +#define ZEND_COROUTINE_SET_EXCEPTION_HANDLED(coroutine) ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(&(coroutine)->event) +#define ZEND_COROUTINE_CLR_EXCEPTION_HANDLED(coroutine) ZEND_ASYNC_EVENT_CLR_EXCEPTION_HANDLED(&(coroutine)->event) static zend_always_inline zend_string *zend_coroutine_callable_name(const zend_coroutine_t *coroutine) { From 3fce53ceb8abe43ec92f6d7876783077a2e9db34 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 31 May 2025 21:15:33 +0300 Subject: [PATCH 022/137] * fix Unix build process --- Zend/zend_async_API.c | 6 +++--- Zend/zend_async_API.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index cd8df59a47969..560a5e0004d7d 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -21,7 +21,7 @@ int zend_async_globals_id = 0; size_t zend_async_globals_offset = 0; #else // The default data structure that is used when no other extensions are present. -zend_async_globals_t zend_async_globals = {0}; +zend_async_globals_t zend_async_globals_api = {0}; #endif #define ASYNC_THROW_ERROR(error) zend_throw_error(NULL, error); @@ -147,7 +147,7 @@ void zend_async_globals_ctor(void) ZEND_ASSERT(zend_async_globals_id != 0 && "zend_async_globals allocation failed"); #else - ts_globals_ctor(zend_async_globals); + ts_globals_ctor(&zend_async_globals_api); #endif } @@ -159,7 +159,7 @@ void zend_async_globals_dtor(void) { #ifdef ZTS #else - ts_globals_dtor(zend_async_globals); + ts_globals_dtor(&zend_async_globals_api); #endif } diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index f1935b51496a7..5009af8dbe7a7 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -829,8 +829,8 @@ ZEND_API extern int zend_async_globals_id; ZEND_API extern size_t zend_async_globals_offset; #define ZEND_ASYNC_G(v) ZEND_TSRMG_FAST(zend_async_globals_offset, zend_async_globals_t *, v) #else -#define ZEND_ASYNC_G(v) (zend_async_globals.v) -ZEND_API extern zend_async_globals_t zend_async_globals; +#define ZEND_ASYNC_G(v) (zend_async_globals_api.v) +ZEND_API extern zend_async_globals_t zend_async_globals_api; #endif END_EXTERN_C() From f471995a427d45fdc19919e53ab698533cc95f57 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 31 May 2025 22:00:01 +0300 Subject: [PATCH 023/137] fix: ensure extended_data is set only in coroutine context --- Zend/zend_gc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index cbcb38c8178bb..0e24fd64d711a 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -1856,11 +1856,15 @@ static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); GC_ADDREF(obj); #ifdef PHP_ASYNC_API - ZEND_ASYNC_CURRENT_COROUTINE->extended_data = obj; + if (in_coroutine) { + ZEND_ASYNC_CURRENT_COROUTINE->extended_data = obj; + } #endif obj->handlers->dtor_obj(obj); #ifdef PHP_ASYNC_API - ZEND_ASYNC_CURRENT_COROUTINE->extended_data = NULL; + if (in_coroutine) { + ZEND_ASYNC_CURRENT_COROUTINE->extended_data = NULL; + } #endif GC_TRACE_REF(obj, "returned from destructor"); GC_DELREF(obj); From daac3068130e1e52d1db129cc6f1c6fce96d8938 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 1 Jun 2025 09:40:36 +0300 Subject: [PATCH 024/137] * fix GC error fornon TLS --- Zend/zend_gc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 0e24fd64d711a..11621782e32d7 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -1833,6 +1833,7 @@ static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t #define RESUMED_AFTER_SUSPENSION (fiber != NULL && GC_G(dtor_fiber) != fiber) \ || (in_coroutine && GC_G(dtor_coroutine) != ZEND_ASYNC_CURRENT_COROUTINE) #else + const bool in_coroutine = false; #define RESUMED_AFTER_SUSPENSION fiber != NULL && GC_G(dtor_fiber) != fiber #endif From 7e9e31a55070ff727f84d8cd506f12e01e777d31 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 1 Jun 2025 11:17:05 +0300 Subject: [PATCH 025/137] * Fixed correct handling of multiple SAPI initializations for PHPDBG. --- Zend/zend_async_API.c | 59 ++++++++++++++++++++++++++++--------------- Zend/zend_async_API.h | 8 +++--- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 560a5e0004d7d..e25f1f76ade3f 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -60,7 +60,8 @@ static zend_class_entry * get_class_ce(zend_async_class type) return NULL; } -static zend_string * scheduler_module_name = NULL; +static zend_atomic_bool scheduler_lock = {0}; +static char * scheduler_module_name = NULL; zend_async_spawn_t zend_async_spawn_fn = spawn; zend_async_new_coroutine_t zend_async_new_coroutine_fn = NULL; zend_async_new_scope_t zend_async_new_scope_fn = NULL; @@ -74,7 +75,8 @@ zend_async_add_microtask_t zend_async_add_microtask_fn = NULL; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = NULL; zend_async_get_class_ce_t zend_async_get_class_ce_fn = get_class_ce; -static zend_string * reactor_module_name = NULL; +static zend_atomic_bool reactor_lock = {0}; +static char * reactor_module_name = NULL; zend_async_reactor_startup_t zend_async_reactor_startup_fn = NULL; zend_async_reactor_shutdown_t zend_async_reactor_shutdown_fn = NULL; zend_async_reactor_execute_t zend_async_reactor_execute_fn = NULL; @@ -173,8 +175,8 @@ ZEND_API int zend_async_get_api_version_number(void) return ZEND_ASYNC_API_VERSION_NUMBER; } -ZEND_API void zend_async_scheduler_register( - zend_string *module, +ZEND_API bool zend_async_scheduler_register( + char *module, bool allow_override, zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, @@ -191,19 +193,24 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_class_ce_t get_class_ce_fn ) { + if (zend_atomic_bool_exchange(&scheduler_lock, 1)) { + return false; + } + + if (scheduler_module_name == module) { + return true; + } + if (scheduler_module_name != NULL && false == allow_override) { + zend_atomic_bool_store(&scheduler_lock, 0); zend_error( E_CORE_ERROR, "The module %s is trying to override Scheduler, which was registered by the module %s.", - ZSTR_VAL(module), ZSTR_VAL(scheduler_module_name) + module, scheduler_module_name ); - return; + return false; } - if (scheduler_module_name != NULL) { - zend_string_release(scheduler_module_name); - } - - scheduler_module_name = zend_string_copy(module); + scheduler_module_name = module; zend_async_new_coroutine_fn = new_coroutine_fn; zend_async_new_scope_fn = new_scope_fn; @@ -218,10 +225,14 @@ ZEND_API void zend_async_scheduler_register( zend_async_add_microtask_fn = add_microtask_fn; zend_async_get_awaiting_info_fn = get_awaiting_info_fn; zend_async_get_class_ce_fn = get_class_ce_fn; + + zend_atomic_bool_store(&scheduler_lock, 0); + + return true; } -ZEND_API void zend_async_reactor_register( - zend_string *module, +ZEND_API bool zend_async_reactor_register( + char *module, bool allow_override, zend_async_reactor_startup_t reactor_startup_fn, zend_async_reactor_shutdown_t reactor_shutdown_fn, @@ -240,19 +251,23 @@ ZEND_API void zend_async_reactor_register( zend_async_exec_t exec_fn ) { + if (zend_atomic_bool_exchange(&reactor_lock, 1)) { + return false; + } + + if (reactor_module_name == module) { + return true; + } + if (reactor_module_name != NULL && false == allow_override) { zend_error( E_CORE_ERROR, "The module %s is trying to override Reactor, which was registered by the module %s.", - ZSTR_VAL(module), ZSTR_VAL(reactor_module_name) + module, reactor_module_name ); - return; + return false; } - if (reactor_module_name != NULL) { - zend_string_release(reactor_module_name); - } - - reactor_module_name = zend_string_copy(module); + reactor_module_name = module; zend_async_reactor_startup_fn = reactor_startup_fn; zend_async_reactor_shutdown_fn = reactor_shutdown_fn; @@ -273,6 +288,10 @@ ZEND_API void zend_async_reactor_register( zend_async_new_exec_event_fn = new_exec_event_fn; zend_async_exec_fn = exec_fn; + + zend_atomic_bool_store(&reactor_lock, 0); + + return true; } ZEND_API void zend_async_thread_pool_register(zend_string *module, bool allow_override, zend_async_queue_task_t queue_task_fn) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 5009af8dbe7a7..764527dad9126 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -944,8 +944,8 @@ ZEND_API extern zend_async_exec_t zend_async_exec_fn; ZEND_API bool zend_async_thread_pool_is_enabled(void); ZEND_API extern zend_async_queue_task_t zend_async_queue_task_fn; -ZEND_API void zend_async_scheduler_register( - zend_string *module, +ZEND_API bool zend_async_scheduler_register( + char *module, bool allow_override, zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, @@ -962,8 +962,8 @@ ZEND_API void zend_async_scheduler_register( zend_async_get_class_ce_t get_class_ce_fn ); -ZEND_API void zend_async_reactor_register( - zend_string *module, +ZEND_API bool zend_async_reactor_register( + char *module, bool allow_override, zend_async_reactor_startup_t reactor_startup_fn, zend_async_reactor_shutdown_t reactor_shutdown_fn, From 0a92de93d68b6f9c5c6a8ff5ae0af54c81948ea3 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 1 Jun 2025 13:54:40 +0300 Subject: [PATCH 026/137] + Add 'True Async API' out for configure Win32 --- win32/build/confutils.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/win32/build/confutils.js b/win32/build/confutils.js index 0f97a1a2d29c6..9e1ca75ccdc0c 100644 --- a/win32/build/confutils.js +++ b/win32/build/confutils.js @@ -1997,6 +1997,11 @@ function write_summary() } else { ar[k++] = ['Static analyzer', 'disabled']; } + if (typeof PHP_ASYNC_API !== "undefined" && PHP_ASYNC_API == "yes") { + ar[k++] = ['True Async API', 'Yes']; + } else { + ar[k++] = ['True Async API', 'No']; + } output_as_table(["",""], ar); STDOUT.WriteBlankLines(2); From a7281f9f72ac4cc6ec757c3ce78a385909f3d445 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:07:36 +0300 Subject: [PATCH 027/137] * fix for #define ZEND_ASYNC_GETADDRINFO(node, service, hints) zend_async_getaddrinfo_fn(node, service, hints, 0) --- Zend/zend_async_API.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 764527dad9126..324cda0a5a9d4 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1062,7 +1062,7 @@ END_EXTERN_C() #define ZEND_ASYNC_GETNAMEINFO(addr, flags) zend_async_getnameinfo_fn(addr, flags, 0) #define ZEND_ASYNC_GETNAMEINFO_EX(addr, flags, extra_size) zend_async_getnameinfo_fn(addr, flags, extra_size) -#define ZEND_ASYNC_GETADDRINFO(node, service, hints, flags) zend_async_getaddrinfo_fn(node, service, hints, flags, 0) +#define ZEND_ASYNC_GETADDRINFO(node, service, hints) zend_async_getaddrinfo_fn(node, service, hints, 0) #define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, flags, extra_size) zend_async_getaddrinfo_fn(node, service, hints, flags, extra_size) #define ZEND_ASYNC_NEW_EXEC_EVENT(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env) \ From 0d981ee91983f671166555770afe775abe0b148a Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:09:10 +0300 Subject: [PATCH 028/137] * fix for #define ZEND_ASYNC_GETADDRINFO(node, service, hints) zend_async_getaddrinfo_fn(node, service, hints, 0) --- Zend/zend_async_API.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 324cda0a5a9d4..8657b1d32d8f3 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1063,7 +1063,7 @@ END_EXTERN_C() #define ZEND_ASYNC_GETNAMEINFO(addr, flags) zend_async_getnameinfo_fn(addr, flags, 0) #define ZEND_ASYNC_GETNAMEINFO_EX(addr, flags, extra_size) zend_async_getnameinfo_fn(addr, flags, extra_size) #define ZEND_ASYNC_GETADDRINFO(node, service, hints) zend_async_getaddrinfo_fn(node, service, hints, 0) -#define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, flags, extra_size) zend_async_getaddrinfo_fn(node, service, hints, flags, extra_size) +#define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, extra_size) zend_async_getaddrinfo_fn(node, service, hints, extra_size) #define ZEND_ASYNC_NEW_EXEC_EVENT(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env) \ zend_async_new_exec_event_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, 0) From 9011197fc30efeab58140545bc0f3ecfc10044f1 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:21:49 +0300 Subject: [PATCH 029/137] Add nanosecond support to async timer events with backward compatibility Enhance zend_async_new_timer_event_fn to support nanosecond precision timers while maintaining full backward compatibility with existing millisecond-based API. Changes: - Added nanoseconds parameter to zend_async_new_timer_event_t function signature - Updated existing macros to pass 0 for nanoseconds (preserving compatibility) - Added new nanosecond-aware macros: * ZEND_ASYNC_NEW_TIMER_EVENT_NS(timeout, nanoseconds, is_periodic) * ZEND_ASYNC_NEW_TIMER_EVENT_NS_EX(timeout, nanoseconds, is_periodic, extra_size) - Implemented ceiling rounding for nanoseconds to milliseconds in libUV backend (nanoseconds + 999999) / 1000000 to ensure minimum 1ms when nanoseconds > 0 This enhancement provides modern nanosecond timer precision while ensuring all existing code continues to work without modification. Files modified: - Zend/zend_async_API.h: Function signature and macro definitions - ext/async/libuv_reactor.c: Implementation with nanosecond handling --- Zend/zend_async_API.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 8657b1d32d8f3..a5a1485098047 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -181,7 +181,7 @@ typedef zend_async_poll_event_t* (*zend_async_new_poll_event_t)( zend_file_descriptor_t fh, zend_socket_t socket, async_poll_event events, size_t extra_size ); typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)( - const zend_ulong timeout, const bool is_periodic, size_t extra_size + const zend_ulong timeout, const zend_ulong nanoseconds, const bool is_periodic, size_t extra_size ); typedef zend_async_signal_event_t* (*zend_async_new_signal_event_t)(int signum, size_t extra_size); typedef zend_async_process_event_t* (*zend_async_new_process_event_t)(zend_process_t process_handle, size_t extra_size); @@ -1049,8 +1049,10 @@ END_EXTERN_C() #define ZEND_ASYNC_NEW_SOCKET_EVENT_EX(socket, events, extra_size) zend_async_new_socket_event_fn(socket, events, extra_size) #define ZEND_ASYNC_NEW_POLL_EVENT(fh, socket, events) zend_async_new_poll_event_fn(fh, socket, events, 0) #define ZEND_ASYNC_NEW_POLL_EVENT_EX(fh, socket, events, extra_size) zend_async_new_poll_event_fn(fh, socket, events, extra_size) -#define ZEND_ASYNC_NEW_TIMER_EVENT(timeout, is_periodic) zend_async_new_timer_event_fn(timeout, is_periodic, 0) -#define ZEND_ASYNC_NEW_TIMER_EVENT_EX(timeout, is_periodic, extra_size) zend_async_new_timer_event_fn(timeout, is_periodic, extra_size) +#define ZEND_ASYNC_NEW_TIMER_EVENT(timeout, is_periodic) zend_async_new_timer_event_fn(timeout, 0, is_periodic, 0) +#define ZEND_ASYNC_NEW_TIMER_EVENT_EX(timeout, is_periodic, extra_size) zend_async_new_timer_event_fn(timeout, 0, is_periodic, extra_size) +#define ZEND_ASYNC_NEW_TIMER_EVENT_NS(timeout, nanoseconds, is_periodic) zend_async_new_timer_event_fn(timeout, nanoseconds, is_periodic, 0) +#define ZEND_ASYNC_NEW_TIMER_EVENT_NS_EX(timeout, nanoseconds, is_periodic, extra_size) zend_async_new_timer_event_fn(timeout, nanoseconds, is_periodic, extra_size) #define ZEND_ASYNC_NEW_SIGNAL_EVENT(signum) zend_async_new_signal_event_fn(signum, 0) #define ZEND_ASYNC_NEW_SIGNAL_EVENT_EX(signum, extra_size) zend_async_new_signal_event_fn(signum, extra_size) #define ZEND_ASYNC_NEW_PROCESS_EVENT(process_handle) zend_async_new_process_event_fn(process_handle, 0) From cd3ac1b3827140c4c0346c8a0636e0a18879334e Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:32:54 +0300 Subject: [PATCH 030/137] + add ZEND_ASYNC_GETADDRINFO_EX --- Zend/zend_async_API.c | 3 +++ Zend/zend_async_API.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index e25f1f76ade3f..e59ceb377f7e6 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -91,6 +91,7 @@ zend_async_new_filesystem_event_t zend_async_new_filesystem_event_fn = NULL; zend_async_getnameinfo_t zend_async_getnameinfo_fn = NULL; zend_async_getaddrinfo_t zend_async_getaddrinfo_fn = NULL; +zend_async_freeaddrinfo_t zend_async_freeaddrinfo_fn = NULL; zend_async_new_exec_event_t zend_async_new_exec_event_fn = NULL; zend_async_exec_t zend_async_exec_fn = NULL; @@ -247,6 +248,7 @@ ZEND_API bool zend_async_reactor_register( zend_async_new_filesystem_event_t new_filesystem_event_fn, zend_async_getnameinfo_t getnameinfo_fn, zend_async_getaddrinfo_t getaddrinfo_fn, + zend_async_freeaddrinfo_t freeaddrinfo_fn, zend_async_new_exec_event_t new_exec_event_fn, zend_async_exec_t exec_fn ) @@ -285,6 +287,7 @@ ZEND_API bool zend_async_reactor_register( zend_async_getnameinfo_fn = getnameinfo_fn; zend_async_getaddrinfo_fn = getaddrinfo_fn; + zend_async_freeaddrinfo_fn = freeaddrinfo_fn; zend_async_new_exec_event_fn = new_exec_event_fn; zend_async_exec_fn = exec_fn; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index a5a1485098047..ca7ae2015e729 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -197,6 +197,7 @@ typedef zend_async_dns_nameinfo_t* (*zend_async_getnameinfo_t)(const struct sock typedef zend_async_dns_addrinfo_t* (*zend_async_getaddrinfo_t)( const char *node, const char *service, const struct addrinfo *hints, size_t extra_size ); +typedef void (*zend_async_freeaddrinfo_t)(struct addrinfo *ai); typedef zend_async_exec_event_t* (*zend_async_new_exec_event_t) ( zend_async_exec_mode exec_mode, @@ -935,6 +936,7 @@ ZEND_API extern zend_async_new_filesystem_event_t zend_async_new_filesystem_even ZEND_API extern zend_async_getnameinfo_t zend_async_getnameinfo_fn; ZEND_API extern zend_async_getaddrinfo_t zend_async_getaddrinfo_fn; +ZEND_API extern zend_async_freeaddrinfo_t zend_async_freeaddrinfo_fn; /* Exec API */ ZEND_API extern zend_async_new_exec_event_t zend_async_new_exec_event_fn; @@ -978,6 +980,7 @@ ZEND_API bool zend_async_reactor_register( zend_async_new_filesystem_event_t new_filesystem_event_fn, zend_async_getnameinfo_t getnameinfo_fn, zend_async_getaddrinfo_t getaddrinfo_fn, + zend_async_freeaddrinfo_t freeaddrinfo_fn, zend_async_new_exec_event_t new_exec_event_fn, zend_async_exec_t exec_fn ); @@ -1066,6 +1069,7 @@ END_EXTERN_C() #define ZEND_ASYNC_GETNAMEINFO_EX(addr, flags, extra_size) zend_async_getnameinfo_fn(addr, flags, extra_size) #define ZEND_ASYNC_GETADDRINFO(node, service, hints) zend_async_getaddrinfo_fn(node, service, hints, 0) #define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, extra_size) zend_async_getaddrinfo_fn(node, service, hints, extra_size) +#define ZEND_ASYNC_FREEADDRINFO(ai) zend_async_freeaddrinfo_fn(ai) #define ZEND_ASYNC_NEW_EXEC_EVENT(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env) \ zend_async_new_exec_event_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, 0) From aac97d784abdcd2282577ad88ce482c58875927c Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:33:24 +0300 Subject: [PATCH 031/137] + TrueAsync API docs --- docs/source/true_async_api/architecture.rst | 30 ++ .../true_async_api/implementation-guide.rst | 396 ++++++++++++++++++ docs/source/true_async_api/index.rst | 103 +++++ 3 files changed, 529 insertions(+) create mode 100644 docs/source/true_async_api/architecture.rst create mode 100644 docs/source/true_async_api/implementation-guide.rst create mode 100644 docs/source/true_async_api/index.rst diff --git a/docs/source/true_async_api/architecture.rst b/docs/source/true_async_api/architecture.rst new file mode 100644 index 0000000000000..9077f5a641bda --- /dev/null +++ b/docs/source/true_async_api/architecture.rst @@ -0,0 +1,30 @@ +############## + Architecture +############## + +The TrueAsync API follows a clean separation-of-concerns architecture that splits API definition +from implementation. This design enables modularity, testability, and support for multiple +backend implementations. + +The **True Async API** is divided into three main functional blocks: + +- **Scheduler** + Manages coroutines and the main event loop. + +- **Reactor** + Handles everything related to input/output (I/O), including sockets, timers, and file descriptors. + +- **Thread Pool** + Allows execution of blocking or heavy tasks in a thread pool. + +--- + +## Implementation Flexibility + +The True Async API provides clearly separated interfaces for each block, allowing them to be implemented independently—even across different PHP extensions. For example: + +- The Scheduler can be implemented as part of the PHP core. +- The Reactor can be implemented in a separate extension using `libuv`. +- The Thread Pool can be based on native threads or a library like `pthreads`. + +This approach gives developers maximum flexibility and control over asynchronous execution. \ No newline at end of file diff --git a/docs/source/true_async_api/implementation-guide.rst b/docs/source/true_async_api/implementation-guide.rst new file mode 100644 index 0000000000000..de00e78edda29 --- /dev/null +++ b/docs/source/true_async_api/implementation-guide.rst @@ -0,0 +1,396 @@ +###################### + Implementation Guide +###################### + +This guide explains how to implement TrueAsync API backends and integrate them with +the PHP core. It covers the registration process, implementation patterns, and best practices. + +********************** + Overview +********************** + +Implementing a TrueAsync API backend involves: + +1. **Creating function implementations** that match the API signatures +2. **Registering implementations** with the core during module initialization +3. **Following memory management** and error handling conventions +4. **Implementing event lifecycle** methods (start, stop, dispose) +5. **Handling callbacks** and result delivery + +*********************** + Registration Process +*********************** + +The registration process connects your implementations to the core API. + +Module Initialization +===================== + +Register your implementations during ``PHP_MINIT_FUNCTION``: + +.. code:: c + + PHP_MINIT_FUNCTION(my_async_backend) { + // Register reactor implementations + bool success = zend_async_reactor_register( + "MyAsyncBackend", // Module name + false, // Allow override + my_reactor_startup, // Startup function + my_reactor_shutdown, // Shutdown function + my_reactor_execute, // Execute function + my_reactor_loop_alive, // Loop alive check + my_new_socket_event, // Socket event factory + my_new_poll_event, // Poll event factory + my_new_timer_event, // Timer event factory + my_new_signal_event, // Signal event factory + my_new_process_event, // Process event factory + my_new_thread_event, // Thread event factory + my_new_filesystem_event, // Filesystem event factory + my_getnameinfo, // DNS nameinfo + my_getaddrinfo, // DNS addrinfo + my_freeaddrinfo, // DNS cleanup + my_new_exec_event, // Exec event factory + my_exec // Exec function + ); + + if (!success) { + php_error_docref(NULL, E_CORE_ERROR, "Failed to register async backend"); + return FAILURE; + } + + return SUCCESS; + } + +Function Pointer Types +====================== + +All implementations must match the exact function pointer signatures defined in +``zend_async_API.h``: + +.. code:: c + + // DNS function signatures + typedef zend_async_dns_addrinfo_t* (*zend_async_getaddrinfo_t)( + const char *node, const char *service, + const struct addrinfo *hints, size_t extra_size + ); + + typedef void (*zend_async_freeaddrinfo_t)(struct addrinfo *ai); + + // Timer function signature + typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)( + const zend_ulong timeout, const zend_ulong nanoseconds, + const bool is_periodic, size_t extra_size + ); + +*********************** + Implementation Patterns +*********************** + +Event Structure Pattern +======================= + +All async events follow a common pattern with base event structure plus +implementation-specific data: + +.. code:: c + + // Your implementation structure + typedef struct _my_dns_addrinfo_s { + zend_async_dns_addrinfo_t event; // Base event (MUST be first) + my_event_loop_handle_t handle; // Your event loop handle + // Additional implementation-specific fields... + } my_dns_addrinfo_t; + +Factory Function Pattern +======================== + +Event factory functions create and initialize event structures: + +.. code:: c + + static zend_async_dns_addrinfo_t* my_getaddrinfo( + const char *node, const char *service, + const struct addrinfo *hints, size_t extra_size + ) { + // Allocate memory (include extra_size if specified) + size_t total_size = sizeof(my_dns_addrinfo_t) + extra_size; + my_dns_addrinfo_t *dns = pecalloc(1, total_size, 0); + + // Initialize base event fields + dns->event.base.extra_offset = sizeof(my_dns_addrinfo_t); + dns->event.base.ref_count = 1; + dns->event.base.add_callback = my_add_callback; + dns->event.base.del_callback = my_remove_callback; + dns->event.base.start = my_dns_start; + dns->event.base.stop = my_dns_stop; + dns->event.base.dispose = my_dns_dispose; + + // Store request parameters + dns->event.node = node; + dns->event.service = service; + dns->event.hints = hints; + + // Initialize your backend-specific handle + int result = my_event_loop_init_dns(&dns->handle, node, service, hints); + if (result != 0) { + my_throw_error("Failed to initialize DNS request: %s", my_strerror(result)); + pefree(dns, 0); + return NULL; + } + + // Link handle to event data + dns->handle.data = dns; + + return &dns->event; + } + +Lifecycle Methods +================= + +Implement the three lifecycle methods for each event type: + +.. code:: c + + // Start the async operation + static void my_dns_start(zend_async_event_t *event) { + EVENT_START_PROLOGUE(event); // Standard prologue + + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; + + int result = my_event_loop_start_dns(&dns->handle, my_dns_callback); + if (result != 0) { + my_throw_error("Failed to start DNS operation: %s", my_strerror(result)); + return; + } + + event->loop_ref_count++; + ZEND_ASYNC_INCREASE_EVENT_COUNT; + } + + // Stop the async operation + static void my_dns_stop(zend_async_event_t *event) { + EVENT_STOP_PROLOGUE(event); // Standard prologue + + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; + + my_event_loop_stop_dns(&dns->handle); + + event->loop_ref_count = 0; + ZEND_ASYNC_DECREASE_EVENT_COUNT; + } + + // Clean up and dispose + static void my_dns_dispose(zend_async_event_t *event) { + if (ZEND_ASYNC_EVENT_REF(event) > 1) { + ZEND_ASYNC_EVENT_DEL_REF(event); + return; + } + + // Stop if still running + if (event->loop_ref_count > 0) { + event->loop_ref_count = 1; + event->stop(event); + } + + // Free callbacks + zend_async_callbacks_free(event); + + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; + + // Clean up backend-specific resources + my_event_loop_cleanup_dns(&dns->handle); + + // Free the structure + pefree(dns, 0); + } + +******************** + Callback Handling +******************** + +Event Completion Callbacks +=========================== + +When your backend completes an operation, notify the core through callbacks: + +.. code:: c + + // Your backend callback (called by event loop) + static void my_dns_callback(my_event_loop_handle_t *handle, int status, struct addrinfo *result) { + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)handle->data; + zend_object *exception = NULL; + + // Close the event after one-time operations + ZEND_ASYNC_EVENT_SET_CLOSED(&dns->event.base); + dns->event.base.stop(&dns->event.base); + + // Handle errors + if (status != 0) { + exception = my_new_exception( + "DNS resolution failed: %s", my_strerror(status) + ); + } + + // Notify all registered callbacks + zend_async_callbacks_notify(&dns->event.base, result, exception); + + // Release exception if created + if (exception) { + zend_object_release(exception); + } + } + +Result Memory Management +======================== + +For operations that return allocated memory (like ``getaddrinfo``): + +.. code:: c + + static void my_freeaddrinfo(struct addrinfo *ai) { + if (ai != NULL) { + my_event_loop_free_addrinfo(ai); // Use your backend's free function + } + } + +**Make sure your ``getaddrinfo`` results can be freed by your ``freeaddrinfo`` implementation.** + +******************* + Error Handling +******************* + +Error Reporting +=============== + +Use the standard PHP error reporting mechanisms: + +.. code:: c + + // For recoverable errors during operation + static void my_throw_error(const char *format, ...) { + va_list args; + va_start(args, format); + zend_string *message = zend_vstrpprintf(0, format, args); + va_end(args); + + zend_throw_error(NULL, ZSTR_VAL(message)); + zend_string_release(message); + } + + // For creating exception objects + static zend_object* my_new_exception(const char *format, ...) { + va_list args; + va_start(args, format); + zend_string *message = zend_vstrpprintf(0, format, args); + va_end(args); + + zend_object *ex = zend_throw_exception(zend_ce_exception, ZSTR_VAL(message), 0); + zend_string_release(message); + return ex; + } + +Exception Classes +================= + +Use appropriate exception classes for different error types: + +.. code:: c + + // DNS errors + zend_object *dns_ex = async_new_exception(async_ce_dns_exception, "DNS error: %s", error_msg); + + // I/O errors + zend_object *io_ex = async_new_exception(async_ce_input_output_exception, "I/O error: %s", error_msg); + + // Timeout errors + zend_object *timeout_ex = async_new_exception(async_ce_timeout_exception, "Operation timed out"); + +******************* + Memory Management +******************* + +Reference Counting +================== + +The TrueAsync API uses reference counting for event objects: + +.. code:: c + + // Add reference (when storing event somewhere) + ZEND_ASYNC_EVENT_ADD_REF(event); + + // Remove reference (when done with event) + ZEND_ASYNC_EVENT_DEL_REF(event); + + // Release event (decrements and disposes if needed) + ZEND_ASYNC_EVENT_RELEASE(event); + +Memory Allocation +================= + +Use persistent allocation for long-lived structures: + +.. code:: c + + // For event structures that may outlive a request + my_event_t *event = pecalloc(1, sizeof(my_event_t), 0); // Persistent + + // For temporary data + char *buffer = emalloc(size); // Request-scoped + +Extra Data Support +================== + +Support the ``extra_size`` parameter for extensions: + +.. code:: c + + static zend_async_timer_event_t* my_new_timer_event( + const zend_ulong timeout, const zend_ulong nanoseconds, + const bool is_periodic, size_t extra_size + ) { + // Allocate base size + extra space + size_t total_size = sizeof(my_timer_event_t) + extra_size; + my_timer_event_t *timer = pecalloc(1, total_size, 0); + + // Set extra offset so extensions can find their data + timer->event.base.extra_offset = sizeof(my_timer_event_t); + + // ... rest of initialization + } + +******************* + Best Practices +******************* + +Performance Considerations +========================== + +* **Minimize allocations** in hot paths (event creation/callback handling) +* **Use object pools** for frequently created/destroyed events +* **Batch operations** when possible in your event loop +* **Profile memory usage** to detect leaks early + +Thread Safety +============== + +* **Document thread safety** guarantees of your implementation +* **Use proper locking** if supporting multi-threaded access +* **Consider thread-local storage** for per-thread state + +Testing +======= + +* **Create unit tests** for each API function implementation +* **Test error conditions** and memory cleanup +* **Verify callback behavior** under various scenarios +* **Load test** with many concurrent operations + +Documentation +============= + +* **Document implementation-specific behavior** and limitations +* **Provide configuration options** for tuning performance +* **Include examples** showing integration with your event loop +* **Document version compatibility** with different backend versions \ No newline at end of file diff --git a/docs/source/true_async_api/index.rst b/docs/source/true_async_api/index.rst new file mode 100644 index 0000000000000..76d9d417bdb71 --- /dev/null +++ b/docs/source/true_async_api/index.rst @@ -0,0 +1,103 @@ +################ + TrueAsync API +################ + +.. toctree:: + :hidden: + + architecture + api-reference + implementation-guide + patterns + examples + +The TrueAsync API provides a comprehensive asynchronous programming interface for PHP extensions, +built on top of the Zend Engine. This API enables non-blocking operations for I/O, timers, +signals, and other asynchronous tasks while maintaining clean separation between the API +definition and its implementation. + +.. warning:: + + TrueAsync API is currently in development. The API may change in future versions. + +The TrueAsync API follows a strict architectural pattern where the PHP core (php-src) defines +the API interfaces, while extensions provide concrete implementations. This design ensures +modularity, testability, and allows for multiple backend implementations (libuv, epoll, etc.). + +****************** + Key Features +****************** + +* **Non-blocking I/O operations** - File, network, and socket operations +* **Timer and scheduling support** - Timeout handling and periodic tasks +* **Signal handling** - Asynchronous signal processing +* **DNS resolution** - Async getaddrinfo/getnameinfo with proper memory management +* **Process management** - Spawn and monitor external processes +* **Filesystem monitoring** - Watch for file and directory changes +* **Clean separation** - API definition vs implementation +* **Memory safety** - Proper reference counting and cleanup + +****************** + Architecture +****************** + +The TrueAsync API consists of two main components: + +**API Definition Layer** (``Zend/zend_async_API.h``) + Defines function pointer types, structures, and macros that form the public API. + Located in the php-src core and used by all consumers. + +**Implementation Layer** (``ext/async/``) + Provides concrete implementations using libuv or other event loop libraries. + Registers implementations with the core through registration functions. + +This separation allows php-src core to use async functionality without directly depending +on any specific implementation, enabling clean modular design and testing. + +****************** + Getting Started +****************** + +To use the TrueAsync API in your extension: + +1. Include the API header: ``#include "Zend/zend_async_API.h"`` +2. Check if async is enabled: ``ZEND_ASYNC_IS_ENABLED()`` +3. Use API macros like ``ZEND_ASYNC_GETADDRINFO()`` for operations +4. Handle results through callback mechanisms +5. Clean up resources with appropriate free functions + +For implementation details, see the :doc:`implementation-guide` section. + +****************** + Quick Example +****************** + +.. code:: c + + // Async DNS resolution + zend_async_dns_addrinfo_t *event = ZEND_ASYNC_GETADDRINFO("example.com", "80", &hints); + + // Register callback for completion + zend_async_resume_when(coroutine, &event->base, true, my_callback, NULL); + + // Later, in callback: clean up + ZEND_ASYNC_FREEADDRINFO(result); + +****************** + Documentation Sections +****************** + +:doc:`architecture` + Detailed explanation of the two-repository architecture and component separation. + +:doc:`api-reference` + Complete API reference with function signatures, macros, and data structures. + +:doc:`implementation-guide` + Guide for implementing TrueAsync API backends and registering with the core. + +:doc:`patterns` + Common patterns, best practices, and coding conventions for the API. + +:doc:`examples` + Practical examples showing how to use different API components. \ No newline at end of file From 00a2768053c800810d61c2cf4c5aad018269e6fb Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:33:40 +0300 Subject: [PATCH 032/137] + TrueAsync API docs --- docs/source/index.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 21e2526f47f64..de0cd477c469d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,6 +15,12 @@ core/data-structures/index +.. toctree:: + :caption: TrueAsync API + :hidden: + + true_async_api/index + .. toctree:: :caption: Miscellaneous :hidden: From bd31f8ee65812b27fd1fb8e099523be62088b551 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:19:25 +0300 Subject: [PATCH 033/137] % Refactoring Zend DNS API --- Zend/zend_async_API.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index ca7ae2015e729..42728473a1f75 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -508,16 +508,18 @@ struct _zend_async_filesystem_event_s { struct _zend_async_dns_nameinfo_s { zend_async_event_t base; - const char *hostname; - const char *service; + /* These structure fields store the RESULT of the operation. + * It will be automatically freed when the structure is destroyed. */ + zend_string *hostname; + zend_string *service; }; struct _zend_async_dns_addrinfo_s { zend_async_event_t base; const char *node; const char *service; - const struct addrinfo *hints; - int flags; + /* The DNS resolution result must be explicitly and mandatorily freed using the ZEND_ASYNC_FREEADDRINFO method! */ + struct addrinfo *result; }; struct _zend_async_exec_event_s { From 305cef78cdd1cf90600f39f16f6e51b3754af4fb Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:00:23 +0300 Subject: [PATCH 034/137] + ZEND_ASYNC_GET_COROUTINE_CONTEXT --- Zend/zend_async_API.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 42728473a1f75..694739e2dc6f4 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1085,5 +1085,11 @@ END_EXTERN_C() /* Context API Macros */ #define ZEND_ASYNC_NEW_CONTEXT(parent) zend_async_new_context_fn(parent) #define ZEND_ASYNC_CURRENT_CONTEXT (ZEND_ASYNC_G(coroutine) != NULL ? ZEND_ASYNC_G(coroutine)->scope->context : NULL) +#define ZEND_ASYNC_GET_COROUTINE_CONTEXT() \ + ((ZEND_ASYNC_G(coroutine)) ? \ + (ZEND_ASYNC_G(coroutine)->context ? \ + ZEND_ASYNC_G(coroutine)->context : \ + (ZEND_ASYNC_G(coroutine)->context = ZEND_ASYNC_NEW_CONTEXT(NULL))) \ + : NULL) #endif //ZEND_ASYNC_API_H \ No newline at end of file From 19dc56111a9f4899b10ce5b6b4e6589ad563e93d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:41:23 +0300 Subject: [PATCH 035/137] + Coroutine Internal context --- Zend/zend_async_API.c | 46 +++++++++++++++++++++++++++++++++++++++++++ Zend/zend_async_API.h | 28 ++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index e59ceb377f7e6..93b79e396a2c2 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -45,6 +45,35 @@ static zend_async_context_t * new_context(void) return NULL; } +static uint32_t internal_context_key_alloc(const char *key_name) +{ + ASYNC_THROW_ERROR("Internal Context API is not enabled"); + return 0; +} + +static const char* internal_context_key_name(uint32_t key) +{ + ASYNC_THROW_ERROR("Internal Context API is not enabled"); + return NULL; +} + +static bool internal_context_get(zend_coroutine_t *coroutine, uint32_t key, zval *result) +{ + ASYNC_THROW_ERROR("Internal Context API is not enabled"); + return false; +} + +static void internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value) +{ + ASYNC_THROW_ERROR("Internal Context API is not enabled"); +} + +static bool internal_context_unset(zend_coroutine_t *coroutine, uint32_t key) +{ + ASYNC_THROW_ERROR("Internal Context API is not enabled"); + return false; +} + static zend_class_entry * get_class_ce(zend_async_class type) { if (type == ZEND_ASYNC_EXCEPTION_DEFAULT @@ -102,6 +131,13 @@ zend_async_queue_task_t zend_async_queue_task_fn = NULL; /* Context API */ zend_async_new_context_t zend_async_new_context_fn = new_context; +/* Internal Context API */ +zend_async_internal_context_key_alloc_t zend_async_internal_context_key_alloc_fn = internal_context_key_alloc; +zend_async_internal_context_key_name_t zend_async_internal_context_key_name_fn = internal_context_key_name; +zend_async_internal_context_get_t zend_async_internal_context_get_fn = internal_context_get; +zend_async_internal_context_set_t zend_async_internal_context_set_fn = internal_context_set; +zend_async_internal_context_unset_t zend_async_internal_context_unset_fn = internal_context_unset; + ZEND_API bool zend_async_is_enabled(void) { return scheduler_module_name != NULL && reactor_module_name != NULL; @@ -182,6 +218,11 @@ ZEND_API bool zend_async_scheduler_register( zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, zend_async_new_context_t new_context_fn, + zend_async_internal_context_key_alloc_t internal_context_key_alloc_fn, + zend_async_internal_context_key_name_t internal_context_key_name_fn, + zend_async_internal_context_get_t internal_context_get_fn, + zend_async_internal_context_set_t internal_context_set_fn, + zend_async_internal_context_unset_t internal_context_unset_fn, zend_async_spawn_t spawn_fn, zend_async_suspend_t suspend_fn, zend_async_enqueue_coroutine_t enqueue_coroutine_fn, @@ -216,6 +257,11 @@ ZEND_API bool zend_async_scheduler_register( zend_async_new_coroutine_fn = new_coroutine_fn; zend_async_new_scope_fn = new_scope_fn; zend_async_new_context_fn = new_context_fn; + zend_async_internal_context_key_alloc_fn = internal_context_key_alloc_fn; + zend_async_internal_context_key_name_fn = internal_context_key_name_fn; + zend_async_internal_context_get_fn = internal_context_get_fn; + zend_async_internal_context_set_fn = internal_context_set_fn; + zend_async_internal_context_unset_fn = internal_context_unset_fn; zend_async_spawn_fn = spawn_fn; zend_async_suspend_fn = suspend_fn; zend_async_enqueue_coroutine_fn = enqueue_coroutine_fn; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 694739e2dc6f4..9874489aba2d4 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -156,6 +156,12 @@ typedef struct _zend_async_exec_event_s zend_async_exec_event_t; typedef struct _zend_async_task_s zend_async_task_t; +typedef uint32_t (*zend_async_internal_context_key_alloc_t)(const char *key_name); +typedef const char* (*zend_async_internal_context_key_name_t)(uint32_t key); +typedef bool (*zend_async_internal_context_get_t)(zend_coroutine_t *coroutine, uint32_t key, zval *result); +typedef void (*zend_async_internal_context_set_t)(zend_coroutine_t *coroutine, uint32_t key, zval *value); +typedef bool (*zend_async_internal_context_unset_t)(zend_coroutine_t *coroutine, uint32_t key); + typedef zend_coroutine_t * (*zend_async_new_coroutine_t)(zend_async_scope_t *scope); typedef zend_async_scope_t * (*zend_async_new_scope_t)(zend_async_scope_t * parent_scope); typedef zend_coroutine_t * (*zend_async_spawn_t)(zend_async_scope_t *scope, zend_object *scope_provider); @@ -717,6 +723,9 @@ struct _zend_coroutine_s { /* Coroutine context object */ zend_async_context_t *context; + /* Internal context (for C extensions with numeric keys) */ + HashTable *internal_context; + /* Spawned file and line number */ zend_string *filename; uint32_t lineno; @@ -919,6 +928,13 @@ ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; /* Context API */ ZEND_API extern zend_async_new_context_t zend_async_new_context_fn; +/* Internal Context API */ +ZEND_API extern zend_async_internal_context_key_alloc_t zend_async_internal_context_key_alloc_fn; +ZEND_API extern zend_async_internal_context_key_name_t zend_async_internal_context_key_name_fn; +ZEND_API extern zend_async_internal_context_get_t zend_async_internal_context_get_fn; +ZEND_API extern zend_async_internal_context_set_t zend_async_internal_context_set_fn; +ZEND_API extern zend_async_internal_context_unset_t zend_async_internal_context_unset_fn; + /* Reactor API */ ZEND_API bool zend_async_reactor_is_enabled(void); @@ -954,6 +970,11 @@ ZEND_API bool zend_async_scheduler_register( zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, zend_async_new_context_t new_context_fn, + zend_async_internal_context_key_alloc_t internal_context_key_alloc_fn, + zend_async_internal_context_key_name_t internal_context_key_name_fn, + zend_async_internal_context_get_t internal_context_get_fn, + zend_async_internal_context_set_t internal_context_set_fn, + zend_async_internal_context_unset_t internal_context_unset_fn, zend_async_spawn_t spawn_fn, zend_async_suspend_t suspend_fn, zend_async_enqueue_coroutine_t enqueue_coroutine_fn, @@ -1092,4 +1113,11 @@ END_EXTERN_C() (ZEND_ASYNC_G(coroutine)->context = ZEND_ASYNC_NEW_CONTEXT(NULL))) \ : NULL) +/* Internal Context API Macros */ +#define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC(key_name) zend_async_internal_context_key_alloc_fn(key_name) +#define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_NAME(key) zend_async_internal_context_key_name_fn(key) +#define ZEND_ASYNC_INTERNAL_CONTEXT_GET(coro, key, result) zend_async_internal_context_get_fn(coro, key, result) +#define ZEND_ASYNC_INTERNAL_CONTEXT_SET(coro, key, value) zend_async_internal_context_set_fn(coro, key, value) +#define ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coro, key) zend_async_internal_context_unset_fn(coro, key) + #endif //ZEND_ASYNC_API_H \ No newline at end of file From 83452b8cc67360aaa6ace69c77deec7f1ce4b639 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 5 Jun 2025 21:58:55 +0300 Subject: [PATCH 036/137] % refactoring Internal Context API --- Zend/zend_async_API.c | 201 +++++++++++++++++++++++++++++++++--------- Zend/zend_async_API.h | 39 ++++---- main/main.c | 1 + 3 files changed, 176 insertions(+), 65 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 93b79e396a2c2..45084cbff8de9 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -45,34 +45,7 @@ static zend_async_context_t * new_context(void) return NULL; } -static uint32_t internal_context_key_alloc(const char *key_name) -{ - ASYNC_THROW_ERROR("Internal Context API is not enabled"); - return 0; -} - -static const char* internal_context_key_name(uint32_t key) -{ - ASYNC_THROW_ERROR("Internal Context API is not enabled"); - return NULL; -} - -static bool internal_context_get(zend_coroutine_t *coroutine, uint32_t key, zval *result) -{ - ASYNC_THROW_ERROR("Internal Context API is not enabled"); - return false; -} - -static void internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value) -{ - ASYNC_THROW_ERROR("Internal Context API is not enabled"); -} - -static bool internal_context_unset(zend_coroutine_t *coroutine, uint32_t key) -{ - ASYNC_THROW_ERROR("Internal Context API is not enabled"); - return false; -} +/* Internal context stubs removed - using direct implementation */ static zend_class_entry * get_class_ce(zend_async_class type) { @@ -131,12 +104,7 @@ zend_async_queue_task_t zend_async_queue_task_fn = NULL; /* Context API */ zend_async_new_context_t zend_async_new_context_fn = new_context; -/* Internal Context API */ -zend_async_internal_context_key_alloc_t zend_async_internal_context_key_alloc_fn = internal_context_key_alloc; -zend_async_internal_context_key_name_t zend_async_internal_context_key_name_fn = internal_context_key_name; -zend_async_internal_context_get_t zend_async_internal_context_get_fn = internal_context_get; -zend_async_internal_context_set_t zend_async_internal_context_set_fn = internal_context_set; -zend_async_internal_context_unset_t zend_async_internal_context_unset_fn = internal_context_unset; +/* Internal Context API - now uses direct functions */ ZEND_API bool zend_async_is_enabled(void) { @@ -205,6 +173,7 @@ void zend_async_globals_dtor(void) void zend_async_shutdown(void) { zend_async_globals_dtor(); + zend_async_shutdown_internal_context_api(); } ZEND_API int zend_async_get_api_version_number(void) @@ -218,11 +187,6 @@ ZEND_API bool zend_async_scheduler_register( zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, zend_async_new_context_t new_context_fn, - zend_async_internal_context_key_alloc_t internal_context_key_alloc_fn, - zend_async_internal_context_key_name_t internal_context_key_name_fn, - zend_async_internal_context_get_t internal_context_get_fn, - zend_async_internal_context_set_t internal_context_set_fn, - zend_async_internal_context_unset_t internal_context_unset_fn, zend_async_spawn_t spawn_fn, zend_async_suspend_t suspend_fn, zend_async_enqueue_coroutine_t enqueue_coroutine_fn, @@ -257,11 +221,6 @@ ZEND_API bool zend_async_scheduler_register( zend_async_new_coroutine_fn = new_coroutine_fn; zend_async_new_scope_fn = new_scope_fn; zend_async_new_context_fn = new_context_fn; - zend_async_internal_context_key_alloc_fn = internal_context_key_alloc_fn; - zend_async_internal_context_key_name_fn = internal_context_key_name_fn; - zend_async_internal_context_get_fn = internal_context_get_fn; - zend_async_internal_context_set_fn = internal_context_set_fn; - zend_async_internal_context_unset_fn = internal_context_unset_fn; zend_async_spawn_fn = spawn_fn; zend_async_suspend_fn = suspend_fn; zend_async_enqueue_coroutine_fn = enqueue_coroutine_fn; @@ -774,3 +733,157 @@ ZEND_API ZEND_COLD zend_object * zend_async_throw_timeout(const char *format, co ////////////////////////////////////////////////////////////////////// /* Exception API end */ ////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// +/* Internal Context API */ +////////////////////////////////////////////////////////////////////// + +// Global variables for internal context key management +static HashTable *zend_async_context_key_names = NULL; + +#ifdef TSRM +static MUTEX_T zend_async_context_mutex = NULL; +#endif + +void zend_async_init_internal_context_api(void) +{ +#ifdef TSRM + zend_async_context_mutex = tsrm_mutex_alloc(); +#endif + // Initialize key names table - stores string pointers directly + zend_async_context_key_names = pemalloc(sizeof(HashTable), 1); + zend_hash_init(zend_async_context_key_names, 8, NULL, NULL, 1); // No destructor - we don't own the strings +} + +uint32_t zend_async_internal_context_key_alloc(const char *key_name) +{ +#ifdef TSRM + tsrm_mutex_lock(zend_async_context_mutex); +#endif + + // Check if this string address already has a key + const char **existing_ptr; + ZEND_HASH_FOREACH_NUM_KEY_PTR(zend_async_context_key_names, zend_ulong existing_key, existing_ptr) { + if (*existing_ptr == key_name) { + // Found existing key for this string address +#ifdef TSRM + tsrm_mutex_unlock(zend_async_context_mutex); +#endif + return (uint32_t)existing_key; + } + } ZEND_HASH_FOREACH_END(); + + // Use next available index as key + uint32_t key = zend_hash_num_elements(zend_async_context_key_names) + 1; + + // Store string pointer directly + zend_hash_index_add_ptr(zend_async_context_key_names, key, (void*)key_name); + +#ifdef TSRM + tsrm_mutex_unlock(zend_async_context_mutex); +#endif + + return key; +} + +const char* zend_async_internal_context_key_name(uint32_t key) +{ + if (zend_async_context_key_names == NULL) { + return NULL; + } + +#ifdef TSRM + tsrm_mutex_lock(zend_async_context_mutex); +#endif + + const char *name = zend_hash_index_find_ptr(zend_async_context_key_names, key); + +#ifdef TSRM + tsrm_mutex_unlock(zend_async_context_mutex); +#endif + + return name; +} + +bool zend_async_internal_context_get(zend_coroutine_t *coroutine, uint32_t key, zval *result) +{ + if (coroutine == NULL || coroutine->internal_context == NULL) { + return false; + } + + zval *value = zend_hash_index_find(coroutine->internal_context, key); + if (value == NULL) { + return false; + } + + ZVAL_COPY(result, value); + return true; +} + +void zend_async_internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value) +{ + if (coroutine == NULL) { + return; + } + + // Initialize internal_context if needed + if (coroutine->internal_context == NULL) { + coroutine->internal_context = emalloc(sizeof(HashTable)); + zend_hash_init(coroutine->internal_context, 8, NULL, ZVAL_PTR_DTOR, 0); + } + + // Set the value + zval copy; + ZVAL_COPY(©, value); + zend_hash_index_update(coroutine->internal_context, key, ©); +} + +bool zend_async_internal_context_unset(zend_coroutine_t *coroutine, uint32_t key) +{ + if (coroutine == NULL || coroutine->internal_context == NULL) { + return false; + } + + return zend_hash_index_del(coroutine->internal_context, key) == SUCCESS; +} + +void zend_async_coroutine_dispose_internal_context(zend_coroutine_t *coroutine) +{ + if (coroutine->internal_context != NULL) { + zend_hash_destroy(coroutine->internal_context); + efree(coroutine->internal_context); + coroutine->internal_context = NULL; + } +} + +void zend_async_shutdown_internal_context_api(void) +{ +#ifdef TSRM + if (zend_async_context_mutex != NULL) { + tsrm_mutex_lock(zend_async_context_mutex); + } +#endif + + if (zend_async_context_key_names != NULL) { + zend_hash_destroy(zend_async_context_key_names); + pefree(zend_async_context_key_names, 1); + zend_async_context_key_names = NULL; + } + +#ifdef TSRM + if (zend_async_context_mutex != NULL) { + tsrm_mutex_unlock(zend_async_context_mutex); + tsrm_mutex_free(zend_async_context_mutex); + zend_async_context_mutex = NULL; + } +#endif +} + +void zend_async_coroutine_init_internal_context(zend_coroutine_t *coroutine) +{ + coroutine->internal_context = NULL; +} + +////////////////////////////////////////////////////////////////////// +/* Internal Context API end */ +////////////////////////////////////////////////////////////////////// diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 9874489aba2d4..85177f595e6df 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -156,11 +156,7 @@ typedef struct _zend_async_exec_event_s zend_async_exec_event_t; typedef struct _zend_async_task_s zend_async_task_t; -typedef uint32_t (*zend_async_internal_context_key_alloc_t)(const char *key_name); -typedef const char* (*zend_async_internal_context_key_name_t)(uint32_t key); -typedef bool (*zend_async_internal_context_get_t)(zend_coroutine_t *coroutine, uint32_t key, zval *result); -typedef void (*zend_async_internal_context_set_t)(zend_coroutine_t *coroutine, uint32_t key, zval *value); -typedef bool (*zend_async_internal_context_unset_t)(zend_coroutine_t *coroutine, uint32_t key); +/* Internal context typedefs removed - using direct functions */ typedef zend_coroutine_t * (*zend_async_new_coroutine_t)(zend_async_scope_t *scope); typedef zend_async_scope_t * (*zend_async_new_scope_t)(zend_async_scope_t * parent_scope); @@ -928,12 +924,18 @@ ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; /* Context API */ ZEND_API extern zend_async_new_context_t zend_async_new_context_fn; -/* Internal Context API */ -ZEND_API extern zend_async_internal_context_key_alloc_t zend_async_internal_context_key_alloc_fn; -ZEND_API extern zend_async_internal_context_key_name_t zend_async_internal_context_key_name_fn; -ZEND_API extern zend_async_internal_context_get_t zend_async_internal_context_get_fn; -ZEND_API extern zend_async_internal_context_set_t zend_async_internal_context_set_fn; -ZEND_API extern zend_async_internal_context_unset_t zend_async_internal_context_unset_fn; +/* Internal Context API - Direct Functions */ +ZEND_API uint32_t zend_async_internal_context_key_alloc(const char *key_name); +ZEND_API const char* zend_async_internal_context_key_name(uint32_t key); +ZEND_API bool zend_async_internal_context_get(zend_coroutine_t *coroutine, uint32_t key, zval *result); +ZEND_API void zend_async_internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value); +ZEND_API bool zend_async_internal_context_unset(zend_coroutine_t *coroutine, uint32_t key); + +/* Internal Context initialization and cleanup */ +ZEND_API void zend_async_init_internal_context_api(void); +ZEND_API void zend_async_coroutine_dispose_internal_context(zend_coroutine_t *coroutine); +ZEND_API void zend_async_shutdown_internal_context_api(void); +ZEND_API void zend_async_coroutine_init_internal_context(zend_coroutine_t *coroutine); /* Reactor API */ @@ -970,11 +972,6 @@ ZEND_API bool zend_async_scheduler_register( zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, zend_async_new_context_t new_context_fn, - zend_async_internal_context_key_alloc_t internal_context_key_alloc_fn, - zend_async_internal_context_key_name_t internal_context_key_name_fn, - zend_async_internal_context_get_t internal_context_get_fn, - zend_async_internal_context_set_t internal_context_set_fn, - zend_async_internal_context_unset_t internal_context_unset_fn, zend_async_spawn_t spawn_fn, zend_async_suspend_t suspend_fn, zend_async_enqueue_coroutine_t enqueue_coroutine_fn, @@ -1114,10 +1111,10 @@ END_EXTERN_C() : NULL) /* Internal Context API Macros */ -#define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC(key_name) zend_async_internal_context_key_alloc_fn(key_name) -#define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_NAME(key) zend_async_internal_context_key_name_fn(key) -#define ZEND_ASYNC_INTERNAL_CONTEXT_GET(coro, key, result) zend_async_internal_context_get_fn(coro, key, result) -#define ZEND_ASYNC_INTERNAL_CONTEXT_SET(coro, key, value) zend_async_internal_context_set_fn(coro, key, value) -#define ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coro, key) zend_async_internal_context_unset_fn(coro, key) +#define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC(key_name) zend_async_internal_context_key_alloc(key_name) +#define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_NAME(key) zend_async_internal_context_key_name(key) +#define ZEND_ASYNC_INTERNAL_CONTEXT_GET(coro, key, result) zend_async_internal_context_get(coro, key, result) +#define ZEND_ASYNC_INTERNAL_CONTEXT_SET(coro, key, value) zend_async_internal_context_set(coro, key, value) +#define ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coro, key) zend_async_internal_context_unset(coro, key) #endif //ZEND_ASYNC_API_H \ No newline at end of file diff --git a/main/main.c b/main/main.c index cb50e9c8f89ac..cf7544d8ae500 100644 --- a/main/main.c +++ b/main/main.c @@ -2184,6 +2184,7 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi gc_globals_ctor(); #ifdef PHP_ASYNC_API zend_async_globals_ctor(); + zend_async_init_internal_context_api(); #endif zend_observer_startup(); From 60567ffa118948db2d94ecdeafbdbaa039087f21 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 6 Jun 2025 09:15:01 +0300 Subject: [PATCH 037/137] % refactoring Internal Context API --- Zend/zend_async_API.c | 44 ++++++++++++++++++++++++------------------- Zend/zend_async_API.h | 6 +++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 45084cbff8de9..3ea5d40d32b9c 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -173,7 +173,7 @@ void zend_async_globals_dtor(void) void zend_async_shutdown(void) { zend_async_globals_dtor(); - zend_async_shutdown_internal_context_api(); + zend_async_internal_context_api_shutdown(); } ZEND_API int zend_async_get_api_version_number(void) @@ -735,19 +735,28 @@ ZEND_API ZEND_COLD zend_object * zend_async_throw_timeout(const char *format, co ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// -/* Internal Context API */ +/// ### Internal Coroutine Context +/// +/// The internal coroutine context is intended for use in PHP-extensions. +/// The flow is as follows: +/// +/// 1. The extension registers a unique numeric index by `zend_async_internal_context_key_alloc`, +/// described by a static C string. +/// 2. The **TrueAsync API** verifies the uniqueness of the mapping between the index and the C string address. +/// 3. The extension uses `zend_async_internal_context_*` functions to get, set, or unset keys. +/// 4. Keys are automatically destroyed when the coroutine completes. ////////////////////////////////////////////////////////////////////// // Global variables for internal context key management static HashTable *zend_async_context_key_names = NULL; -#ifdef TSRM +#ifdef ZTS static MUTEX_T zend_async_context_mutex = NULL; #endif void zend_async_init_internal_context_api(void) { -#ifdef TSRM +#ifdef ZTS zend_async_context_mutex = tsrm_mutex_alloc(); #endif // Initialize key names table - stores string pointers directly @@ -757,7 +766,7 @@ void zend_async_init_internal_context_api(void) uint32_t zend_async_internal_context_key_alloc(const char *key_name) { -#ifdef TSRM +#ifdef ZTS tsrm_mutex_lock(zend_async_context_mutex); #endif @@ -766,7 +775,7 @@ uint32_t zend_async_internal_context_key_alloc(const char *key_name) ZEND_HASH_FOREACH_NUM_KEY_PTR(zend_async_context_key_names, zend_ulong existing_key, existing_ptr) { if (*existing_ptr == key_name) { // Found existing key for this string address -#ifdef TSRM +#ifdef ZTS tsrm_mutex_unlock(zend_async_context_mutex); #endif return (uint32_t)existing_key; @@ -779,7 +788,7 @@ uint32_t zend_async_internal_context_key_alloc(const char *key_name) // Store string pointer directly zend_hash_index_add_ptr(zend_async_context_key_names, key, (void*)key_name); -#ifdef TSRM +#ifdef ZTS tsrm_mutex_unlock(zend_async_context_mutex); #endif @@ -792,13 +801,13 @@ const char* zend_async_internal_context_key_name(uint32_t key) return NULL; } -#ifdef TSRM +#ifdef ZTS tsrm_mutex_lock(zend_async_context_mutex); #endif const char *name = zend_hash_index_find_ptr(zend_async_context_key_names, key); -#ifdef TSRM +#ifdef ZTS tsrm_mutex_unlock(zend_async_context_mutex); #endif @@ -828,8 +837,7 @@ void zend_async_internal_context_set(zend_coroutine_t *coroutine, uint32_t key, // Initialize internal_context if needed if (coroutine->internal_context == NULL) { - coroutine->internal_context = emalloc(sizeof(HashTable)); - zend_hash_init(coroutine->internal_context, 8, NULL, ZVAL_PTR_DTOR, 0); + coroutine->internal_context = zend_new_array(0); } // Set the value @@ -847,18 +855,17 @@ bool zend_async_internal_context_unset(zend_coroutine_t *coroutine, uint32_t key return zend_hash_index_del(coroutine->internal_context, key) == SUCCESS; } -void zend_async_coroutine_dispose_internal_context(zend_coroutine_t *coroutine) +void zend_async_coroutine_internal_context_dispose(zend_coroutine_t *coroutine) { if (coroutine->internal_context != NULL) { - zend_hash_destroy(coroutine->internal_context); - efree(coroutine->internal_context); + zend_array_release(coroutine->internal_context); coroutine->internal_context = NULL; } } -void zend_async_shutdown_internal_context_api(void) +void zend_async_internal_context_api_shutdown(void) { -#ifdef TSRM +#ifdef ZTS if (zend_async_context_mutex != NULL) { tsrm_mutex_lock(zend_async_context_mutex); } @@ -870,7 +877,7 @@ void zend_async_shutdown_internal_context_api(void) zend_async_context_key_names = NULL; } -#ifdef TSRM +#ifdef ZTS if (zend_async_context_mutex != NULL) { tsrm_mutex_unlock(zend_async_context_mutex); tsrm_mutex_free(zend_async_context_mutex); @@ -879,11 +886,10 @@ void zend_async_shutdown_internal_context_api(void) #endif } -void zend_async_coroutine_init_internal_context(zend_coroutine_t *coroutine) +void zend_async_coroutine_internal_context_init(zend_coroutine_t *coroutine) { coroutine->internal_context = NULL; } - ////////////////////////////////////////////////////////////////////// /* Internal Context API end */ ////////////////////////////////////////////////////////////////////// diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 85177f595e6df..b05771ece49a0 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -933,9 +933,9 @@ ZEND_API bool zend_async_internal_context_unset(zend_coroutine_t *coroutine, uin /* Internal Context initialization and cleanup */ ZEND_API void zend_async_init_internal_context_api(void); -ZEND_API void zend_async_coroutine_dispose_internal_context(zend_coroutine_t *coroutine); -ZEND_API void zend_async_shutdown_internal_context_api(void); -ZEND_API void zend_async_coroutine_init_internal_context(zend_coroutine_t *coroutine); +ZEND_API void zend_async_coroutine_internal_context_dispose(zend_coroutine_t *coroutine); +ZEND_API void zend_async_internal_context_api_shutdown(void); +ZEND_API void zend_async_coroutine_internal_context_init(zend_coroutine_t *coroutine); /* Reactor API */ From 98ab2e667d8a8dc180a2883d88d1893d4ec1d508 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 6 Jun 2025 11:16:58 +0300 Subject: [PATCH 038/137] % refactoring ZEND_ASYNC_INTERNAL_CONTEXT_FIND --- Zend/zend_async_API.c | 12 +++--------- Zend/zend_async_API.h | 4 ++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 3ea5d40d32b9c..417e92b8b0313 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -814,19 +814,13 @@ const char* zend_async_internal_context_key_name(uint32_t key) return name; } -bool zend_async_internal_context_get(zend_coroutine_t *coroutine, uint32_t key, zval *result) +zval* zend_async_internal_context_find(zend_coroutine_t *coroutine, uint32_t key) { if (coroutine == NULL || coroutine->internal_context == NULL) { - return false; - } - - zval *value = zend_hash_index_find(coroutine->internal_context, key); - if (value == NULL) { - return false; + return NULL; } - ZVAL_COPY(result, value); - return true; + return zend_hash_index_find(coroutine->internal_context, key); } void zend_async_internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index b05771ece49a0..8db3299961287 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -927,7 +927,7 @@ ZEND_API extern zend_async_new_context_t zend_async_new_context_fn; /* Internal Context API - Direct Functions */ ZEND_API uint32_t zend_async_internal_context_key_alloc(const char *key_name); ZEND_API const char* zend_async_internal_context_key_name(uint32_t key); -ZEND_API bool zend_async_internal_context_get(zend_coroutine_t *coroutine, uint32_t key, zval *result); +ZEND_API zval * zend_async_internal_context_find(zend_coroutine_t *coroutine, uint32_t key); ZEND_API void zend_async_internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value); ZEND_API bool zend_async_internal_context_unset(zend_coroutine_t *coroutine, uint32_t key); @@ -1113,7 +1113,7 @@ END_EXTERN_C() /* Internal Context API Macros */ #define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC(key_name) zend_async_internal_context_key_alloc(key_name) #define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_NAME(key) zend_async_internal_context_key_name(key) -#define ZEND_ASYNC_INTERNAL_CONTEXT_GET(coro, key, result) zend_async_internal_context_get(coro, key, result) +#define ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coro, key) zend_async_internal_context_find(coro, key) #define ZEND_ASYNC_INTERNAL_CONTEXT_SET(coro, key, value) zend_async_internal_context_set(coro, key, value) #define ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coro, key) zend_async_internal_context_unset(coro, key) From aaaafe62237460dd7ba1e5398ea18218ff50766f Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 6 Jun 2025 14:34:28 +0300 Subject: [PATCH 039/137] + zend_async_event_callback API --- Zend/zend_async_API.c | 47 ++++++++++++++++++++++++++++++++++++++++--- Zend/zend_async_API.h | 6 ++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 417e92b8b0313..dd7c2032a569a 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -344,6 +344,47 @@ ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char } } +static void event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) +{ + if (callback->ref_count > 1) { + // If the callback is still referenced, we cannot dispose it yet + callback->ref_count--; + return; + } + + callback->ref_count = 0; + efree(callback); +} + +ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_event_callback_fn callback, size_t size) +{ + zend_async_event_callback_t * event_callback = ecalloc(1, size == 0 ? size : sizeof(zend_async_event_callback_t)); + + event_callback->ref_count = 1; + event_callback->callback = callback; + event_callback->dispose = event_callback_dispose; + + return event_callback; +} + +static void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event); + +ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_event_new( + zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size +) +{ + zend_coroutine_event_callback_t * coroutine_callback = ecalloc( + 1, size != 0 ? size : sizeof(zend_coroutine_event_callback_t) + ); + + coroutine_callback->base.ref_count = 1; + coroutine_callback->base.callback = callback; + coroutine_callback->coroutine = coroutine; + coroutine_callback->base.dispose = coroutine_event_callback_dispose; + + return coroutine_callback; +} + ////////////////////////////////////////////////////////////////////// /* Waker API */ ////////////////////////////////////////////////////////////////////// @@ -449,7 +490,7 @@ ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine) efree(waker); } -static void event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) +static void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) { if (callback->ref_count > 1) { // If the callback is still referenced, we cannot dispose it yet @@ -516,12 +557,12 @@ ZEND_API void zend_async_resume_when( event_callback = emalloc(sizeof(zend_coroutine_event_callback_t)); event_callback->base.ref_count = 1; event_callback->base.callback = callback; - event_callback->base.dispose = event_callback_dispose; + event_callback->base.dispose = coroutine_event_callback_dispose; } // Set up the default dispose function if not set if (event_callback->base.dispose == NULL) { - event_callback->base.dispose = event_callback_dispose; + event_callback->base.dispose = coroutine_event_callback_dispose; } event_callback->coroutine = coroutine; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 8db3299961287..cadc644cf5e69 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1011,6 +1011,12 @@ ZEND_API void zend_async_thread_pool_register( ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char *zend_coroutine_name); +ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_event_callback_fn callback, size_t size); + +ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_event_new( + zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size +); + /* Waker API */ ZEND_API zend_async_waker_t *zend_async_waker_new(zend_coroutine_t *coroutine); ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( From 0381a5c37832b39e26e2b4b850b7db2b46acfeb3 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:10:17 +0300 Subject: [PATCH 040/137] fix: Fixed a bug related to event deletion. --- Zend/zend_async_API.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index cadc644cf5e69..cc5138e2e89ba 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -407,8 +407,8 @@ zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_ca for (uint32_t i = 0; i < vector->length; ++i) { if (vector->data[i] == callback) { - vector->data[i] = vector->data[--vector->length]; /* O(1) removal */ callback->dispose(vector->data[i], event); + vector->data[i] = vector->length > 0 ? vector->data[--vector->length] : NULL; /* O(1) removal */ return; } } From 7970eaa74f7789a4ea19029f90e0ada817f8dc4d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 7 Jun 2025 11:45:56 +0300 Subject: [PATCH 041/137] Fix concurrent modification bug in zend_async_callbacks_notify Resolved a critical race condition where callbacks could be added/removed during iteration, causing memory corruption and unpredictable behavior. Solution: - Added single iterator tracking to zend_async_callbacks_vector_t - Implemented safe index adjustment when callbacks are removed during iteration - Added explicit protection against nested notify() calls with clear error message - Maintains O(1) performance for all operations with zero additional allocations Changes: - Modified zend_async_callbacks_vector_t structure with current_iterator field - Updated zend_async_callbacks_notify() with iterator registration/protection - Enhanced zend_async_callbacks_remove() to safely adjust active iterator - Added helper functions for iterator management and concurrent access prevention Benefits: - Eliminates race conditions during callback execution - Prevents undefined behavior from concurrent modifications - Maintains backward compatibility and performance - Provides clear diagnostics for invalid usage patterns --- Zend/zend_async_API.h | 92 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index cc5138e2e89ba..aea36f348ca2d 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -259,11 +259,14 @@ struct _zend_async_waker_trigger_s { zend_async_event_callback_t *callback; }; -/* Dynamic array of async event callbacks */ +/* Dynamic array of async event callbacks with single iterator protection */ typedef struct _zend_async_callbacks_vector_s { - uint32_t length; /* current number of callbacks */ - uint32_t capacity; /* allocated slots in the array */ - zend_async_event_callback_t **data; /* dynamically allocated callback array */ + uint32_t length; /* current number of callbacks */ + uint32_t capacity; /* allocated slots in the array */ + zend_async_event_callback_t **data; /* dynamically allocated callback array */ + + /* Single iterator tracking - NULL means no active iteration */ + uint32_t *current_iterator; /* pointer to active iterator index */ } zend_async_callbacks_vector_t; /** @@ -377,6 +380,35 @@ struct _zend_async_event_s { } \ } while (0) +/* Register the single iterator - prevents concurrent iterations */ +static zend_always_inline bool +zend_async_callbacks_register_iterator(zend_async_callbacks_vector_t *vector, uint32_t *iterator_index) +{ + if (vector->current_iterator != NULL) { + // Concurrent iteration detected - this is not allowed + return false; + } + + vector->current_iterator = iterator_index; + return true; +} + +/* Unregister the iterator after iteration completes */ +static zend_always_inline void +zend_async_callbacks_unregister_iterator(zend_async_callbacks_vector_t *vector) +{ + vector->current_iterator = NULL; +} + +/* Adjust the active iterator when an element is removed */ +static zend_always_inline void +zend_async_callbacks_adjust_iterator(zend_async_callbacks_vector_t *vector, uint32_t removed_index) +{ + if (vector->current_iterator != NULL && *vector->current_iterator > removed_index) { + (*vector->current_iterator)--; + } +} + /* Append a callback; grows the buffer when needed */ static zend_always_inline void zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t *callback) @@ -399,7 +431,7 @@ zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t vector->data[vector->length++] = callback; } -/* Remove a specific callback; order is NOT preserved */ +/* Remove a specific callback; order is NOT preserved, but iterator is safely adjusted */ static zend_always_inline void zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_callback_t *callback) { @@ -408,13 +440,18 @@ zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_ca for (uint32_t i = 0; i < vector->length; ++i) { if (vector->data[i] == callback) { callback->dispose(vector->data[i], event); - vector->data[i] = vector->length > 0 ? vector->data[--vector->length] : NULL; /* O(1) removal */ + + // Adjust the active iterator before performing the removal + zend_async_callbacks_adjust_iterator(vector, i); + + // O(1) removal: move last element to current position + vector->data[i] = vector->length > 0 ? vector->data[--vector->length] : NULL; return; } } } -/* Call all callbacks */ +/* Call all callbacks with safe iterator tracking to handle concurrent modifications */ static zend_always_inline void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception) { @@ -425,20 +462,41 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object } } - if (event->callbacks.data == NULL) { + if (event->callbacks.data == NULL || event->callbacks.length == 0) { return; } - // TODO: Consider the case when the callback is removed during iteration! - - const zend_async_callbacks_vector_t *vector = &event->callbacks; + zend_async_callbacks_vector_t *vector = &event->callbacks; + uint32_t current_index = 0; + + // Register iterator - prevents concurrent iterations + if (!zend_async_callbacks_register_iterator(vector, ¤t_index)) { + zend_error(E_CORE_WARNING, + "Concurrent callback iteration detected - nested notify() calls are not allowed" + ); + return; + } - for (uint32_t i = 0; i < vector->length; ++i) { - vector->data[i]->callback(event, vector->data[i], result, exception); + // Iterate through callbacks with safe index tracking + while (current_index < vector->length) { + // Store current callback (it might be moved during execution) + zend_async_event_callback_t *callback = vector->data[current_index]; + + // Execute callback + callback->callback(event, callback, result, exception); + if (UNEXPECTED(EG(exception) != NULL)) { break; } + + // Move to next callback + // Note: current_index may have been adjusted by zend_async_callbacks_adjust_iterator + // if a callback was removed during this iteration + current_index++; } + + // Unregister iterator + zend_async_callbacks_unregister_iterator(vector); } /* Call all callbacks and close the event (Like future) */ @@ -461,9 +519,11 @@ zend_async_callbacks_free(zend_async_event_t *event) efree(event->callbacks.data); } - event->callbacks.data = NULL; - event->callbacks.length = 0; - event->callbacks.capacity = 0; + // Reset all fields + event->callbacks.data = NULL; + event->callbacks.length = 0; + event->callbacks.capacity = 0; + event->callbacks.current_iterator = NULL; } struct _zend_async_poll_event_s { From 43d48eb8a260a5611441ee8e51e2ba5fad97053d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 7 Jun 2025 12:07:41 +0300 Subject: [PATCH 042/137] * fix bugs with callbacks vector iterator --- Zend/zend_async_API.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index aea36f348ca2d..a94bbd21aeb16 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -433,19 +433,19 @@ zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t /* Remove a specific callback; order is NOT preserved, but iterator is safely adjusted */ static zend_always_inline void -zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_callback_t *callback) +zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback_t *callback) { zend_async_callbacks_vector_t *vector = &event->callbacks; for (uint32_t i = 0; i < vector->length; ++i) { if (vector->data[i] == callback) { - callback->dispose(vector->data[i], event); // Adjust the active iterator before performing the removal zend_async_callbacks_adjust_iterator(vector, i); // O(1) removal: move last element to current position vector->data[i] = vector->length > 0 ? vector->data[--vector->length] : NULL; + callback->dispose(callback, event); return; } } @@ -481,18 +481,18 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object while (current_index < vector->length) { // Store current callback (it might be moved during execution) zend_async_event_callback_t *callback = vector->data[current_index]; - + + // Move to next callback + // Note: current_index may have been adjusted by zend_async_callbacks_adjust_iterator + // if a callback was removed during this iteration + current_index++; + // Execute callback callback->callback(event, callback, result, exception); if (UNEXPECTED(EG(exception) != NULL)) { break; } - - // Move to next callback - // Note: current_index may have been adjusted by zend_async_callbacks_adjust_iterator - // if a callback was removed during this iteration - current_index++; } // Unregister iterator From 2fcd971e2f4de39c4f9cac7505cdd675008462ab Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 7 Jun 2025 12:24:02 +0300 Subject: [PATCH 043/137] % move vector functions to *.c --- Zend/zend_async_API.c | 131 ++++++++++++++++++++++++++++++++++++++++++ Zend/zend_async_API.h | 128 ++--------------------------------------- 2 files changed, 136 insertions(+), 123 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index dd7c2032a569a..028c88d449581 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -928,3 +928,134 @@ void zend_async_coroutine_internal_context_init(zend_coroutine_t *coroutine) ////////////////////////////////////////////////////////////////////// /* Internal Context API end */ ////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// +/* Callback Vector Implementation */ +////////////////////////////////////////////////////////////////////// + +/* Private helper functions for iterator management - static inline for performance */ + +/* Register the single iterator - prevents concurrent iterations */ +static zend_always_inline bool +zend_async_callbacks_register_iterator(zend_async_callbacks_vector_t *vector, uint32_t *iterator_index) +{ + if (vector->current_iterator != NULL) { + // Concurrent iteration detected - this is not allowed + return false; + } + + vector->current_iterator = iterator_index; + return true; +} + +/* Unregister the iterator after iteration completes */ +static zend_always_inline void +zend_async_callbacks_unregister_iterator(zend_async_callbacks_vector_t *vector) +{ + vector->current_iterator = NULL; +} + +/* Adjust the active iterator when an element is removed */ +static zend_always_inline void +zend_async_callbacks_adjust_iterator(zend_async_callbacks_vector_t *vector, uint32_t removed_index) +{ + if (vector->current_iterator != NULL && *vector->current_iterator > removed_index) { + (*vector->current_iterator)--; + } +} + +/* Public API implementations */ + +/* Remove a specific callback; order is NOT preserved, but iterator is safely adjusted */ +ZEND_API void +zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_callback_t *callback) +{ + zend_async_callbacks_vector_t *vector = &event->callbacks; + + for (uint32_t i = 0; i < vector->length; ++i) { + if (vector->data[i] == callback) { + // Adjust the active iterator before performing the removal + zend_async_callbacks_adjust_iterator(vector, i); + + // O(1) removal: move last element to current position + vector->data[i] = vector->length > 0 ? vector->data[--vector->length] : NULL; + callback->dispose(callback, event); + return; + } + } +} + +/* Call all callbacks with safe iterator tracking to handle concurrent modifications */ +ZEND_API void +zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception) +{ + // If pre-notify returns false, we stop notifying callbacks + if (event->before_notify != NULL) { + if (false == event->before_notify(event, &result, &exception)) { + return; + } + } + + if (event->callbacks.data == NULL || event->callbacks.length == 0) { + return; + } + + zend_async_callbacks_vector_t *vector = &event->callbacks; + uint32_t current_index = 0; + + // Register iterator - prevents concurrent iterations + if (!zend_async_callbacks_register_iterator(vector, ¤t_index)) { + zend_error(E_CORE_WARNING, + "Concurrent callback iteration detected - nested notify() calls are not allowed" + ); + return; + } + + // Iterate through callbacks with safe index tracking + while (current_index < vector->length) { + // Store current callback (it might be moved during execution) + zend_async_event_callback_t *callback = vector->data[current_index]; + + // Move to next callback + // Note: current_index may have been adjusted by zend_async_callbacks_adjust_iterator + // if a callback was removed during this iteration + current_index++; + + // Execute callback + callback->callback(event, callback, result, exception); + + if (UNEXPECTED(EG(exception) != NULL)) { + break; + } + } + + // Unregister iterator + zend_async_callbacks_unregister_iterator(vector); +} + +/* Call all callbacks and close the event (Like future) */ +ZEND_API void +zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception) +{ + ZEND_ASYNC_EVENT_SET_CLOSED(event); + zend_async_callbacks_notify(event, result, exception); +} + +/* Free the vector's memory including iterator tracking */ +ZEND_API void +zend_async_callbacks_free(zend_async_event_t *event) +{ + if (event->callbacks.data != NULL) { + for (uint32_t i = 0; i < event->callbacks.length; ++i) { + event->callbacks.data[i]->dispose(event->callbacks.data[i], event); + } + + efree(event->callbacks.data); + } + + // Reset all fields + event->callbacks.data = NULL; + event->callbacks.length = 0; + event->callbacks.capacity = 0; + event->callbacks.current_iterator = NULL; +} diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index a94bbd21aeb16..a8f202f6a692c 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -380,34 +380,11 @@ struct _zend_async_event_s { } \ } while (0) -/* Register the single iterator - prevents concurrent iterations */ -static zend_always_inline bool -zend_async_callbacks_register_iterator(zend_async_callbacks_vector_t *vector, uint32_t *iterator_index) -{ - if (vector->current_iterator != NULL) { - // Concurrent iteration detected - this is not allowed - return false; - } - - vector->current_iterator = iterator_index; - return true; -} - -/* Unregister the iterator after iteration completes */ -static zend_always_inline void -zend_async_callbacks_unregister_iterator(zend_async_callbacks_vector_t *vector) -{ - vector->current_iterator = NULL; -} - -/* Adjust the active iterator when an element is removed */ -static zend_always_inline void -zend_async_callbacks_adjust_iterator(zend_async_callbacks_vector_t *vector, uint32_t removed_index) -{ - if (vector->current_iterator != NULL && *vector->current_iterator > removed_index) { - (*vector->current_iterator)--; - } -} +/* Public callback vector functions - implementations in zend_async_API.c */ +ZEND_API void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception); +ZEND_API void zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_callback_t *callback); +ZEND_API void zend_async_callbacks_free(zend_async_event_t *event); +ZEND_API void zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception); /* Append a callback; grows the buffer when needed */ static zend_always_inline void @@ -431,101 +408,6 @@ zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t vector->data[vector->length++] = callback; } -/* Remove a specific callback; order is NOT preserved, but iterator is safely adjusted */ -static zend_always_inline void -zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback_t *callback) -{ - zend_async_callbacks_vector_t *vector = &event->callbacks; - - for (uint32_t i = 0; i < vector->length; ++i) { - if (vector->data[i] == callback) { - - // Adjust the active iterator before performing the removal - zend_async_callbacks_adjust_iterator(vector, i); - - // O(1) removal: move last element to current position - vector->data[i] = vector->length > 0 ? vector->data[--vector->length] : NULL; - callback->dispose(callback, event); - return; - } - } -} - -/* Call all callbacks with safe iterator tracking to handle concurrent modifications */ -static zend_always_inline void -zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception) -{ - // If pre-notify returns false, we stop notifying callbacks - if (event->before_notify != NULL) { - if (false == event->before_notify(event, &result, &exception)) { - return; - } - } - - if (event->callbacks.data == NULL || event->callbacks.length == 0) { - return; - } - - zend_async_callbacks_vector_t *vector = &event->callbacks; - uint32_t current_index = 0; - - // Register iterator - prevents concurrent iterations - if (!zend_async_callbacks_register_iterator(vector, ¤t_index)) { - zend_error(E_CORE_WARNING, - "Concurrent callback iteration detected - nested notify() calls are not allowed" - ); - return; - } - - // Iterate through callbacks with safe index tracking - while (current_index < vector->length) { - // Store current callback (it might be moved during execution) - zend_async_event_callback_t *callback = vector->data[current_index]; - - // Move to next callback - // Note: current_index may have been adjusted by zend_async_callbacks_adjust_iterator - // if a callback was removed during this iteration - current_index++; - - // Execute callback - callback->callback(event, callback, result, exception); - - if (UNEXPECTED(EG(exception) != NULL)) { - break; - } - } - - // Unregister iterator - zend_async_callbacks_unregister_iterator(vector); -} - -/* Call all callbacks and close the event (Like future) */ -static zend_always_inline void -zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception) -{ - ZEND_ASYNC_EVENT_SET_CLOSED(event); - zend_async_callbacks_notify(event, result, exception); -} - -/* Free the vector’s memory */ -static zend_always_inline void -zend_async_callbacks_free(zend_async_event_t *event) -{ - if (event->callbacks.data != NULL) { - for (uint32_t i = 0; i < event->callbacks.length; ++i) { - event->callbacks.data[i]->dispose(event->callbacks.data[i], event); - } - - efree(event->callbacks.data); - } - - // Reset all fields - event->callbacks.data = NULL; - event->callbacks.length = 0; - event->callbacks.capacity = 0; - event->callbacks.current_iterator = NULL; -} - struct _zend_async_poll_event_s { zend_async_event_t base; bool is_socket; From d4d0d8bdec939abdce2e899efd4f567c3fc45253 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 7 Jun 2025 12:45:39 +0300 Subject: [PATCH 044/137] * fix bugs with events callbacks vector --- Zend/zend_async_API.c | 40 ++++++++++++++++++++++++++++++---------- Zend/zend_async_API.h | 2 +- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 028c88d449581..7673433da50a6 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -968,7 +968,7 @@ zend_async_callbacks_adjust_iterator(zend_async_callbacks_vector_t *vector, uint /* Remove a specific callback; order is NOT preserved, but iterator is safely adjusted */ ZEND_API void -zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_callback_t *callback) +zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback_t *callback) { zend_async_callbacks_vector_t *vector = &event->callbacks; @@ -1045,17 +1045,37 @@ zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, z ZEND_API void zend_async_callbacks_free(zend_async_event_t *event) { - if (event->callbacks.data != NULL) { - for (uint32_t i = 0; i < event->callbacks.length; ++i) { - event->callbacks.data[i]->dispose(event->callbacks.data[i], event); - } + if (event->callbacks.data == NULL) { + return; + } - efree(event->callbacks.data); + zend_async_callbacks_vector_t *vector = &event->callbacks; + uint32_t current_index = 0; + + // Register iterator - prevents concurrent iterations + if (!zend_async_callbacks_register_iterator(vector, ¤t_index)) { + zend_error(E_CORE_WARNING, + "Concurrent callback iteration detected - nested free() calls are not allowed" + ); + return; } + // Dispose all callbacks + while (current_index < vector->length) { + zend_async_event_callback_t *callback = vector->data[current_index]; + current_index++; + callback->dispose(callback, event); + } + + // Free memory + efree(vector->data); + + // Unregister iterator + zend_async_callbacks_unregister_iterator(vector); + // Reset all fields - event->callbacks.data = NULL; - event->callbacks.length = 0; - event->callbacks.capacity = 0; - event->callbacks.current_iterator = NULL; + vector->data = NULL; + vector->length = 0; + vector->capacity = 0; + vector->current_iterator = NULL; } diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index a8f202f6a692c..59d35678e65fb 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -382,7 +382,7 @@ struct _zend_async_event_s { /* Public callback vector functions - implementations in zend_async_API.c */ ZEND_API void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception); -ZEND_API void zend_async_callbacks_remove(zend_async_event_t *event, const zend_async_event_callback_t *callback); +ZEND_API void zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback_t *callback); ZEND_API void zend_async_callbacks_free(zend_async_event_t *event); ZEND_API void zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception); From 9eb5688d7983e88ceaac0108655edc2fab9210ac Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 7 Jun 2025 15:29:48 +0300 Subject: [PATCH 045/137] * fix waker_events_dtor --- Zend/zend_async_API.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 7673433da50a6..26369e083300e 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -395,6 +395,12 @@ static void waker_events_dtor(zval *item) trigger->event->del_callback(trigger->event, trigger->callback); + // + // At this point, we explicitly stop the event because it is no longer being listened to by our handlers. + // However, this does not mean the object is destroyed—it may remain in memory if something still holds a reference to it. + // + trigger->event->stop(trigger->event); + ZEND_ASYNC_EVENT_RELEASE(trigger->event); efree(trigger); From a234daa0bcb6934c832fa98211dba01c7af77648 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:31:32 +0300 Subject: [PATCH 046/137] * fix for waker_events_dtor --- Zend/zend_async_API.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 26369e083300e..fb48e35fe3570 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -392,16 +392,20 @@ ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_event_new( static void waker_events_dtor(zval *item) { zend_async_waker_trigger_t * trigger = Z_PTR_P(item); + zend_async_event_t *event = trigger->event; + trigger->event = NULL; - trigger->event->del_callback(trigger->event, trigger->callback); + printf("waker_events_dtor event %p\n", trigger); - // - // At this point, we explicitly stop the event because it is no longer being listened to by our handlers. - // However, this does not mean the object is destroyed—it may remain in memory if something still holds a reference to it. - // - trigger->event->stop(trigger->event); - - ZEND_ASYNC_EVENT_RELEASE(trigger->event); + if (event != NULL) { + event->del_callback(event, trigger->callback); + // + // At this point, we explicitly stop the event because it is no longer being listened to by our handlers. + // However, this does not mean the object is destroyed—it may remain in memory if something still holds a reference to it. + // + event->stop(event); + ZEND_ASYNC_EVENT_RELEASE(event); + } efree(trigger); } From e08a5b526d7feb37e9bbff27707713063ffc97d6 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:51:00 +0300 Subject: [PATCH 047/137] * fix coroutine_event_callback_dispose issue double free --- Zend/zend_async_API.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index fb48e35fe3570..5597873ccf4cb 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -395,8 +395,6 @@ static void waker_events_dtor(zval *item) zend_async_event_t *event = trigger->event; trigger->event = NULL; - printf("waker_events_dtor event %p\n", trigger); - if (event != NULL) { event->del_callback(event, trigger->callback); // @@ -506,6 +504,11 @@ static void coroutine_event_callback_dispose(zend_async_event_callback_t *callba // If the callback is still referenced, we cannot dispose it yet callback->ref_count--; return; + } else if (UNEXPECTED(callback->ref_count == 0)) { + // Circular free from destructor + return; + } else { + ZEND_ASSERT(callback->ref_count > 0 && "Callback ref_count must be greater than 0. Memory corruption detected."); } callback->ref_count = 0; @@ -575,6 +578,12 @@ ZEND_API void zend_async_resume_when( event_callback->base.dispose = coroutine_event_callback_dispose; } + if (event_callback->base.ref_count == 0) { + event_callback->base.ref_count = 1; + } + + ZEND_ASSERT(event_callback->base.ref_count > 0 && "Callback ref_count must be greater than 0."); + event_callback->coroutine = coroutine; event->add_callback(event, &event_callback->base); From 63d43e55afda9495349b2960d22c684882f7ff52 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 8 Jun 2025 16:58:47 +0300 Subject: [PATCH 048/137] =?UTF-8?q?%=20Event=20references=20have=20been=20?= =?UTF-8?q?added=20=E2=80=94=20a=20mechanism=20that=20allows=20separating?= =?UTF-8?q?=20events=20from=20Zend=20objects.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Zend/zend_async_API.h | 56 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 59d35678e65fb..07249217b2e14 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -310,6 +310,31 @@ struct _zend_async_event_s { zend_async_event_info_t info; }; +/** + * Event reference. A special data structure that allows representing an object with the Awaitable interface, + * but which does not store the event directly—instead, it holds only a reference to it. + * This is necessary for events that are destroyed asynchronously and therefore cannot be used as Zend objects. + * + * For example, events like Timer, Poll, and Signal cannot be Zend objects + * because their destruction cycle does not align. + * + * * flags should always be equal to ZEND_ASYNC_EVENT_REFERENCE_PREFIX. + * * zend_object_offset is the offset of the Zend object structure. + * * event is a pointer to the zend_async_event_t structure. + */ +#define ZEND_ASYNC_EVENT_REF_PROLOG \ + uint32_t flags; \ + uint32_t zend_object_offset; + +#define ZEND_ASYNC_EVENT_REF_FIELDS \ + uint32_t flags; \ + uint32_t zend_object_offset; \ + zend_async_event_t *event; + +typedef struct { + ZEND_ASYNC_EVENT_REF_FIELDS +} zend_async_event_ref_t; + #define ZEND_ASYNC_EVENT_F_CLOSED (1u << 0) /* event was closed */ #define ZEND_ASYNC_EVENT_F_RESULT_USED (1u << 1) /* result will be used in exception handler */ #define ZEND_ASYNC_EVENT_F_EXC_CAUGHT (1u << 2) /* error was caught in exception handler */ @@ -319,6 +344,20 @@ struct _zend_async_event_s { #define ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY (1u << 5) /* event will not free memory in dispose handler */ #define ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED (1u << 6) /* exception has been caught and processed */ +#define ZEND_ASYNC_EVENT_F_REFERENCE (1u << 7) /* event is a reference structure */ + +// Flag indicating that the event has a zend_object reference by extra_offset. +#define ZEND_ASYNC_EVENT_F_OBJ_REF (1u << 8) /* has zend_object ref */ + +#define ZEND_ASYNC_EVENT_REFERENCE_PREFIX ((uint32_t)0x80) /* prefix for reference structures */ + +// Create a reference to an event with the given offset and event pointer. +#define ZEND_ASYNC_EVENT_REF_SET(ptr, offset, ev) do { \ + (ptr)->flags = ZEND_ASYNC_EVENT_REFERENCE_PREFIX; \ + (ptr)->zend_object_offset = (offset); \ + (ptr)->event = (ev); \ + } while (0) + #define ZEND_ASYNC_EVENT_IS_CLOSED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) #define ZEND_ASYNC_EVENT_WILL_RESULT_USED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_RESULT_USED) != 0) #define ZEND_ASYNC_EVENT_WILL_EXC_CAUGHT(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_EXC_CAUGHT) != 0) @@ -347,9 +386,22 @@ struct _zend_async_event_s { #define ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) #define ZEND_ASYNC_EVENT_CLR_EXCEPTION_HANDLED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) +#define ZEND_ASYNC_EVENT_WITH_OBJECT_REF(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_OBJ_REF) + // Convert awaitable Zend object to zend_async_event_t pointer -#define ZEND_ASYNC_OBJECT_TO_EVENT(obj) ((zend_async_event_t *)((char *)(obj) - (obj)->handlers->offset)) -#define ZEND_ASYNC_EVENT_TO_OBJECT(event) ((zend_object *)((char *)(event) + (event)->zend_object_offset)) +#define ZEND_ASYNC_EVENT_IS_REFERENCE(ptr) (*((const uint32_t *)(ptr)) == ZEND_ASYNC_EVENT_REFERENCE_PREFIX) +#define ZEND_ASYNC_OBJECT_TO_EVENT(obj) \ + ( \ + ZEND_ASYNC_EVENT_IS_REFERENCE((void *)((char *)(obj) - (obj)->handlers->offset)) \ + ? ((zend_async_event_ref_t *)((char *)(obj) - (obj)->handlers->offset))->event \ + : (zend_async_event_t *)((char *)(obj) - (obj)->handlers->offset) \ + ) + +// Convert zend_async_event_t to zend_object pointer +#define ZEND_ASYNC_EVENT_TO_OBJECT(ev) \ + (((ev)->flags & ZEND_ASYNC_EVENT_F_OBJ_REF) \ + ? *(zend_object **)((char *)(ev) + (ev)->extra_offset) \ + : (zend_object *)((char *)(ev) + (ev)->zend_object_offset) ) // Get refcount of the event object #define ZEND_ASYNC_EVENT_REF(ev) (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? \ From 21681023cabff329d96c761b3a9f0508deb775d9 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 8 Jun 2025 21:58:59 +0300 Subject: [PATCH 049/137] % refactoring zend_async_callbacks_notify --- Zend/zend_async_API.c | 11 +++++------ Zend/zend_async_API.h | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 5597873ccf4cb..d6f844e669f39 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -1006,13 +1006,12 @@ zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback /* Call all callbacks with safe iterator tracking to handle concurrent modifications */ ZEND_API void -zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception) +zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception, bool from_handler) { // If pre-notify returns false, we stop notifying callbacks - if (event->before_notify != NULL) { - if (false == event->before_notify(event, &result, &exception)) { - return; - } + if (false == from_handler && event->notify_handler != NULL) { + event->notify_handler(event, result, exception); + return; } if (event->callbacks.data == NULL || event->callbacks.length == 0) { @@ -1057,7 +1056,7 @@ ZEND_API void zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception) { ZEND_ASYNC_EVENT_SET_CLOSED(event); - zend_async_callbacks_notify(event, result, exception); + zend_async_callbacks_notify(event, result, exception, false); } /* Free the vector's memory including iterator tracking */ diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 07249217b2e14..0c0b9e1878e0a 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -135,7 +135,7 @@ typedef void (*zend_async_event_callback_fn) typedef void (*zend_async_event_callback_dispose_fn)(zend_async_event_callback_t *callback, zend_async_event_t * event); typedef void (*zend_async_event_add_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); typedef void (*zend_async_event_del_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); -typedef bool (*zend_async_event_callbacks_notify_t)(zend_async_event_t *event, void **result, zend_object **exception); +typedef void (*zend_async_event_callbacks_notify_t)(zend_async_event_t *event, void *result, zend_object *exception); typedef void (*zend_async_event_start_t) (zend_async_event_t *event); typedef void (*zend_async_event_stop_t) (zend_async_event_t *event); typedef void (*zend_async_event_dispose_t) (zend_async_event_t *event); @@ -302,7 +302,7 @@ struct _zend_async_event_s { * Handler that is invoked before all event listeners are notified. * May be NULL. */ - zend_async_event_callbacks_notify_t before_notify; + zend_async_event_callbacks_notify_t notify_handler; zend_async_event_start_t start; zend_async_event_stop_t stop; zend_async_event_dispose_t dispose; @@ -433,11 +433,21 @@ typedef struct { } while (0) /* Public callback vector functions - implementations in zend_async_API.c */ -ZEND_API void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception); +ZEND_API void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception, bool from_handler); ZEND_API void zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback_t *callback); ZEND_API void zend_async_callbacks_free(zend_async_event_t *event); ZEND_API void zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception); +#define ZEND_ASYNC_CALLBACKS_NOTIFY(event, result, exception) \ + zend_async_callbacks_notify((event), (result), (exception), false) + +#define ZEND_ASYNC_CALLBACKS_NOTIFY_AND_CLOSE(event, result, exception) \ + zend_async_callbacks_notify_and_close((event), (result), (exception)) + +#define ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER(event, result, exception) \ + zend_async_callbacks_notify((event), (result), (exception), true) + + /* Append a callback; grows the buffer when needed */ static zend_always_inline void zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t *callback) From 5991d08eaf9519238434ae66b6a5701f0f4f0481 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 8 Jun 2025 23:36:58 +0300 Subject: [PATCH 050/137] + update docs --- docs/source/true_async_api/events.rst | 303 ++++++++++++++++++++++++++ docs/source/true_async_api/index.rst | 1 + 2 files changed, 304 insertions(+) create mode 100644 docs/source/true_async_api/events.rst diff --git a/docs/source/true_async_api/events.rst b/docs/source/true_async_api/events.rst new file mode 100644 index 0000000000000..3ccce430bc098 --- /dev/null +++ b/docs/source/true_async_api/events.rst @@ -0,0 +1,303 @@ +################ + Events +################ + +The TrueAsync API revolves around the concept of **events**. An event represents any asynchronous action that can produce a result or error in the future. All specific event types share a common base structure ``zend_async_event_t`` which provides memory management, state flags and a standard lifecycle. + +********************** +Creating an Event +********************** + +To create an event you typically call one of the ``ZEND_ASYNC_NEW_*`` macros which delegate to the underlying implementation. Each macro returns a structure that contains ``zend_async_event_t`` as its first member. + +.. code:: c + + zend_async_timer_event_t *timer = ZEND_ASYNC_NEW_TIMER_EVENT(1000, false); + timer->base.start(&timer->base); + +The ``base`` field exposes the common functions ``start``, ``stop`` and ``dispose`` as well as reference counting helpers. + +***************** +Event Flags +***************** + +Each event has a ``flags`` field that controls its state and behaviour. The most +important flags are: + +``ZEND_ASYNC_EVENT_F_CLOSED`` + The event has been closed and may no longer be started or stopped. + +``ZEND_ASYNC_EVENT_F_RESULT_USED`` + Indicates that the result will be consumed by an awaiting context. When this + flag is set the awaiting code keeps the result alive until it is processed + in the exception handler. + +``ZEND_ASYNC_EVENT_F_EXC_CAUGHT`` + Marks that an error produced by the event was caught in a callback and will + not be rethrown automatically. + +``ZEND_ASYNC_EVENT_F_ZVAL_RESULT`` + Signals that the callback result is a ``zval`` pointer. The TrueAsync core + will properly increment the reference count before inserting the value into + userland arrays. + +``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` + Specifies that the structure also acts as a Zend object implementing + ``Awaitable``. + +``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` + The dispose handler must not free the memory of the event itself. This is + used when the event is embedded into another structure. + +``ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED`` + Set by the callback once it has fully processed an exception. If this flag + is not set the exception will be rethrown after callbacks finish. + +``ZEND_ASYNC_EVENT_F_REFERENCE`` + Indicates that this structure is only an event reference which stores a + pointer to the real event. + +``ZEND_ASYNC_EVENT_F_OBJ_REF`` + Marks that the event keeps a pointer to the Zend object in its + ``extra_offset`` region so reference counting works through the object. + +Convenience macros such as ``ZEND_ASYNC_EVENT_SET_CLOSED`` and +``ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED`` are provided to manipulate these +flags. + +****************** +Event Callbacks +****************** + +The core maintains a dynamic vector of callbacks for each event. Implementations +provide the ``add_callback`` and ``del_callback`` methods which internally use +``zend_async_callbacks_push`` and ``zend_async_callbacks_remove``. The +``zend_async_callbacks_notify`` helper iterates over all registered callbacks +and passes the result or exception. If a ``notify_handler`` is set on the event +it is invoked first and can adjust the result or exception as needed. The +handler is expected to call ``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` to +forward the values to all listeners. The ``php-async`` extension provides +convenience macros ``ZEND_ASYNC_CALLBACKS_NOTIFY`` for regular use, +``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` when called from inside a +``notify_handler``, and ``ZEND_ASYNC_CALLBACKS_NOTIFY_AND_CLOSE`` to close the +event before notifying listeners. + +The following example shows a libuv poll event that dispatches its callbacks +once the underlying handle becomes readable: + +.. code:: c + + static void on_poll_event(uv_poll_t *handle, int status, int events) { + async_poll_event_t *poll = handle->data; + zend_object *exception = NULL; + + if (status < 0) { + exception = async_new_exception( + async_ce_input_output_exception, + "Input output error: %s", + uv_strerror(status) + ); + } + + poll->event.triggered_events = events; + + ZEND_ASYNC_CALLBACKS_NOTIFY(&poll->event.base, NULL, exception); + + if (exception != NULL) { + zend_object_release(exception); + } + } + +************************* +Event Coroutines +************************* + +Coroutines themselves are implemented as events using the ``zend_coroutine_t`` structure. When a coroutine yields, its waker waits on multiple events and resumes the coroutine once any of them triggers. + +.. code:: c + + // Spawn a coroutine that waits for a timer + zend_coroutine_t *co = ZEND_ASYNC_SPAWN(NULL); + zend_async_resume_when(co, &timer->base, false, zend_async_waker_callback_resolve, NULL); + zend_async_enqueue_coroutine(co); + +When the coroutine finishes execution the event triggers again to deliver the +result or exception. The coroutine implementation marks the callback result as a +``zval`` value using ``ZEND_ASYNC_EVENT_SET_ZVAL_RESULT``. Callback handlers may +also set ``ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED`` to indicate that the thrown +exception has been processed and should not be rethrown by the runtime. + +**************************** +Extending Events +**************************** + +Custom event types embed ``zend_async_event_t`` at the beginning of their +structure and may allocate additional memory beyond the end of the struct. +The ``extra_size`` argument in ``ZEND_ASYNC_NEW_*_EX`` controls how much extra +space is reserved, and ``extra_offset`` records where that region begins. + +.. code:: c + + // Allocate extra space for user data + zend_async_poll_event_t *poll = ZEND_ASYNC_NEW_POLL_EVENT_EX(fd, false, sizeof(my_data_t)); + my_data_t *data = (my_data_t *)((char*)poll + poll->base.extra_offset); + +The libuv backend defines event wrappers that embed libuv handles. A timer +event, for example, extends ``zend_async_timer_event_t`` as follows: + +.. code:: c + + typedef struct { + zend_async_timer_event_t event; + uv_timer_t uv_handle; + } async_timer_event_t; + + // Initialize callbacks for the event + event->event.base.add_callback = libuv_add_callback; + event->event.base.del_callback = libuv_remove_callback; + event->event.base.start = libuv_timer_start; + event->event.base.stop = libuv_timer_stop; + event->event.base.dispose = libuv_timer_dispose; + +Every extended event defines its own ``start``, ``stop`` and ``dispose`` +functions. The dispose handler must release all resources associated with +the event and is called when the reference count reaches ``1``. It is +common to stop the event first and then close the underlying libuv handle so +that memory gets freed in the ``uv_close`` callback. + +.. code:: c + + static void libuv_timer_dispose(zend_async_event_t *event) + { + if (ZEND_ASYNC_EVENT_REF(event) > 1) { + ZEND_ASYNC_EVENT_DEL_REF(event); + return; + } + + if (event->loop_ref_count > 0) { + event->loop_ref_count = 1; + event->stop(event); + } + + zend_async_callbacks_free(event); + + async_timer_event_t *timer = (async_timer_event_t *)event; + uv_close((uv_handle_t *)&timer->uv_handle, libuv_close_handle_cb); + } + +If ``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` is set the dispose handler must not +free the event memory itself because the structure is embedded in another +object (e.g. ``async_coroutine_t``). The libuv close callback will only free +the libuv handle in this case. + +*********************** +Custom Event Callbacks +*********************** + +Callbacks can also be extended to store additional state. The await logic in +``php-async`` defines a callback that inherits from +``zend_coroutine_event_callback_t`` and keeps a reference to the awaiting +context: + +.. code:: c + + typedef struct { + zend_coroutine_event_callback_t callback; + async_await_context_t *await_context; + zval key; + zend_async_event_callback_dispose_fn prev_dispose; + } async_await_callback_t; + + async_await_callback_t *cb = ecalloc(1, sizeof(async_await_callback_t)); + cb->callback.base.callback = async_waiting_callback; + cb->await_context = ctx; + zend_async_resume_when(co, awaitable, false, NULL, &cb->callback); + +*********************** +Events as Zend Objects +*********************** + +If ``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` is set, the event also acts as a Zend object implementing ``Awaitable``. The ``zend_object_offset`` field stores the location of the ``zend_object`` within the structure. Reference counting macros automatically use either the internal counter or ``GC_REFCOUNT`` depending on this flag. + +This allows events to be exposed to userland seamlessly while keeping the internal lifecycle consistent. + +For events that are destroyed asynchronously (e.g. libuv timers) the actual +event structure cannot be a Zend object. Instead a lightweight reference +structure is used. ``ZEND_ASYNC_EVENT_REF_PROLOG`` reserves the required fields +in the Zend object and ``ZEND_ASYNC_EVENT_REF_SET`` stores the pointer to the +real event together with the ``zend_object`` offset. The event must then be +flagged with ``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` so that reference counting +delegates to the object. + +When accessing the event from userland objects use +``ZEND_ASYNC_OBJECT_TO_EVENT`` and ``ZEND_ASYNC_EVENT_TO_OBJECT`` which handle +both direct and reference-based layouts transparently. + +The ``php-async`` extension provides ``Async\\Timeout`` objects that embed a +timer event. Recent updates introduce a helper API for linking an event with a +Zend object. A helper macro ``ZEND_ASYNC_EVENT_REF_PROLOG`` reserves fields at +the beginning of the object to hold an event reference. ``ZEND_ASYNC_EVENT_REF_SET`` +stores the pointer to the newly created event and the offset of the ``zend_object`` +inside the structure. ``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` then marks the event +so reference counting will use the Zend object rather than the internal counter. + +The object factory now uses these helpers when creating the timer:: + + static zend_object *async_timeout_create(const zend_ulong ms, const bool is_periodic) + { + async_timeout_object_t *object = + ecalloc(1, sizeof(async_timeout_object_t) + + zend_object_properties_size(async_ce_timeout)); + + zend_async_event_t *event = (zend_async_event_t *) ZEND_ASYNC_NEW_TIMER_EVENT_EX( + ms, is_periodic, sizeof(async_timeout_ext_t) + ); + + if (UNEXPECTED(event == NULL)) { + efree(object); + return NULL; + } + + ZEND_ASYNC_EVENT_REF_SET(object, XtOffsetOf(async_timeout_object_t, std), (zend_async_timer_event_t *) event); + ZEND_ASYNC_EVENT_WITH_OBJECT_REF(event); + + async_timeout_ext_t *timeout = ASYNC_TIMEOUT_FROM_EVENT(event); + timeout->std = &object->std; + timeout->prev_dispose = event->dispose; + event->notify_handler = timeout_before_notify_handler; + event->dispose = async_timeout_event_dispose; + + zend_object_std_init(&object->std, async_ce_timeout); + object_properties_init(&object->std, async_ce_timeout); + object->std.handlers = &async_timeout_handlers; + + return &object->std; + } + + static void timeout_before_notify_handler(zend_async_event_t *event, + void *result, + zend_object *exception) + { + if (UNEXPECTED(exception != NULL)) { + ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER(event, result, exception); + return; + } + + zend_object *timeout_exception = async_new_exception( + async_ce_timeout_exception, + "Timeout occurred after %lu milliseconds", + ((zend_async_timer_event_t *) event)->timeout + ); + + ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER(event, result, timeout_exception); + zend_object_release(timeout_exception); + } + +.. note:: + + Events must not be exposed as Zend objects if their memory is released + asynchronously. Zend assumes that object destruction happens entirely + during the ``zend_object_release`` call and cannot wait for callbacks such as + ``uv_close`` to free the underlying event. The ``Async\\Timeout`` class will + be redesigned to avoid this pattern. + diff --git a/docs/source/true_async_api/index.rst b/docs/source/true_async_api/index.rst index 76d9d417bdb71..8d39623cff7a5 100644 --- a/docs/source/true_async_api/index.rst +++ b/docs/source/true_async_api/index.rst @@ -6,6 +6,7 @@ :hidden: architecture + events api-reference implementation-guide patterns From 2fbd94096fcb535b99310cb71dd08d12205feea4 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:32:33 +0300 Subject: [PATCH 051/137] + A special method event.replay has been added, which can be used for events to retrieve their last result. This method is meaningful for coroutines and Future objects. --- Zend/zend_async_API.h | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 0c0b9e1878e0a..f039078a4f916 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -138,6 +138,17 @@ typedef void (*zend_async_event_del_callback_t)(zend_async_event_t *event, zend_ typedef void (*zend_async_event_callbacks_notify_t)(zend_async_event_t *event, void *result, zend_object *exception); typedef void (*zend_async_event_start_t) (zend_async_event_t *event); typedef void (*zend_async_event_stop_t) (zend_async_event_t *event); + +/** + * The replay method can be called in several modes: + * If the callback parameter is not NULL, it will be invoked synchronously and immediately. + * If callback is NULL, then the `result` and `exception` parameters will be filled in. + * + * The method will return true if the result was applied. + */ +typedef bool (*zend_async_event_replay_t) ( + zend_async_event_t *event, zend_async_event_callback_t *callback, zval *result, zend_object **exception +); typedef void (*zend_async_event_dispose_t) (zend_async_event_t *event); typedef zend_string* (*zend_async_event_info_t) (zend_async_event_t *event); @@ -298,16 +309,22 @@ struct _zend_async_event_s { /* Methods */ zend_async_event_add_callback_t add_callback; zend_async_event_del_callback_t del_callback; - /* - * Handler that is invoked before all event listeners are notified. - * May be NULL. - */ - zend_async_event_callbacks_notify_t notify_handler; zend_async_event_start_t start; zend_async_event_stop_t stop; + /* + * Replay method. Nullable. + * This method is implemented only by those events that can provide a result again, even after they have completed. + * For example, this method is relevant for coroutines and futures, which can provide the result again and again. + */ + zend_async_event_replay_t replay; zend_async_event_dispose_t dispose; /* Event info: can be NULL */ zend_async_event_info_t info; + /* + * Handler that is invoked before all event listeners are notified. + * May be NULL. + */ + zend_async_event_callbacks_notify_t notify_handler; }; /** @@ -432,6 +449,10 @@ typedef struct { } \ } while (0) +#define ZEND_ASYNC_EVENT_REPLAY(ev, callback) (ev->replay != NULL ? ev->replay(ev, callback, NULL, NULL) : false) +#define ZEND_ASYNC_EVENT_EXTRACT_RESULT(ev, result) (ev->replay != NULL ? ev->replay(ev, NULL, result, NULL) : false) +#define ZEND_ASYNC_EVENT_EXTRACT_RESULT_OR_ERROR(ev, result, exception) (ev->replay != NULL ? ev->replay(ev, NULL, result, exception) : false) + /* Public callback vector functions - implementations in zend_async_API.c */ ZEND_API void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception, bool from_handler); ZEND_API void zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback_t *callback); @@ -720,6 +741,9 @@ struct _zend_coroutine_s { /* Storage for return value. */ zval result; + /* Exception object, if any, nullable */ + zend_object *exception; + /* Coroutine context object */ zend_async_context_t *context; From cb8debd3334994ab2be16c8bffa34f975108b982 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:18:53 +0300 Subject: [PATCH 052/137] % refactoring zend_async_coroutine_callback_new --- Zend/zend_async_API.c | 2 +- Zend/zend_async_API.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index d6f844e669f39..02e94b892ad67 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -369,7 +369,7 @@ ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_ static void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event); -ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_event_new( +ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size ) { diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index f039078a4f916..e52e6979d3635 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1041,7 +1041,7 @@ ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_event_callback_fn callback, size_t size); -ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_event_new( +ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size ); From 01d3fdc945d90a562834f6ffb5c45a37a3173278 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 10 Jun 2025 22:35:43 +0300 Subject: [PATCH 053/137] + ZEND_ASYNC_EVENT_CALLBACK macrofunction --- Zend/zend_async_API.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index e52e6979d3635..c0a28a80304e2 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1041,6 +1041,9 @@ ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_event_callback_fn callback, size_t size); +#define ZEND_ASYNC_EVENT_CALLBACK(callback) zend_async_event_callback_new(callback, 0) +#define ZEND_ASYNC_EVENT_CALLBACK_EX(callback, size) zend_async_event_callback_new(callback, size) + ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size ); From a71012b5e0318ac17afb592a0f52c76aba48099a Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:54:01 +0300 Subject: [PATCH 054/137] + auto stop event for zend_async_callbacks_notify_and_close --- Zend/zend_async_API.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 02e94b892ad67..f208a56ed46b6 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -1055,6 +1055,7 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object ZEND_API void zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception) { + event->stop(event); ZEND_ASYNC_EVENT_SET_CLOSED(event); zend_async_callbacks_notify(event, result, exception, false); } From a8c2d42bdfd72b989c4351f9eb3fe438d6517d32 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:12:35 +0300 Subject: [PATCH 055/137] + Socket Listening API --- Zend/zend_async_API.c | 45 +++++++++++++++++++++++++++++++++++++++++++ Zend/zend_async_API.h | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index f208a56ed46b6..fe16299e6e6cb 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -1098,3 +1098,48 @@ zend_async_callbacks_free(zend_async_event_t *event) vector->capacity = 0; vector->current_iterator = NULL; } + +/////////////////////////////////////////////////////////////// +/// Socket Listening API Registration +/////////////////////////////////////////////////////////////// +/* Socket listening stubs */ +static zend_async_listen_event_t* socket_listen_stub(const char *host, int port, int backlog, size_t extra_size) +{ + ASYNC_THROW_ERROR("Socket listening API is not enabled"); + return NULL; +} + +/* Socket listening function pointers */ +ZEND_API zend_async_socket_listen_t zend_async_socket_listen_fn = socket_listen_stub; + +/* Registration lock for socket listening */ +static zend_atomic_bool socket_listening_lock = {0}; +static char *socket_listening_module_name = NULL; + +ZEND_API bool zend_async_socket_listening_register( + char *module, + bool allow_override, + zend_async_socket_listen_t socket_listen_fn +) +{ + if (zend_atomic_bool_exchange(&socket_listening_lock, 1)) { + return false; + } + + if (socket_listening_module_name == module) { + return true; + } + + if (socket_listening_module_name != NULL && false == allow_override) { + zend_error( + E_CORE_ERROR, "The module %s is trying to override Socket Listening API, which was registered by the module %s.", + module, socket_listening_module_name + ); + return false; + } + + socket_listening_module_name = module; + zend_async_socket_listen_fn = socket_listen_fn; + + return true; +} diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index c0a28a80304e2..e67606bf1d63e 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -165,6 +165,8 @@ typedef struct _zend_async_dns_addrinfo_s zend_async_dns_addrinfo_t; typedef struct _zend_async_exec_event_s zend_async_exec_event_t; +typedef struct _zend_async_listen_event_s zend_async_listen_event_t; + typedef struct _zend_async_task_s zend_async_task_t; /* Internal context typedefs removed - using direct functions */ @@ -223,6 +225,16 @@ typedef zend_async_exec_event_t* (*zend_async_new_exec_event_t) ( size_t extra_size ); +typedef zend_async_listen_event_t* (*zend_async_socket_listen_t)( + const char *host, int port, int backlog, size_t extra_size +); + +typedef int (*zend_async_listen_get_local_address_t)( + zend_async_listen_event_t *listen_event, + char *host, size_t host_len, + int *port +); + typedef int (* zend_async_exec_t) ( zend_async_exec_mode exec_mode, const char *cmd, @@ -561,6 +573,15 @@ struct _zend_async_exec_event_s { zval * std_error; }; +struct _zend_async_listen_event_s { + zend_async_event_t base; + const char *host; + int port; + int backlog; + zend_socket_t socket_fd; + zend_async_listen_get_local_address_t get_local_address; +}; + struct _zend_async_task_s { zend_async_event_t base; }; @@ -980,6 +1001,10 @@ ZEND_API extern zend_async_new_process_event_t zend_async_new_process_event_fn; ZEND_API extern zend_async_new_thread_event_t zend_async_new_thread_event_fn; ZEND_API extern zend_async_new_filesystem_event_t zend_async_new_filesystem_event_fn; +/* Socket Listening API */ + +ZEND_API extern zend_async_socket_listen_t zend_async_socket_listen_fn; + /* DNS API */ ZEND_API extern zend_async_getnameinfo_t zend_async_getnameinfo_fn; @@ -1037,6 +1062,12 @@ ZEND_API void zend_async_thread_pool_register( zend_string *module, bool allow_override, zend_async_queue_task_t queue_task_fn ); +ZEND_API bool zend_async_socket_listening_register( + char *module, + bool allow_override, + zend_async_socket_listen_t socket_listen_fn +); + ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char *zend_coroutine_name); ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_event_callback_fn callback, size_t size); @@ -1137,6 +1168,12 @@ END_EXTERN_C() #define ZEND_ASYNC_QUEUE_TASK(task) zend_async_queue_task_fn(task) +/* Socket Listening API Macros */ +#define ZEND_ASYNC_SOCKET_LISTEN(host, port, backlog) \ + zend_async_socket_listen_fn(host, port, backlog, 0) +#define ZEND_ASYNC_SOCKET_LISTEN_EX(host, port, backlog, extra_size) \ + zend_async_socket_listen_fn(host, port, backlog, extra_size) + /* Context API Macros */ #define ZEND_ASYNC_NEW_CONTEXT(parent) zend_async_new_context_fn(parent) #define ZEND_ASYNC_CURRENT_CONTEXT (ZEND_ASYNC_G(coroutine) != NULL ? ZEND_ASYNC_G(coroutine)->scope->context : NULL) From fc174c1bd27dafcd45bf9a4fab5f1b576670f731 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 14 Jun 2025 16:42:20 +0300 Subject: [PATCH 056/137] 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. --- Zend/zend_async_API.c | 201 ++++++++++++++++++++++++++++++++++++++++++ Zend/zend_async_API.h | 76 ++++++++++++++++ 2 files changed, 277 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index fe16299e6e6cb..692655d2553f6 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -26,6 +26,9 @@ zend_async_globals_t zend_async_globals_api = {0}; #define ASYNC_THROW_ERROR(error) zend_throw_error(NULL, error); +/* Forward declarations */ +static void zend_async_main_handlers_shutdown(void); + static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_provider) { ASYNC_THROW_ERROR("Async API is not enabled"); @@ -174,6 +177,7 @@ void zend_async_shutdown(void) { zend_async_globals_dtor(); zend_async_internal_context_api_shutdown(); + zend_async_main_handlers_shutdown(); } ZEND_API int zend_async_get_api_version_number(void) @@ -1143,3 +1147,200 @@ ZEND_API bool zend_async_socket_listening_register( return true; } + +/////////////////////////////////////////////////////////////// +/// Coroutine Switch Handlers Implementation +/////////////////////////////////////////////////////////////// + +ZEND_API void zend_coroutine_switch_handlers_init(zend_coroutine_t *coroutine) +{ + if (coroutine->switch_handlers) { + return; /* Already initialized */ + } + + coroutine->switch_handlers = emalloc(sizeof(zend_coroutine_switch_handlers_vector_t)); + coroutine->switch_handlers->length = 0; + coroutine->switch_handlers->capacity = 0; + coroutine->switch_handlers->data = NULL; + coroutine->switch_handlers->in_execution = false; +} + +ZEND_API void zend_coroutine_switch_handlers_destroy(zend_coroutine_t *coroutine) +{ + if (!coroutine->switch_handlers) { + return; + } + + if (coroutine->switch_handlers->data) { + efree(coroutine->switch_handlers->data); + } + efree(coroutine->switch_handlers); + coroutine->switch_handlers = NULL; +} + +ZEND_API uint32_t zend_coroutine_add_switch_handler( + zend_coroutine_t *coroutine, + zend_coroutine_switch_handler_fn handler, + void *user_data) +{ + if (!handler) { + zend_error(E_WARNING, "Cannot add NULL switch handler"); + return 0; + } + + if (!coroutine->switch_handlers) { + zend_coroutine_switch_handlers_init(coroutine); + } + + zend_coroutine_switch_handlers_vector_t *vector = coroutine->switch_handlers; + + if (vector->in_execution) { + zend_error(E_WARNING, "Cannot add switch handler during handler execution"); + return 0; + } + + /* Expand array if needed */ + if (vector->length == vector->capacity) { + vector->capacity = vector->capacity ? vector->capacity * 2 : 4; + vector->data = safe_erealloc(vector->data, + vector->capacity, + sizeof(zend_coroutine_switch_handler_t), + 0); + } + + /* Add handler */ + uint32_t index = vector->length; + vector->data[index].handler = handler; + vector->data[index].user_data = user_data; + vector->length++; + + return index; +} + +ZEND_API bool zend_coroutine_remove_switch_handler( + zend_coroutine_t *coroutine, + uint32_t handler_index) +{ + if (!coroutine->switch_handlers) { + return false; + } + + zend_coroutine_switch_handlers_vector_t *vector = coroutine->switch_handlers; + + if (vector->in_execution) { + zend_error(E_WARNING, "Cannot remove switch handler during handler execution"); + return false; + } + + if (handler_index >= vector->length) { + return false; + } + + /* Shift elements to remove the handler */ + for (uint32_t i = handler_index; i < vector->length - 1; i++) { + vector->data[i] = vector->data[i + 1]; + } + + vector->length--; + return true; +} + +ZEND_API void zend_coroutine_call_switch_handlers( + zend_coroutine_t *coroutine, + bool is_enter, + bool is_finishing) +{ + if (!coroutine->switch_handlers || coroutine->switch_handlers->length == 0) { + return; + } + + zend_coroutine_switch_handlers_vector_t *vector = coroutine->switch_handlers; + + /* Set execution protection flag */ + vector->in_execution = true; + + /* Call all handlers */ + for (uint32_t i = 0; i < vector->length; i++) { + vector->data[i].handler(coroutine, is_enter, is_finishing); + } + + /* Clear execution protection flag */ + vector->in_execution = false; +} + +/////////////////////////////////////////////////////////////// +/// Global Main Coroutine Switch Handlers Implementation +/////////////////////////////////////////////////////////////// + +static zend_coroutine_switch_handlers_vector_t global_main_coroutine_start_handlers = {0, 0, NULL, false}; + +ZEND_API void zend_async_add_main_coroutine_start_handler( + zend_coroutine_switch_handler_fn handler, + void *user_data) +{ + zend_coroutine_switch_handlers_vector_t *vector = &global_main_coroutine_start_handlers; + + /* Expand vector if needed */ + if (vector->length >= vector->capacity) { + uint32_t new_capacity = vector->capacity ? vector->capacity * 2 : 4; + vector->data = safe_perealloc(vector->data, new_capacity, sizeof(zend_coroutine_switch_handler_t), 0, 1); + vector->capacity = new_capacity; + } + + /* Add handler */ + vector->data[vector->length].handler = handler; + vector->data[vector->length].user_data = user_data; + vector->length++; +} + +ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *main_coroutine) +{ + zend_coroutine_switch_handlers_vector_t *vector = &global_main_coroutine_start_handlers; + + if (vector->length == 0 || vector->in_execution) { + return; + } + + /* Set execution protection flag */ + vector->in_execution = true; + + /* Call all handlers */ + for (uint32_t i = 0; i < vector->length; i++) { + vector->data[i].handler(main_coroutine, true, false); + } + + /* Clear execution protection flag */ + vector->in_execution = false; + + /* Copy handlers to main coroutine */ + if (main_coroutine->switch_handlers == NULL) { + main_coroutine->switch_handlers = safe_emalloc(1, sizeof(zend_coroutine_switch_handlers_vector_t), 0); + main_coroutine->switch_handlers->length = 0; + main_coroutine->switch_handlers->capacity = 0; + main_coroutine->switch_handlers->data = NULL; + main_coroutine->switch_handlers->in_execution = false; + } + + /* Copy all global handlers to main coroutine */ + for (uint32_t i = 0; i < vector->length; i++) { + zend_coroutine_add_switch_handler( + main_coroutine, + vector->data[i].handler, + vector->data[i].user_data + ); + } +} + +/* Global cleanup function - called during PHP shutdown */ +static void zend_async_main_handlers_shutdown(void) +{ + zend_coroutine_switch_handlers_vector_t *vector = &global_main_coroutine_start_handlers; + + if (vector->data != NULL) { + pefree(vector->data, 1); + vector->data = NULL; + vector->length = 0; + vector->capacity = 0; + vector->in_execution = false; + } +} diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index e67606bf1d63e..77da2c00fbbbe 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -121,6 +121,16 @@ typedef struct _zend_async_scope_s zend_async_scope_t; typedef struct _zend_fcall_s zend_fcall_t; typedef void (*zend_coroutine_entry_t)(void); +/* Coroutine Switch Handlers */ +typedef struct _zend_coroutine_switch_handler_s zend_coroutine_switch_handler_t; +typedef struct _zend_coroutine_switch_handlers_vector_s zend_coroutine_switch_handlers_vector_t; + +typedef void (*zend_coroutine_switch_handler_fn)( + zend_coroutine_t *coroutine, + bool is_enter, /* true = entering coroutine, false = leaving */ + bool is_finishing /* true = coroutine is finishing */ +); + typedef struct _zend_async_event_s zend_async_event_t; typedef struct _zend_async_event_callback_s zend_async_event_callback_t; typedef struct _zend_async_waker_trigger_s zend_async_waker_trigger_t; @@ -255,6 +265,22 @@ struct _zend_fcall_s { zend_fcall_info_cache fci_cache; }; +/////////////////////////////////////////////////////////////////// +/// Coroutine Switch Handlers Structures +/////////////////////////////////////////////////////////////////// + +struct _zend_coroutine_switch_handler_s { + zend_coroutine_switch_handler_fn handler; /* Handler function pointer */ + void *user_data; /* User data (can be NULL) */ +}; + +struct _zend_coroutine_switch_handlers_vector_s { + uint32_t length; /* Number of handlers */ + uint32_t capacity; /* Allocated capacity */ + zend_coroutine_switch_handler_t *data; /* Array of handlers */ + bool in_execution; /* Protection flag during execution */ +}; + struct _zend_async_microtask_s { zend_async_microtask_handler_t handler; zend_async_microtask_handler_t dtor; @@ -777,6 +803,9 @@ struct _zend_coroutine_s { /* Extended dispose handler */ zend_async_coroutine_dispose extended_dispose; + + /* Switch handlers for context switching */ + zend_coroutine_switch_handlers_vector_t *switch_handlers; }; /** @@ -1107,6 +1136,35 @@ ZEND_API void zend_async_waker_callback_timeout( zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception ); +/* Coroutine Switch Handlers API */ +ZEND_API uint32_t zend_coroutine_add_switch_handler( + zend_coroutine_t *coroutine, + zend_coroutine_switch_handler_fn handler, + void *user_data +); + +ZEND_API bool zend_coroutine_remove_switch_handler( + zend_coroutine_t *coroutine, + uint32_t handler_index +); + +ZEND_API void zend_coroutine_call_switch_handlers( + zend_coroutine_t *coroutine, + bool is_enter, + bool is_finishing +); + +ZEND_API void zend_coroutine_switch_handlers_init(zend_coroutine_t *coroutine); +ZEND_API void zend_coroutine_switch_handlers_destroy(zend_coroutine_t *coroutine); + +/* Global Main Coroutine Switch Handlers API */ +ZEND_API void zend_async_add_main_coroutine_start_handler( + zend_coroutine_switch_handler_fn handler, + void *user_data +); + +ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *main_coroutine); + END_EXTERN_C() #define ZEND_ASYNC_IS_ENABLED() zend_async_is_enabled() @@ -1191,4 +1249,22 @@ END_EXTERN_C() #define ZEND_ASYNC_INTERNAL_CONTEXT_SET(coro, key, value) zend_async_internal_context_set(coro, key, value) #define ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coro, key) zend_async_internal_context_unset(coro, key) +/* Coroutine Switch Handlers API Macros */ +#define ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler) \ + zend_coroutine_add_switch_handler(coroutine, handler, NULL) + +#define ZEND_COROUTINE_ADD_SWITCH_HANDLER_EX(coroutine, handler, user_data) \ + zend_coroutine_add_switch_handler(coroutine, handler, user_data) + +#define ZEND_COROUTINE_ENTER(coroutine) zend_coroutine_call_switch_handlers(coroutine, true, false); +#define ZEND_COROUTINE_LEAVE(coroutine) zend_coroutine_call_switch_handlers(coroutine, false, false) +#define ZEND_COROUTINE_FINISH(coroutine) zend_coroutine_call_switch_handlers(coroutine, false, true) + +/* Global Main Coroutine Switch Handlers API Macros */ +#define ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler) \ + zend_async_add_main_coroutine_start_handler(handler, NULL) + +#define ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER_EX(handler, user_data) \ + zend_async_add_main_coroutine_start_handler(handler, user_data) + #endif //ZEND_ASYNC_API_H \ No newline at end of file From 5aca1a97d760c4c3478ce7393053a4d49313ef1d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 15 Jun 2025 09:57:22 +0300 Subject: [PATCH 057/137] % optimize coroutine switch hook --- Zend/zend_async_API.c | 69 ++-- Zend/zend_async_API.h | 22 +- docs/source/true_async_api/coroutines.rst | 455 ++++++++++++++++++++++ docs/source/true_async_api/index.rst | 4 + 4 files changed, 502 insertions(+), 48 deletions(-) create mode 100644 docs/source/true_async_api/coroutines.rst diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 692655d2553f6..55f649905e909 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -1180,8 +1180,7 @@ ZEND_API void zend_coroutine_switch_handlers_destroy(zend_coroutine_t *coroutine ZEND_API uint32_t zend_coroutine_add_switch_handler( zend_coroutine_t *coroutine, - zend_coroutine_switch_handler_fn handler, - void *user_data) + zend_coroutine_switch_handler_fn handler) { if (!handler) { zend_error(E_WARNING, "Cannot add NULL switch handler"); @@ -1211,7 +1210,6 @@ ZEND_API uint32_t zend_coroutine_add_switch_handler( /* Add handler */ uint32_t index = vector->length; vector->data[index].handler = handler; - vector->data[index].user_data = user_data; vector->length++; return index; @@ -1259,13 +1257,35 @@ ZEND_API void zend_coroutine_call_switch_handlers( /* Set execution protection flag */ vector->in_execution = true; - /* Call all handlers */ - for (uint32_t i = 0; i < vector->length; i++) { - vector->data[i].handler(coroutine, is_enter, is_finishing); + /* Call all handlers and remove those that return false */ + uint32_t write_index = 0; + for (uint32_t read_index = 0; read_index < vector->length; read_index++) { + bool keep_handler = vector->data[read_index].handler(coroutine, is_enter, is_finishing); + + if (keep_handler) { + /* Keep this handler - move it to write position if needed */ + if (write_index != read_index) { + vector->data[write_index] = vector->data[read_index]; + } + write_index++; + } + /* If keep_handler is false, we skip copying this handler (effectively removing it) */ } + /* Update length to reflect removed handlers */ + vector->length = write_index; + /* Clear execution protection flag */ vector->in_execution = false; + + /* Free vector memory if no handlers remain */ + if (vector->length == 0 && vector->data != NULL) { + coroutine->switch_handlers = NULL; + efree(vector->data); + vector->data = NULL; + vector->capacity = 0; + efree(vector); + } } /////////////////////////////////////////////////////////////// @@ -1275,8 +1295,7 @@ ZEND_API void zend_coroutine_call_switch_handlers( static zend_coroutine_switch_handlers_vector_t global_main_coroutine_start_handlers = {0, 0, NULL, false}; ZEND_API void zend_async_add_main_coroutine_start_handler( - zend_coroutine_switch_handler_fn handler, - void *user_data) + zend_coroutine_switch_handler_fn handler) { zend_coroutine_switch_handlers_vector_t *vector = &global_main_coroutine_start_handlers; @@ -1289,46 +1308,32 @@ ZEND_API void zend_async_add_main_coroutine_start_handler( /* Add handler */ vector->data[vector->length].handler = handler; - vector->data[vector->length].user_data = user_data; vector->length++; } ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *main_coroutine) { - zend_coroutine_switch_handlers_vector_t *vector = &global_main_coroutine_start_handlers; + zend_coroutine_switch_handlers_vector_t *global_vector = &global_main_coroutine_start_handlers; - if (vector->length == 0 || vector->in_execution) { + if (global_vector->length == 0) { return; } - /* Set execution protection flag */ - vector->in_execution = true; - - /* Call all handlers */ - for (uint32_t i = 0; i < vector->length; i++) { - vector->data[i].handler(main_coroutine, true, false); - } - - /* Clear execution protection flag */ - vector->in_execution = false; - - /* Copy handlers to main coroutine */ + /* Initialize main coroutine switch handlers if needed */ if (main_coroutine->switch_handlers == NULL) { - main_coroutine->switch_handlers = safe_emalloc(1, sizeof(zend_coroutine_switch_handlers_vector_t), 0); - main_coroutine->switch_handlers->length = 0; - main_coroutine->switch_handlers->capacity = 0; - main_coroutine->switch_handlers->data = NULL; - main_coroutine->switch_handlers->in_execution = false; + zend_coroutine_switch_handlers_init(main_coroutine); } - /* Copy all global handlers to main coroutine */ - for (uint32_t i = 0; i < vector->length; i++) { + /* Copy all global handlers to main coroutine first */ + for (uint32_t i = 0; i < global_vector->length; i++) { zend_coroutine_add_switch_handler( main_coroutine, - vector->data[i].handler, - vector->data[i].user_data + global_vector->data[i].handler ); } + + /* Now call the standard switch handlers function which will handle removal logic */ + zend_coroutine_call_switch_handlers(main_coroutine, true, false); } /* Global cleanup function - called during PHP shutdown */ diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 77da2c00fbbbe..c147d1670a930 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -125,10 +125,11 @@ typedef void (*zend_coroutine_entry_t)(void); typedef struct _zend_coroutine_switch_handler_s zend_coroutine_switch_handler_t; typedef struct _zend_coroutine_switch_handlers_vector_s zend_coroutine_switch_handlers_vector_t; -typedef void (*zend_coroutine_switch_handler_fn)( +typedef bool (*zend_coroutine_switch_handler_fn)( zend_coroutine_t *coroutine, bool is_enter, /* true = entering coroutine, false = leaving */ bool is_finishing /* true = coroutine is finishing */ + /* returns: true = keep handler, false = remove handler after execution */ ); typedef struct _zend_async_event_s zend_async_event_t; @@ -271,7 +272,6 @@ struct _zend_fcall_s { struct _zend_coroutine_switch_handler_s { zend_coroutine_switch_handler_fn handler; /* Handler function pointer */ - void *user_data; /* User data (can be NULL) */ }; struct _zend_coroutine_switch_handlers_vector_s { @@ -1139,8 +1139,7 @@ ZEND_API void zend_async_waker_callback_timeout( /* Coroutine Switch Handlers API */ ZEND_API uint32_t zend_coroutine_add_switch_handler( zend_coroutine_t *coroutine, - zend_coroutine_switch_handler_fn handler, - void *user_data + zend_coroutine_switch_handler_fn handler ); ZEND_API bool zend_coroutine_remove_switch_handler( @@ -1159,8 +1158,7 @@ ZEND_API void zend_coroutine_switch_handlers_destroy(zend_coroutine_t *coroutine /* Global Main Coroutine Switch Handlers API */ ZEND_API void zend_async_add_main_coroutine_start_handler( - zend_coroutine_switch_handler_fn handler, - void *user_data + zend_coroutine_switch_handler_fn handler ); ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *main_coroutine); @@ -1250,21 +1248,13 @@ END_EXTERN_C() #define ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coro, key) zend_async_internal_context_unset(coro, key) /* Coroutine Switch Handlers API Macros */ -#define ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler) \ - zend_coroutine_add_switch_handler(coroutine, handler, NULL) - -#define ZEND_COROUTINE_ADD_SWITCH_HANDLER_EX(coroutine, handler, user_data) \ - zend_coroutine_add_switch_handler(coroutine, handler, user_data) +#define ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler) zend_coroutine_add_switch_handler(coroutine, handler) #define ZEND_COROUTINE_ENTER(coroutine) zend_coroutine_call_switch_handlers(coroutine, true, false); #define ZEND_COROUTINE_LEAVE(coroutine) zend_coroutine_call_switch_handlers(coroutine, false, false) #define ZEND_COROUTINE_FINISH(coroutine) zend_coroutine_call_switch_handlers(coroutine, false, true) /* Global Main Coroutine Switch Handlers API Macros */ -#define ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler) \ - zend_async_add_main_coroutine_start_handler(handler, NULL) - -#define ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER_EX(handler, user_data) \ - zend_async_add_main_coroutine_start_handler(handler, user_data) +#define ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler) zend_async_add_main_coroutine_start_handler(handler) #endif //ZEND_ASYNC_API_H \ No newline at end of file diff --git a/docs/source/true_async_api/coroutines.rst b/docs/source/true_async_api/coroutines.rst new file mode 100644 index 0000000000000..3e80e2bccc52e --- /dev/null +++ b/docs/source/true_async_api/coroutines.rst @@ -0,0 +1,455 @@ +################ + Coroutines +################ + +Coroutines are the core building blocks of the TrueAsync API. They represent lightweight execution contexts that can be suspended and resumed, enabling cooperative multitasking without traditional thread overhead. Every coroutine is implemented as an event (``zend_coroutine_t`` extends ``zend_async_event_t``) and participates in the standard event lifecycle. + +******************* + Coroutine Basics +******************* + +A coroutine represents a function or code block that can be paused during execution and resumed later. Unlike traditional threads, coroutines are cooperatively scheduled - they yield control voluntarily rather than being preemptively interrupted. + +Key characteristics: + +* **Lightweight** - Minimal memory overhead compared to threads +* **Cooperative** - Explicit yield points, no race conditions +* **Event-based** - Coroutines are events that can await other events +* **Scoped** - Each coroutine runs within a scope for context isolation + +*********************** + Creating Coroutines +*********************** + +The TrueAsync API provides several ways to create coroutines: + +.. code:: c + + // Create a new coroutine within default scope + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + + // Create with specific scope + zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(parent_scope); + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH(scope); + + // Create with scope provider object + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider); + +Coroutines can be either: + +**Userland coroutines** + Execute PHP code through ``zend_fcall_t`` callback mechanism + +**Internal coroutines** + Execute C code through ``zend_coroutine_entry_t`` function pointer + +************************* + Coroutine Lifecycle +************************* + +Coroutines follow a well-defined lifecycle: + +1. **Creation** - Coroutine is spawned but not yet started +2. **Enqueue** - Added to execution queue via ``ZEND_ASYNC_ENQUEUE_COROUTINE()`` +3. **Execution** - Runs until suspension point or completion +4. **Suspension** - Yields control while waiting for events +5. **Resume** - Continues execution when events complete +6. **Completion** - Finishes with result or exception + +.. code:: c + + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + + // Set up coroutine execution + coro->fcall = &my_function_call; + coro->scope = current_scope; + + // Start execution + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + + // Later: resume with result + ZEND_ASYNC_RESUME(coro); + + // Or resume with error + ZEND_ASYNC_RESUME_WITH_ERROR(coro, exception, transfer_error); + +************************* + Coroutine Context +************************* + +Each coroutine maintains its own context information: + +**Scope Context** (``zend_async_scope_t *scope``) + Links to parent scope for hierarchical context management + +**Async Context** (``zend_async_context_t *context``) + User-defined context variables accessible from PHP code + +**Internal Context** (``HashTable *internal_context``) + Extension-private data storage with numeric keys + +**Switch Handlers** (``zend_coroutine_switch_handlers_vector_t *switch_handlers``) + Functions called when entering/leaving the coroutine + +**************************** + Context Switch Handlers +**************************** + +Context switch handlers are one of the most powerful features of the coroutine system. They allow extensions to hook into coroutine context switches and perform necessary cleanup, initialization, or state management. + +Handler Function Signature +=========================== + +.. code:: c + + typedef bool (*zend_coroutine_switch_handler_fn)( + zend_coroutine_t *coroutine, + bool is_enter, /* true = entering coroutine, false = leaving */ + bool is_finishing /* true = coroutine is finishing */ + /* returns: true = keep handler, false = remove handler after execution */ + ); + +The handler receives three parameters: + +* **coroutine** - The coroutine being switched to/from +* **is_enter** - ``true`` when entering coroutine, ``false`` when leaving +* **is_finishing** - ``true`` when coroutine is finishing execution + +The return value determines handler lifetime: + +* **true** - Keep handler for future context switches +* **false** - Remove handler immediately after execution + +This allows handlers to perform one-time initialization and then remove themselves automatically. + +Registering Handlers +==================== + +There are two types of switch handlers: + +**Per-Coroutine Handlers** + +.. code:: c + + // Add handler to specific coroutine + uint32_t handler_id = ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, my_handler); + + // Remove specific handler + zend_coroutine_remove_switch_handler(coroutine, handler_id); + +**Global Main Coroutine Handlers** + +.. code:: c + + // Register global handler for all main coroutines + ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(my_main_handler); + +Global handlers are automatically copied to each main coroutine when it starts and can remove themselves by returning ``false``. + +Practical Example: Output Buffering +==================================== + +The ``main/output.c`` module provides an excellent example of context switch handlers in action. The output system needs to isolate output buffers between the main execution context and coroutines to prevent buffer conflicts. + +.. code:: c + + static bool php_output_main_coroutine_start_handler( + zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (false == is_enter || OG(handlers).elements == 0) { + return false; /* Remove handler - no work to do */ + } + + /* Create coroutine-specific output context */ + php_output_context_t *ctx = ecalloc(1, sizeof(php_output_context_t)); + php_output_init_async_context(ctx); + + /* Copy output handlers from global to coroutine context */ + php_output_handler **src_handlers = (php_output_handler **)zend_stack_base(&OG(handlers)); + int handler_count = zend_stack_count(&OG(handlers)); + + for (int i = 0; i < handler_count; i++) { + php_output_handler *handler = src_handlers[i]; + zend_stack_push(&ctx->handlers, &handler); + } + + /* Store context in coroutine internal_context */ + zval ctx_zval; + ZVAL_PTR(&ctx_zval, ctx); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, php_output_context_key, &ctx_zval); + + /* Clean global handlers to avoid conflicts */ + zend_stack_destroy(&OG(handlers)); + zend_stack_init(&OG(handlers), sizeof(php_output_handler *)); + OG(active) = NULL; + + /* Add cleanup callback for coroutine completion */ + zend_coroutine_event_callback_t *cleanup_callback = + zend_async_coroutine_callback_new(coroutine, php_output_coroutine_cleanup_callback, 0); + coroutine->event.add_callback(&coroutine->event, &cleanup_callback->base); + + return false; /* Remove handler - initialization is complete */ + } + +This handler: + +1. **Detects main coroutine entry** - Only acts when ``is_enter`` is true +2. **Creates isolated context** - Copies output handlers to coroutine-specific storage +3. **Stores in internal_context** - Uses numeric key for fast access +4. **Cleans global state** - Prevents conflicts between main and coroutine contexts +5. **Registers cleanup** - Ensures proper resource cleanup on coroutine completion +6. **Removes itself** - Returns ``false`` since initialization is one-time only + +When to Use Context Switch Handlers +==================================== + +Context switch handlers are ideal for: + +**Resource Isolation** + Separating global state between main execution and coroutines (like output buffers, error handlers, etc.) + +**Context Migration** + Moving data from one execution context to another + +**State Initialization** + Setting up coroutine-specific resources or configurations + +**Cleanup Management** + Registering cleanup callbacks for coroutine completion + +**Performance Optimization** + Pre-computing or caching data when entering frequently-used coroutines + +************************** + Internal Context API +************************** + +The internal context system provides a type-safe, efficient way for PHP extensions to store private data associated with coroutines. Unlike userland context variables, internal context uses numeric keys for faster access and is completely isolated from PHP code. + +Why Internal Context? +====================== + +Before internal context, extensions had limited options for storing coroutine-specific data: + +* **Global variables** - Not coroutine-safe, cause conflicts +* **Object properties** - Not available for internal coroutines +* **Manual management** - Complex, error-prone cleanup + +Internal context solves these problems by providing: + +* **Automatic cleanup** - Data is freed when coroutine completes +* **Type safety** - Values stored as ``zval`` with proper reference counting +* **Uniqueness** - Each extension gets private numeric keys +* **Performance** - Hash table lookup by integer key + +Key Allocation +============== + +Extensions allocate unique keys during module initialization: + +.. code:: c + + static uint32_t my_extension_context_key = 0; + + PHP_MINIT_FUNCTION(my_extension) + { + // Allocate unique key for this extension + my_extension_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension_data"); + return SUCCESS; + } + +The ``ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC`` macro: + +* Takes a static string identifier (for debugging) +* Returns a unique numeric key +* Validates that the same string address always gets the same key +* Is thread-safe in ZTS builds + +Storing and Retrieving Data +=========================== + +.. code:: c + + void my_coroutine_handler(zend_coroutine_t *coroutine) + { + // Store data in coroutine context + zval my_data; + ZVAL_LONG(&my_data, 42); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_extension_context_key, &my_data); + + // Later: retrieve data + zval *stored_data = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_extension_context_key); + if (stored_data && Z_TYPE_P(stored_data) == IS_LONG) { + long value = Z_LVAL_P(stored_data); + // Use value... + } + + // Optional: remove data explicitly + ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coroutine, my_extension_context_key); + } + +The API automatically handles: + +* **Reference counting** - ``zval`` reference counts are managed properly +* **Memory management** - All context data is freed when coroutine ends +* **Thread safety** - Operations are safe in ZTS builds + +Advanced Usage Patterns +======================== + +**Caching expensive computations:** + +.. code:: c + + zval *cached_result = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, cache_key); + if (!cached_result) { + // Expensive operation + zval computed_value; + ZVAL_STRING(&computed_value, expensive_computation()); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, cache_key, &computed_value); + cached_result = &computed_value; + } + +**Tracking state across suspensions:** + +.. code:: c + + // Before suspending + zval state; + ZVAL_LONG(&state, OPERATION_IN_PROGRESS); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, state_key, &state); + + // After resuming + zval *state_val = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, state_key); + if (state_val && Z_LVAL_P(state_val) == OPERATION_IN_PROGRESS) { + // Continue operation... + } + +**Storing complex objects:** + +.. code:: c + + zval object_container; + ZVAL_OBJ(&object_container, my_object); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, object_key, &object_container); + // Object reference count is automatically incremented + +Best Practices +============== + +1. **Allocate keys in MINIT** - Ensures keys are available when needed +2. **Use descriptive key names** - Helps with debugging and maintenance +3. **Check return values** - ``FIND`` returns ``NULL`` if key doesn't exist +4. **Validate data types** - Always check ``Z_TYPE_P`` before accessing data +5. **Clean up appropriately** - Use ``UNSET`` for early cleanup if needed + +**************** + Coroutine API +**************** + +Core Functions +============== + +.. code:: c + + // Coroutine creation and management + zend_coroutine_t *ZEND_ASYNC_SPAWN(); + zend_coroutine_t *ZEND_ASYNC_NEW_COROUTINE(scope); + void ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine); + + // Execution control + void ZEND_ASYNC_SUSPEND(); + void ZEND_ASYNC_RESUME(coroutine); + void ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error); + void ZEND_ASYNC_CANCEL(coroutine, error, transfer_error); + + // Context switch handlers + uint32_t ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler); + bool zend_coroutine_remove_switch_handler(coroutine, handler_index); + void ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler); + +State Checking Macros +===================== + +.. code:: c + + // Coroutine state + ZEND_COROUTINE_IS_STARTED(coroutine) + ZEND_COROUTINE_IS_FINISHED(coroutine) + ZEND_COROUTINE_IS_CANCELLED(coroutine) + ZEND_COROUTINE_SUSPENDED(coroutine) + ZEND_COROUTINE_IS_MAIN(coroutine) + + // Current context + ZEND_ASYNC_CURRENT_COROUTINE + ZEND_ASYNC_CURRENT_SCOPE + +Memory Management +================= + +Coroutines follow standard event reference counting: + +.. code:: c + + ZEND_ASYNC_EVENT_ADD_REF(&coroutine->event); // Increment reference + ZEND_ASYNC_EVENT_RELEASE(&coroutine->event); // Decrement and cleanup if needed + +The coroutine automatically cleans up: + +* Internal context data +* Switch handlers +* Event callbacks +* Scope references + +******************** + Best Practices +******************** + +1. **Always check if async is enabled** before using coroutine APIs +2. **Use appropriate scopes** to maintain context isolation +3. **Handle exceptions properly** in coroutine callbacks +4. **Register cleanup handlers** for long-running operations +5. **Use internal context** instead of global variables for coroutine data +6. **Remove one-time handlers** by returning ``false`` from switch handlers +7. **Validate coroutine state** before performing operations + +Example: Complete Coroutine Usage +================================== + +.. code:: c + + static uint32_t my_context_key = 0; + + PHP_MINIT_FUNCTION(my_extension) + { + my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); + ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(my_main_handler); + return SUCCESS; + } + + static bool my_main_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (!is_enter) return true; + + // Initialize coroutine-specific data + zval init_data; + ZVAL_STRING(&init_data, "initialized"); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &init_data); + + return false; // Remove handler after initialization + } + + void my_async_operation(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) { + return; // Fallback to synchronous operation + } + + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + coro->internal_entry = my_coroutine_function; + + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + ZEND_ASYNC_SUSPEND(); // Wait for completion + } + +This comprehensive coroutine system enables powerful asynchronous programming patterns while maintaining clean separation of concerns and proper resource management. \ No newline at end of file diff --git a/docs/source/true_async_api/index.rst b/docs/source/true_async_api/index.rst index 8d39623cff7a5..3d1c455615764 100644 --- a/docs/source/true_async_api/index.rst +++ b/docs/source/true_async_api/index.rst @@ -7,6 +7,7 @@ architecture events + coroutines api-reference implementation-guide patterns @@ -91,6 +92,9 @@ For implementation details, see the :doc:`implementation-guide` section. :doc:`architecture` Detailed explanation of the two-repository architecture and component separation. +:doc:`coroutines` + Complete guide to coroutines, context switching, and internal context management. + :doc:`api-reference` Complete API reference with function signatures, macros, and data structures. From 089cfe5ac25e803d7491c57c8ba4107ccb3dd0d4 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 15 Jun 2025 15:38:02 +0300 Subject: [PATCH 058/137] * fix behavior with dispose operation inside zend_async_callbacks_notify --- Zend/zend_async_API.c | 9 +++++++++ Zend/zend_async_API.h | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 55f649905e909..c902079ec6421 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -1012,13 +1012,18 @@ zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback ZEND_API void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception, bool from_handler) { + // Protect from self-deletion by incrementing ref count before calling callbacks + ZEND_ASYNC_EVENT_ADD_REF(event); + // If pre-notify returns false, we stop notifying callbacks if (false == from_handler && event->notify_handler != NULL) { event->notify_handler(event, result, exception); + ZEND_ASYNC_EVENT_RELEASE(event); return; } if (event->callbacks.data == NULL || event->callbacks.length == 0) { + ZEND_ASYNC_EVENT_RELEASE(event); return; } @@ -1030,6 +1035,7 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object zend_error(E_CORE_WARNING, "Concurrent callback iteration detected - nested notify() calls are not allowed" ); + ZEND_ASYNC_EVENT_RELEASE(event); return; } @@ -1053,6 +1059,9 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object // Unregister iterator zend_async_callbacks_unregister_iterator(vector); + + // Dispose the reference we added at the beginning - this may trigger disposal if ref count reaches 0 + ZEND_ASYNC_EVENT_RELEASE(event); } /* Call all callbacks and close the event (Like future) */ diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index c147d1670a930..eb440f30dfdf7 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -476,7 +476,11 @@ typedef struct { /* Properly release the event object */ #define ZEND_ASYNC_EVENT_RELEASE(ev) do { \ if (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev)) { \ - OBJ_RELEASE(ZEND_ASYNC_EVENT_TO_OBJECT(ev)); \ + if(GC_REFCOUNT(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) == 1) { \ + OBJ_RELEASE(ZEND_ASYNC_EVENT_TO_OBJECT(ev)); \ + } else { \ + GC_DELREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)); \ + } \ } else { \ if ((ev)->ref_count == 1) { \ (ev)->ref_count = 0; \ From fa0ccab6ced100ff12c07583e22592f637b50077 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 20 Jun 2025 13:41:12 +0300 Subject: [PATCH 059/137] * UNIX: fix exec tests --- Zend/zend_async_API.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index eb440f30dfdf7..7c16ad76901f6 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -600,6 +600,8 @@ struct _zend_async_exec_event_s { zval * result_buffer; size_t output_len; char * output_buffer; + zend_long exit_code; + int term_signal; zval * std_error; }; From cbe111ca36c1cf466793521a05b3e2e9c053f31d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:05:25 +0300 Subject: [PATCH 060/137] * UNIX: Refactoring of coroutine cancellation and completion logic --- Zend/zend_async_API.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 7c16ad76901f6..d16e0eb04d9a9 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -743,6 +743,11 @@ typedef enum { ZEND_ASYNC_WAKER_RESULT } ZEND_ASYNC_WAKER_STATUS; +/** + * Condition that is TRUE if the coroutine is in the queue + */ +#define ZEND_ASYNC_WAKER_IN_QUEUE(waker) (waker != NULL && ((waker)->status == ZEND_ASYNC_WAKER_QUEUED || waker->status == ZEND_ASYNC_WAKER_IGNORED)) + struct _zend_async_waker_s { /* The waker status. */ ZEND_ASYNC_WAKER_STATUS status; From 8ee66aa5b0f061035e93615e1c66f9c1a79ccde5 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:27:43 +0300 Subject: [PATCH 061/137] + ZEND_ASYNC_WAKER_APPLY_ERROR --- Zend/zend_async_API.c | 26 ++++++++++++++++++++++++++ Zend/zend_async_API.h | 5 +++++ 2 files changed, 31 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index c902079ec6421..0bf343a634b66 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -711,6 +711,32 @@ ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( return waker; } +ZEND_API bool zend_async_waker_apply_error(zend_async_waker_t *waker, zend_object *error, bool override, bool for_cancellation) +{ + if (UNEXPECTED(waker == NULL)) { + return false; + } + + if (EXPECTED(waker->error == NULL)) { + waker->error = error; + return true; + } + + if (for_cancellation && instanceof_function(waker->error->ce, zend_ce_cancellation_exception)) { + // If the waker already has a cancellation exception, we do not override it + return false; + } + + if (override) { + zend_exception_set_previous(error, waker->error); + waker->error = error; + } else { + zend_exception_set_previous(waker->error, error); + } + + return true; +} + ////////////////////////////////////////////////////////////////////// /* Waker API end */ ////////////////////////////////////////////////////////////////////// diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index d16e0eb04d9a9..d8bca46a41e1e 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1124,9 +1124,14 @@ ZEND_API zend_async_waker_t *zend_async_waker_new(zend_coroutine_t *coroutine); ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( zend_coroutine_t * coroutine, const zend_ulong timeout, zend_async_event_t *cancellation ); +ZEND_API bool zend_async_waker_apply_error(zend_async_waker_t *waker, zend_object *error, bool override, bool for_cancellation); ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine); ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, zend_async_event_t *event); +#define ZEND_ASYNC_WAKER_APPLY_ERROR(waker, error) zend_async_waker_apply_error((waker), (error), true, false) +#define ZEND_ASYNC_WAKER_APPEND_ERROR(waker, error) zend_async_waker_apply_error((waker), (error), false, false) +#define ZEND_ASYNC_WAKER_APPLY_CANCELLATION(waker, error) zend_async_waker_apply_error((waker), (error), true, true) + ZEND_API void zend_async_resume_when( zend_coroutine_t *coroutine, zend_async_event_t *event, From a0ed682a22c473546a8c1817cc7dd9789e810296 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:49:06 +0300 Subject: [PATCH 062/137] + Refactoring async_coroutine_cancel --- Zend/zend_async_API.c | 17 ++++++++++++++++- Zend/zend_async_API.h | 12 +++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 0bf343a634b66..187f2484e89cb 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -711,19 +711,30 @@ ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( return waker; } -ZEND_API bool zend_async_waker_apply_error(zend_async_waker_t *waker, zend_object *error, bool override, bool for_cancellation) +ZEND_API bool zend_async_waker_apply_error( + zend_async_waker_t *waker, zend_object *error, bool tranfer_error, bool override, bool for_cancellation +) { if (UNEXPECTED(waker == NULL)) { + if (tranfer_error) { + OBJ_RELEASE(error); + } return false; } if (EXPECTED(waker->error == NULL)) { waker->error = error; + if (false == tranfer_error) { + GC_ADDREF(error); + } return true; } if (for_cancellation && instanceof_function(waker->error->ce, zend_ce_cancellation_exception)) { // If the waker already has a cancellation exception, we do not override it + if (tranfer_error) { + OBJ_RELEASE(error); + } return false; } @@ -734,6 +745,10 @@ ZEND_API bool zend_async_waker_apply_error(zend_async_waker_t *waker, zend_objec zend_exception_set_previous(waker->error, error); } + if (false == tranfer_error) { + GC_ADDREF(error); + } + return true; } diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index d8bca46a41e1e..0bf8219fab82d 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -188,7 +188,7 @@ typedef zend_coroutine_t * (*zend_async_spawn_t)(zend_async_scope_t *scope, zend typedef void (*zend_async_suspend_t)(bool from_main); typedef void (*zend_async_enqueue_coroutine_t)(zend_coroutine_t *coroutine); typedef void (*zend_async_resume_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error); -typedef void (*zend_async_cancel_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error, const bool is_safely); +typedef void (*zend_async_cancel_t)(zend_coroutine_t *coroutine, zend_object * error, bool transfer_error, const bool is_safely); typedef void (*zend_async_shutdown_t)(void); typedef zend_array* (*zend_async_get_coroutines_t)(void); typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); @@ -1124,13 +1124,15 @@ ZEND_API zend_async_waker_t *zend_async_waker_new(zend_coroutine_t *coroutine); ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( zend_coroutine_t * coroutine, const zend_ulong timeout, zend_async_event_t *cancellation ); -ZEND_API bool zend_async_waker_apply_error(zend_async_waker_t *waker, zend_object *error, bool override, bool for_cancellation); +ZEND_API bool zend_async_waker_apply_error( + zend_async_waker_t *waker, zend_object *error, bool transfer_error, bool override, bool for_cancellation +); ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine); ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, zend_async_event_t *event); -#define ZEND_ASYNC_WAKER_APPLY_ERROR(waker, error) zend_async_waker_apply_error((waker), (error), true, false) -#define ZEND_ASYNC_WAKER_APPEND_ERROR(waker, error) zend_async_waker_apply_error((waker), (error), false, false) -#define ZEND_ASYNC_WAKER_APPLY_CANCELLATION(waker, error) zend_async_waker_apply_error((waker), (error), true, true) +#define ZEND_ASYNC_WAKER_APPLY_ERROR(waker, error, transfer) zend_async_waker_apply_error((waker), (error), (transfer), true, false) +#define ZEND_ASYNC_WAKER_APPEND_ERROR(waker, error, transfer) zend_async_waker_apply_error((waker), (error), (transfer), false, false) +#define ZEND_ASYNC_WAKER_APPLY_CANCELLATION(waker, error, transfer) zend_async_waker_apply_error((waker), (error), (transfer), true, true) ZEND_API void zend_async_resume_when( zend_coroutine_t *coroutine, From def82b4cd70d51e24e542de74636f59b41eae769 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 21 Jun 2025 09:07:47 +0300 Subject: [PATCH 063/137] * fix ZEND_ASYNC_INITIALIZE issue --- Zend/zend_async_API.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 0bf8219fab82d..f5f86c9778fc0 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -936,7 +936,7 @@ END_EXTERN_C() #define ZEND_ASYNC_OFF (ZEND_ASYNC_G(state) == ZEND_ASYNC_OFF) #define ZEND_ASYNC_IS_READY (ZEND_ASYNC_G(state) == ZEND_ASYNC_READY) #define ZEND_ASYNC_ACTIVATE ZEND_ASYNC_G(state) = ZEND_ASYNC_ACTIVE -#define ZEND_ASYNC_READY ZEND_ASYNC_G(state) = ZEND_ASYNC_READY +#define ZEND_ASYNC_INITIALIZE ZEND_ASYNC_G(state) = ZEND_ASYNC_READY #define ZEND_ASYNC_DEACTIVATE ZEND_ASYNC_G(state) = ZEND_ASYNC_OFF #define ZEND_ASYNC_SCHEDULER_ALIVE (zend_atomic_bool_load(&ZEND_ASYNC_G(heartbeat)) == true) #define ZEND_ASYNC_SCHEDULER_HEARTBEAT zend_atomic_bool_store(&ZEND_ASYNC_G(heartbeat), true) From 085e265e6d5653ebac5c4a8553fe8cb66c91f898 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 21 Jun 2025 09:10:05 +0300 Subject: [PATCH 064/137] * fix ZEND_ASYNC_IS_OFF issue --- Zend/zend_async_API.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index f5f86c9778fc0..c1df25542fdf8 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -933,7 +933,7 @@ END_EXTERN_C() #define ZEND_ASYNC_ON (ZEND_ASYNC_G(state) > ZEND_ASYNC_OFF) #define ZEND_ASYNC_IS_ACTIVE (ZEND_ASYNC_G(state) == ZEND_ASYNC_ACTIVE) -#define ZEND_ASYNC_OFF (ZEND_ASYNC_G(state) == ZEND_ASYNC_OFF) +#define ZEND_ASYNC_IS_OFF (ZEND_ASYNC_G(state) == ZEND_ASYNC_OFF) #define ZEND_ASYNC_IS_READY (ZEND_ASYNC_G(state) == ZEND_ASYNC_READY) #define ZEND_ASYNC_ACTIVATE ZEND_ASYNC_G(state) = ZEND_ASYNC_ACTIVE #define ZEND_ASYNC_INITIALIZE ZEND_ASYNC_G(state) = ZEND_ASYNC_READY From 7b665eda77bb0a75136576eea71885b328145a96 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:27:01 +0300 Subject: [PATCH 065/137] + Scope API --- Zend/zend_async_API.h | 60 ++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index c1df25542fdf8..5b5ba2b5e242f 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -624,7 +624,6 @@ struct _zend_async_task_s { typedef void (*zend_async_before_coroutine_enqueue_t)(zend_coroutine_t *coroutine, zend_async_scope_t *scope, zval *result); typedef void (*zend_async_after_coroutine_enqueue_t)(zend_coroutine_t *coroutine, zend_async_scope_t *scope); -typedef void (*zend_async_scope_dispose_t)(zend_async_scope_t *scope); /* Dynamic array of async event callbacks */ typedef struct _zend_async_scopes_vector_s { @@ -641,13 +640,12 @@ typedef struct _zend_async_scopes_vector_s { * it initiates the disposal process of the Scope. * * However, the internal Scope structure remains in memory until the last coroutine has completed. - * */ struct _zend_async_scope_s { - /* The scope ZEND_ASYNC_SCOPE_F flags */ - uint32_t flags; + /* Event object for reacting to events. */ + zend_async_event_t event; /* The link to the zend_object structure */ - zend_object * scope_object; + zend_object *scope_object; zend_async_scopes_vector_t scopes; zend_async_scope_t *parent_scope; @@ -656,25 +654,29 @@ struct _zend_async_scope_s { zend_async_before_coroutine_enqueue_t before_coroutine_enqueue; zend_async_after_coroutine_enqueue_t after_coroutine_enqueue; - zend_async_scope_dispose_t dispose; + void (*cancel)(zend_async_scope_t *scope, zend_object *error, bool transfer_error, const bool is_safely); }; -#define ZEND_ASYNC_SCOPE_F_CLOSED (1u << 0) /* scope was closed */ -#define ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY (1u << 1) /* scope will not free memory in dispose handler */ -#define ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY (1u << 2) /* scope will be disposed safely */ +#define ZEND_ASYNC_SCOPE_F_CLOSED ZEND_ASYNC_EVENT_F_CLOSED /* scope was closed */ +#define ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY /* scope will not free memory in dispose handler */ +#define ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY (1u << 14) /* scope will be disposed safely */ +#define ZEND_ASYNC_SCOPE_F_CANCELLED (1u << 15) /* scope was cancelled */ + +#define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CLOSED) != 0) +#define ZEND_ASYNC_SCOPE_IS_NO_FREE_MEMORY(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) != 0) +#define ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) != 0) +#define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CANCELLED) != 0) -#define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) (((scope)->flags & ZEND_ASYNC_SCOPE_F_CLOSED) != 0) -#define ZEND_ASYNC_SCOPE_IS_NO_FREE_MEMORY(scope) (((scope)->flags & ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) != 0) -#define ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(scope) (((scope)->flags & ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) != 0) +#define ZEND_ASYNC_SCOPE_SET_CLOSED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CLOSED) +#define ZEND_ASYNC_SCOPE_CLR_CLOSED(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_CLOSED) -#define ZEND_ASYNC_SCOPE_SET_CLOSED(scope) ((scope)->flags |= ZEND_ASYNC_SCOPE_F_CLOSED) -#define ZEND_ASYNC_SCOPE_CLR_CLOSED(scope) ((scope)->flags &= ~ZEND_ASYNC_SCOPE_F_CLOSED) +#define ZEND_ASYNC_SCOPE_SET_NO_FREE_MEMORY(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) +#define ZEND_ASYNC_SCOPE_CLR_NO_FREE_MEMORY(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) -#define ZEND_ASYNC_SCOPE_SET_NO_FREE_MEMORY(scope) ((scope)->flags |= ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) -#define ZEND_ASYNC_SCOPE_CLR_NO_FREE_MEMORY(scope) ((scope)->flags &= ~ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) +#define ZEND_ASYNC_SCOPE_SET_DISPOSE_SAFELY(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) +#define ZEND_ASYNC_SCOPE_CLR_DISPOSE_SAFELY(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) -#define ZEND_ASYNC_SCOPE_SET_DISPOSE(scope) ((scope)->flags |= ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) -#define ZEND_ASYNC_SCOPE_CLR_DISPOSE(scope) ((scope)->flags &= ~ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) +#define ZEND_ASYNC_SCOPE_SET_CANCELLED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CANCELLED) static zend_always_inline void zend_async_scope_add_child(zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) @@ -862,6 +864,28 @@ static zend_always_inline zend_string *zend_coroutine_callable_name(const zend_c return zend_string_init("internal function", sizeof("internal function") - 1, 0); } +/** + * Macro for constructing an FCALL structure from PHP function parameters. + * Z_PARAM_FUNC(fci, fcc); + * Z_PARAM_VARIADIC_WITH_NAMED(args, args_count, named_args); + */ +#define ZEND_ASYNC_FCALL_DEFINE(fcall, fci, fcc, args, args_count, named_args) \ + zend_fcall_t * fcall = ecalloc(1, sizeof(zend_fcall_t)); \ + fcall->fci = fci; \ + fcall->fci_cache = fcc; \ + if (args_count) { \ + fcall->fci.param_count = args_count; \ + fcall->fci.params = safe_emalloc(args_count, sizeof(zval), 0); \ + for (uint32_t i = 0; i < args_count; i++) { \ + ZVAL_COPY(&fcall->fci.params[i], &args[i]); \ + } \ + } \ + if (named_args) { \ + fcall->fci.named_params = named_args; \ + GC_ADDREF(named_args); \ + } \ + Z_TRY_ADDREF(fcall->fci.function_name); + /////////////////////////////////////////////////////////////// /// Async Context Structures /////////////////////////////////////////////////////////////// From 9990aa8e7027b2fc12a6184f8329b15528c01d13 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 24 Jun 2025 13:03:53 +0300 Subject: [PATCH 066/137] % refactoring catch_or_cancel logic according to RFC scope behavior --- Zend/zend_async_API.c | 2 ++ Zend/zend_async_API.h | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 187f2484e89cb..f07f31d690bc0 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -1055,6 +1055,8 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object { // Protect from self-deletion by incrementing ref count before calling callbacks ZEND_ASYNC_EVENT_ADD_REF(event); + // Automatically clear exception handled flag + ZEND_ASYNC_EVENT_CLR_EXCEPTION_HANDLED(event); // If pre-notify returns false, we stop notifying callbacks if (false == from_handler && event->notify_handler != NULL) { diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 5b5ba2b5e242f..5437d2e2e647e 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -654,7 +654,24 @@ struct _zend_async_scope_s { zend_async_before_coroutine_enqueue_t before_coroutine_enqueue; zend_async_after_coroutine_enqueue_t after_coroutine_enqueue; - void (*cancel)(zend_async_scope_t *scope, zend_object *error, bool transfer_error, const bool is_safely); + + /** + * The method handles an exception delivered to the Scope. + * Its result may either be the cancellation of the Scope or the suppression of the exception. + * If the coroutine parameter is specified, it indicates an attempt to handle an exception from a coroutine. + * Otherwise, it's an attempt by the user to stop the execution of the Scope. + * + * The method should return true if the exception was handled and the Scope can continue execution. + * + * This method is the central point of responsibility where the behavior in case of an error is determined. + */ + bool (*catch_or_cancel)( + zend_async_scope_t *scope, + zend_coroutine_t *coroutine, + zend_object *exception, + bool transfer_error, + const bool is_safely + ); }; #define ZEND_ASYNC_SCOPE_F_CLOSED ZEND_ASYNC_EVENT_F_CLOSED /* scope was closed */ From 739364fd55d5177f2e751a34d10cd83f35bb4078 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:25:04 +0300 Subject: [PATCH 067/137] * fix build issue for Scope --- Zend/zend_async_API.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 5437d2e2e647e..89c8ec726d133 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -655,6 +655,12 @@ struct _zend_async_scope_s { zend_async_before_coroutine_enqueue_t before_coroutine_enqueue; zend_async_after_coroutine_enqueue_t after_coroutine_enqueue; + /** + * The method determines the moment when the Scope can be destructed. + * It checks the conditions and, if necessary, calls the dispose method. + */ + void (*try_to_dispose)(zend_async_scope_t *scope); + /** * The method handles an exception delivered to the Scope. * Its result may either be the cancellation of the Scope or the suppression of the exception. @@ -726,7 +732,7 @@ zend_async_scope_remove_child(zend_async_scope_t *parent_scope, zend_async_scope // Try to dispose the parent scope if it is empty if (parent_scope->scopes.length == 0) { - parent_scope->dispose(parent_scope); + parent_scope->try_to_dispose(parent_scope); } return; From 209bd329aa3e1d33aa875a599c97187245c48233 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 24 Jun 2025 21:45:58 +0300 Subject: [PATCH 068/137] * fixes scope_try_to_dispose --- Zend/zend_async_API.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 89c8ec726d133..856a2fc2bbc14 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -659,7 +659,7 @@ struct _zend_async_scope_s { * The method determines the moment when the Scope can be destructed. * It checks the conditions and, if necessary, calls the dispose method. */ - void (*try_to_dispose)(zend_async_scope_t *scope); + bool (*try_to_dispose)(zend_async_scope_t *scope); /** * The method handles an exception delivered to the Scope. From a23429941b6037a7f1ba954da479b8fc1108d69c Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:14:48 +0300 Subject: [PATCH 069/137] =?UTF-8?q?%=20The=20ZEND=5FASYNC=5FNEW=5FSCOPE=20?= =?UTF-8?q?API=20function=20has=20been=20modified=20=E2=80=94=20it=20can?= =?UTF-8?q?=20now=20create=20a=20Scope=20without=20a=20ZEND=20object=20for?= =?UTF-8?q?=20internal=20use.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new macro has been added: ZEND_ASYNC_NEW_SCOPE_WITH_OBJECT --- Zend/zend_async_API.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 856a2fc2bbc14..f1943ef246447 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -183,7 +183,7 @@ typedef struct _zend_async_task_s zend_async_task_t; /* Internal context typedefs removed - using direct functions */ typedef zend_coroutine_t * (*zend_async_new_coroutine_t)(zend_async_scope_t *scope); -typedef zend_async_scope_t * (*zend_async_new_scope_t)(zend_async_scope_t * parent_scope); +typedef zend_async_scope_t * (*zend_async_new_scope_t)(zend_async_scope_t * parent_scope, bool with_zend_object); typedef zend_coroutine_t * (*zend_async_spawn_t)(zend_async_scope_t *scope, zend_object *scope_provider); typedef void (*zend_async_suspend_t)(bool from_main); typedef void (*zend_async_enqueue_coroutine_t)(zend_coroutine_t *coroutine); @@ -1235,7 +1235,8 @@ END_EXTERN_C() #define ZEND_ASYNC_SPAWN_WITH(scope) zend_async_spawn_fn(scope, NULL) #define ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider) zend_async_spawn_fn(NULL, scope_provider) #define ZEND_ASYNC_NEW_COROUTINE(scope) zend_async_new_coroutine_fn(scope) -#define ZEND_ASYNC_NEW_SCOPE(parent) zend_async_new_scope_fn(parent) +#define ZEND_ASYNC_NEW_SCOPE(parent) zend_async_new_scope_fn(parent, false) +#define ZEND_ASYNC_NEW_SCOPE_WITH_OBJECT(parent) zend_async_new_scope_fn(parent, true) #define ZEND_ASYNC_SUSPEND() zend_async_suspend_fn(false) #define ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN() zend_async_suspend_fn(true) #define ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine) zend_async_enqueue_coroutine_fn(coroutine) From 964eca0763793216d89863f2a7ad4c1bf985b112 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:05:16 +0300 Subject: [PATCH 070/137] add coroutine priority support to TrueAsync API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements priority-based coroutine scheduling where higher priority values result in coroutines being queued at the front of the execution queue. ## Changes ### Circular Buffer Enhancement - Add `circular_buffer_push_front()` function to insert elements at buffer head - Fallback to normal push when front insertion is not possible (buffer full) ### API Extension - Update `zend_async_spawn_t` typedef to include `int32_t priority` parameter - Add new macros: - `ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority)` - `ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority)` - Maintain backward compatibility by updating existing macros to pass priority=0 ### Priority Logic - Priority > 0: coroutine queued at front (high priority) - Priority ≤ 0: coroutine queued at back (normal/low priority) - Preserves existing behavior for all current code ### Files Modified - `ext/async/internal/circular_buffer.{h,c}` - Buffer front insertion - `Zend/zend_async_API.{h,c}` - API definitions and stub functions - `ext/async/async_API.c` - Priority-aware spawn implementation ## Backward Compatibility All existing spawn macros unchanged in behavior, new priority parameter defaults to 0 for normal scheduling order. --- Zend/zend_async_API.c | 2 +- Zend/zend_async_API.h | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index f07f31d690bc0..8b608a690e841 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -29,7 +29,7 @@ zend_async_globals_t zend_async_globals_api = {0}; /* Forward declarations */ static void zend_async_main_handlers_shutdown(void); -static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_provider) +static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_provider, int32_t priority) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index f1943ef246447..4c82ef73d4b62 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -184,7 +184,7 @@ typedef struct _zend_async_task_s zend_async_task_t; typedef zend_coroutine_t * (*zend_async_new_coroutine_t)(zend_async_scope_t *scope); typedef zend_async_scope_t * (*zend_async_new_scope_t)(zend_async_scope_t * parent_scope, bool with_zend_object); -typedef zend_coroutine_t * (*zend_async_spawn_t)(zend_async_scope_t *scope, zend_object *scope_provider); +typedef zend_coroutine_t * (*zend_async_spawn_t)(zend_async_scope_t *scope, zend_object *scope_provider, int32_t priority); typedef void (*zend_async_suspend_t)(bool from_main); typedef void (*zend_async_enqueue_coroutine_t)(zend_coroutine_t *coroutine); typedef void (*zend_async_resume_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error); @@ -1231,9 +1231,11 @@ ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *ma END_EXTERN_C() #define ZEND_ASYNC_IS_ENABLED() zend_async_is_enabled() -#define ZEND_ASYNC_SPAWN() zend_async_spawn_fn(NULL, NULL) -#define ZEND_ASYNC_SPAWN_WITH(scope) zend_async_spawn_fn(scope, NULL) -#define ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider) zend_async_spawn_fn(NULL, scope_provider) +#define ZEND_ASYNC_SPAWN() zend_async_spawn_fn(NULL, NULL, 0) +#define ZEND_ASYNC_SPAWN_WITH(scope) zend_async_spawn_fn(scope, NULL, 0) +#define ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider) zend_async_spawn_fn(NULL, scope_provider, 0) +#define ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority) zend_async_spawn_fn(NULL, NULL, priority) +#define ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority) zend_async_spawn_fn(scope, NULL, priority) #define ZEND_ASYNC_NEW_COROUTINE(scope) zend_async_new_coroutine_fn(scope) #define ZEND_ASYNC_NEW_SCOPE(parent) zend_async_new_scope_fn(parent, false) #define ZEND_ASYNC_NEW_SCOPE_WITH_OBJECT(parent) zend_async_new_scope_fn(parent, true) From 09947a163e002068f1062922dbadd947733d59b7 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:54:12 +0300 Subject: [PATCH 071/137] Add cross-thread trigger event API Implement zend_async_trigger_event_t using uv_async_t for thread-safe event activation across different threads and event loops. - Add API definitions in zend_async_API.h - Implement libuv backend in ext/async/ - Thread-safe trigger() method - Persistent memory allocation - No additional synchronization needed --- Zend/zend_async_API.c | 8 +++++++- Zend/zend_async_API.h | 18 +++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 8b608a690e841..85a951f88fe22 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -101,6 +101,9 @@ zend_async_freeaddrinfo_t zend_async_freeaddrinfo_fn = NULL; zend_async_new_exec_event_t zend_async_new_exec_event_fn = NULL; zend_async_exec_t zend_async_exec_fn = NULL; +/* Trigger Event API */ +zend_async_new_trigger_event_t zend_async_new_trigger_event_fn = NULL; + static zend_string * thread_pool_module_name = NULL; zend_async_queue_task_t zend_async_queue_task_fn = NULL; @@ -259,7 +262,8 @@ ZEND_API bool zend_async_reactor_register( zend_async_getaddrinfo_t getaddrinfo_fn, zend_async_freeaddrinfo_t freeaddrinfo_fn, zend_async_new_exec_event_t new_exec_event_fn, - zend_async_exec_t exec_fn + zend_async_exec_t exec_fn, + zend_async_new_trigger_event_t new_trigger_event_fn ) { if (zend_atomic_bool_exchange(&reactor_lock, 1)) { @@ -301,6 +305,8 @@ ZEND_API bool zend_async_reactor_register( zend_async_new_exec_event_fn = new_exec_event_fn; zend_async_exec_fn = exec_fn; + zend_async_new_trigger_event_fn = new_trigger_event_fn; + zend_atomic_bool_store(&reactor_lock, 0); return true; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 4c82ef73d4b62..a8116cf009705 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -170,6 +170,7 @@ typedef struct _zend_async_filesystem_event_s zend_async_filesystem_event_t; typedef struct _zend_async_process_event_s zend_async_process_event_t; typedef struct _zend_async_thread_event_s zend_async_thread_event_t; +typedef struct _zend_async_trigger_event_s zend_async_trigger_event_t; typedef struct _zend_async_dns_nameinfo_s zend_async_dns_nameinfo_t; typedef struct _zend_async_dns_addrinfo_s zend_async_dns_addrinfo_t; @@ -215,6 +216,8 @@ typedef void (*zend_async_thread_entry_t)(void *arg, size_t extra_size); typedef zend_async_thread_event_t* (*zend_async_new_thread_event_t)( zend_async_thread_entry_t entry, void *arg, size_t extra_size ); +typedef void (*zend_async_trigger_event_trigger_fn)(zend_async_trigger_event_t *event); +typedef zend_async_trigger_event_t* (*zend_async_new_trigger_event_t)(size_t extra_size); typedef zend_async_filesystem_event_t* (*zend_async_new_filesystem_event_t)( zend_string * path, const unsigned int flags, size_t extra_size ); @@ -618,6 +621,11 @@ struct _zend_async_task_s { zend_async_event_t base; }; +struct _zend_async_trigger_event_s { + zend_async_event_t base; + zend_async_trigger_event_trigger_fn trigger; +}; + /////////////////////////////////////////////////////////////////// /// Scope Structures /////////////////////////////////////////////////////////////////// @@ -1106,6 +1114,9 @@ ZEND_API extern zend_async_exec_t zend_async_exec_fn; ZEND_API bool zend_async_thread_pool_is_enabled(void); ZEND_API extern zend_async_queue_task_t zend_async_queue_task_fn; +/* Trigger Event API */ +ZEND_API extern zend_async_new_trigger_event_t zend_async_new_trigger_event_fn; + ZEND_API bool zend_async_scheduler_register( char *module, bool allow_override, @@ -1142,7 +1153,8 @@ ZEND_API bool zend_async_reactor_register( zend_async_getaddrinfo_t getaddrinfo_fn, zend_async_freeaddrinfo_t freeaddrinfo_fn, zend_async_new_exec_event_t new_exec_event_fn, - zend_async_exec_t exec_fn + zend_async_exec_t exec_fn, + zend_async_new_trigger_event_t new_trigger_event_fn ); ZEND_API void zend_async_thread_pool_register( @@ -1292,6 +1304,10 @@ END_EXTERN_C() #define ZEND_ASYNC_QUEUE_TASK(task) zend_async_queue_task_fn(task) +/* Trigger Event API Macros */ +#define ZEND_ASYNC_NEW_TRIGGER_EVENT() zend_async_new_trigger_event_fn(0) +#define ZEND_ASYNC_NEW_TRIGGER_EVENT_EX(extra_size) zend_async_new_trigger_event_fn(extra_size) + /* Socket Listening API Macros */ #define ZEND_ASYNC_SOCKET_LISTEN(host, port, backlog) \ zend_async_socket_listen_fn(host, port, backlog, 0) From 2137eb5bd7dbe51e44d52e6b92bdaf1cf70c3b01 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 25 Jun 2025 15:53:34 +0300 Subject: [PATCH 072/137] + **Iterator API integration**: Added `zend_async_iterator_t` structure to TrueAsync API with `run()` and `run_in_coroutine()` methods --- Zend/zend_async_API.c | 7 ++++++- Zend/zend_async_API.h | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 85a951f88fe22..e7bb698f55f26 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -107,6 +107,9 @@ zend_async_new_trigger_event_t zend_async_new_trigger_event_fn = NULL; static zend_string * thread_pool_module_name = NULL; zend_async_queue_task_t zend_async_queue_task_fn = NULL; +/* Iterator API */ +zend_async_new_iterator_t zend_async_new_iterator_fn = NULL; + /* Context API */ zend_async_new_context_t zend_async_new_context_fn = new_context; @@ -203,7 +206,8 @@ ZEND_API bool zend_async_scheduler_register( zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_class_ce_t get_class_ce_fn + zend_async_get_class_ce_t get_class_ce_fn, + zend_async_new_iterator_t new_iterator_fn ) { if (zend_atomic_bool_exchange(&scheduler_lock, 1)) { @@ -238,6 +242,7 @@ ZEND_API bool zend_async_scheduler_register( zend_async_add_microtask_fn = add_microtask_fn; zend_async_get_awaiting_info_fn = get_awaiting_info_fn; zend_async_get_class_ce_fn = get_class_ce_fn; + zend_async_new_iterator_fn = new_iterator_fn; zend_atomic_bool_store(&scheduler_lock, 0); diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index a8116cf009705..d29a82aa6d2b2 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -118,6 +118,7 @@ typedef struct _zend_async_context_s zend_async_context_t; typedef struct _zend_async_waker_s zend_async_waker_t; typedef struct _zend_async_microtask_s zend_async_microtask_t; typedef struct _zend_async_scope_s zend_async_scope_t; +typedef struct _zend_async_iterator_s zend_async_iterator_t; typedef struct _zend_fcall_s zend_fcall_t; typedef void (*zend_coroutine_entry_t)(void); @@ -291,6 +292,28 @@ struct _zend_async_microtask_s { uint32_t ref_count; }; +/////////////////////////////////////////////////////////////////// +/// Async iterator structures +/////////////////////////////////////////////////////////////////// + +struct _zend_async_iterator_s { + zend_async_microtask_t microtask; + void (*run)(zend_async_iterator_t *iterator); + void (*run_in_coroutine)(zend_async_iterator_t *iterator, int32_t priority); +}; + +typedef zend_result (*zend_async_iterator_handler_t)(zend_async_iterator_t *iterator, zval *current, zval *key); + +typedef zend_async_iterator_t* (*zend_async_new_iterator_t)( + zval *array, + zend_object_iterator *zend_iterator, + zend_fcall_t *fcall, + zend_async_iterator_handler_t handler, + unsigned int concurrency, + int32_t priority, + size_t iterator_size +); + /////////////////////////////////////////////////////////////////// /// Event Structures /////////////////////////////////////////////////////////////////// @@ -1065,6 +1088,9 @@ ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; +/* Iterator API */ +ZEND_API extern zend_async_new_iterator_t zend_async_new_iterator_fn; + /* Context API */ ZEND_API extern zend_async_new_context_t zend_async_new_context_fn; @@ -1132,7 +1158,8 @@ ZEND_API bool zend_async_scheduler_register( zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_class_ce_t get_class_ce_fn + zend_async_get_class_ce_t get_class_ce_fn, + zend_async_new_iterator_t new_iterator_fn ); ZEND_API bool zend_async_reactor_register( @@ -1314,6 +1341,12 @@ END_EXTERN_C() #define ZEND_ASYNC_SOCKET_LISTEN_EX(host, port, backlog, extra_size) \ zend_async_socket_listen_fn(host, port, backlog, extra_size) +/* Iterator API Macros */ +#define ZEND_ASYNC_NEW_ITERATOR(array, zend_iterator, fcall, handler, concurrency, priority) \ + zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, concurrency, priority, 0) +#define ZEND_ASYNC_NEW_ITERATOR_EX(array, zend_iterator, fcall, handler, concurrency, priority, size) \ + zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, concurrency, priority, size) + /* Context API Macros */ #define ZEND_ASYNC_NEW_CONTEXT(parent) zend_async_new_context_fn(parent) #define ZEND_ASYNC_CURRENT_CONTEXT (ZEND_ASYNC_G(coroutine) != NULL ? ZEND_ASYNC_G(coroutine)->scope->context : NULL) From 2593f660543fb035fac8452df805170cca555581 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 26 Jun 2025 18:40:09 +0300 Subject: [PATCH 073/137] % refactoring typedef zend_async_iterator_t* (*zend_async_new_iterator_t)( zval *array, zend_object_iterator *zend_iterator, zend_fcall_t *fcall, zend_async_iterator_handler_t handler, zend_async_scope_t *scope, unsigned int concurrency, int32_t priority, size_t iterator_size ); add scope parameter --- Zend/zend_async_API.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index d29a82aa6d2b2..949771be251b0 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -309,6 +309,7 @@ typedef zend_async_iterator_t* (*zend_async_new_iterator_t)( zend_object_iterator *zend_iterator, zend_fcall_t *fcall, zend_async_iterator_handler_t handler, + zend_async_scope_t *scope, unsigned int concurrency, int32_t priority, size_t iterator_size @@ -1342,10 +1343,12 @@ END_EXTERN_C() zend_async_socket_listen_fn(host, port, backlog, extra_size) /* Iterator API Macros */ +#define ZEND_ASYNC_NEW_ITERATOR_SCOPE(array, zend_iterator, fcall, handler, scope, concurrency, priority) \ + zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, scope, concurrency, priority, 0) #define ZEND_ASYNC_NEW_ITERATOR(array, zend_iterator, fcall, handler, concurrency, priority) \ - zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, concurrency, priority, 0) + zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, NULL, concurrency, priority, 0) #define ZEND_ASYNC_NEW_ITERATOR_EX(array, zend_iterator, fcall, handler, concurrency, priority, size) \ - zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, concurrency, priority, size) + zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, NULL, concurrency, priority, size) /* Context API Macros */ #define ZEND_ASYNC_NEW_CONTEXT(parent) zend_async_new_context_fn(parent) From de45e8ccfb08eb9264810d957adf98545d47b23f Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 26 Jun 2025 23:29:54 +0300 Subject: [PATCH 074/137] + async_spawn_and_throw --- Zend/zend_async_API.c | 9 +++++++++ Zend/zend_async_API.h | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index e7bb698f55f26..0ede7b8a72d9d 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -42,6 +42,12 @@ static void enqueue_coroutine(zend_coroutine_t *coroutine) ASYNC_THROW_ERROR("Async API is not enabled"); } +static bool spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, int32_t priority) +{ + ASYNC_THROW_ERROR("Async API is not enabled"); + return false; +} + static zend_async_context_t * new_context(void) { ASYNC_THROW_ERROR("Context API is not enabled"); @@ -74,6 +80,7 @@ zend_async_suspend_t zend_async_suspend_fn = suspend; zend_async_enqueue_coroutine_t zend_async_enqueue_coroutine_fn = enqueue_coroutine; zend_async_resume_t zend_async_resume_fn = NULL; zend_async_cancel_t zend_async_cancel_fn = NULL; +zend_async_spawn_and_throw_t zend_async_spawn_and_throw_fn = spawn_and_throw; zend_async_shutdown_t zend_async_shutdown_fn = NULL; zend_async_get_coroutines_t zend_async_get_coroutines_fn = NULL; zend_async_add_microtask_t zend_async_add_microtask_fn = NULL; @@ -202,6 +209,7 @@ ZEND_API bool zend_async_scheduler_register( zend_async_enqueue_coroutine_t enqueue_coroutine_fn, zend_async_resume_t resume_fn, zend_async_cancel_t cancel_fn, + zend_async_spawn_and_throw_t spawn_and_throw_fn, zend_async_shutdown_t shutdown_fn, zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, @@ -237,6 +245,7 @@ ZEND_API bool zend_async_scheduler_register( zend_async_enqueue_coroutine_fn = enqueue_coroutine_fn; zend_async_resume_fn = resume_fn; zend_async_cancel_fn = cancel_fn; + zend_async_spawn_and_throw_fn = spawn_and_throw_fn; zend_async_shutdown_fn = shutdown_fn; zend_async_get_coroutines_fn = get_coroutines_fn; zend_async_add_microtask_fn = add_microtask_fn; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 949771be251b0..675c8a44cf519 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -191,6 +191,7 @@ typedef void (*zend_async_suspend_t)(bool from_main); typedef void (*zend_async_enqueue_coroutine_t)(zend_coroutine_t *coroutine); typedef void (*zend_async_resume_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error); typedef void (*zend_async_cancel_t)(zend_coroutine_t *coroutine, zend_object * error, bool transfer_error, const bool is_safely); +typedef bool (*zend_async_spawn_and_throw_t)(zend_object *exception, zend_async_scope_t *scope, int32_t priority); typedef void (*zend_async_shutdown_t)(void); typedef zend_array* (*zend_async_get_coroutines_t)(void); typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); @@ -296,10 +297,26 @@ struct _zend_async_microtask_s { /// Async iterator structures /////////////////////////////////////////////////////////////////// +typedef void (*zend_async_iterator_method_t)(zend_async_iterator_t *iterator); + +#define ZEND_ASYNC_ITERATOR_FIELDS \ + zend_async_microtask_t microtask; \ + zend_async_scope_t *scope; \ + /* NULLABLE. Custom data for the iterator, can be used to store additional information. */ \ + void *extended_data; \ + /* NULLABLE. An additional destructor that will be called. */ \ + zend_async_iterator_method_t extended_dtor; \ + /* A method that starts the iterator in the current coroutine. */ \ + zend_async_iterator_method_t run; \ + /* A method that starts the iterator in a separate coroutine with the specified priority. */ \ + void (*run_in_coroutine)(zend_async_iterator_t *iterator, int32_t priority); \ + /* The maximum number of concurrent tasks that can be executed at the same time */ \ + unsigned int concurrency; \ + /* Priority for coroutines created by this iterator */ \ + int32_t priority; + struct _zend_async_iterator_s { - zend_async_microtask_t microtask; - void (*run)(zend_async_iterator_t *iterator); - void (*run_in_coroutine)(zend_async_iterator_t *iterator, int32_t priority); + ZEND_ASYNC_ITERATOR_FIELDS }; typedef zend_result (*zend_async_iterator_handler_t)(zend_async_iterator_t *iterator, zval *current, zval *key); @@ -1083,6 +1100,7 @@ ZEND_API extern zend_async_suspend_t zend_async_suspend_fn; ZEND_API extern zend_async_enqueue_coroutine_t zend_async_enqueue_coroutine_fn; ZEND_API extern zend_async_resume_t zend_async_resume_fn; ZEND_API extern zend_async_cancel_t zend_async_cancel_fn; +ZEND_API extern zend_async_spawn_and_throw_t zend_async_spawn_and_throw_fn; ZEND_API extern zend_async_shutdown_t zend_async_shutdown_fn; ZEND_API extern zend_async_get_coroutines_t zend_async_get_coroutines_fn; ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; @@ -1155,6 +1173,7 @@ ZEND_API bool zend_async_scheduler_register( zend_async_enqueue_coroutine_t enqueue_coroutine_fn, zend_async_resume_t resume_fn, zend_async_cancel_t cancel_fn, + zend_async_spawn_and_throw_t spawn_and_throw_fn, zend_async_shutdown_t shutdown_fn, zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, @@ -1286,6 +1305,19 @@ END_EXTERN_C() #define ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error) zend_async_resume_fn(coroutine, error, transfer_error) #define ZEND_ASYNC_CANCEL(coroutine, error, transfer_error) zend_async_cancel_fn(coroutine, error, transfer_error, false) #define ZEND_ASYNC_CANCEL_EX(coroutine, error, transfer_error, is_safely) zend_async_cancel_fn(coroutine, error, transfer_error, is_safely) + +/** + * Spawns a new coroutine and throws the specified exception within it. + * + * This creates a dedicated coroutine for exception handling, ensuring proper + * scope-based error propagation when exceptions occur in microtasks or other + * contexts where direct throwing would bypass scope exception handling. + * + * @param exception The exception object to throw in the new coroutine + * @param scope Target scope for the coroutine (NULL for current scope) + * @param priority Priority level for the exception-throwing coroutine + */ +#define ZEND_ASYNC_SPAWN_AND_THROW(exception, scope, priority) zend_async_spawn_and_throw_fn(exception, scope, priority) #define ZEND_ASYNC_SHUTDOWN() zend_async_shutdown_fn() #define ZEND_ASYNC_GET_COROUTINES() zend_async_get_coroutines_fn() #define ZEND_ASYNC_ADD_MICROTASK(microtask) zend_async_add_microtask_fn(microtask) From 9301b6c3457fd441c912a56bbd30a43d266fed77 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:45:21 +0300 Subject: [PATCH 075/137] % Major refactoring of Finally Handlers and the exception handling mechanism for Scope. --- Zend/zend_async_API.h | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 675c8a44cf519..21ad12f974358 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -713,7 +713,7 @@ struct _zend_async_scope_s { /** * The method handles an exception delivered to the Scope. * Its result may either be the cancellation of the Scope or the suppression of the exception. - * If the coroutine parameter is specified, it indicates an attempt to handle an exception from a coroutine. + * If the is_cancellation parameter is FALSE, it indicates an attempt to handle an exception from a coroutine. * Otherwise, it's an attempt by the user to stop the execution of the Scope. * * The method should return true if the exception was handled and the Scope can continue execution. @@ -723,12 +723,23 @@ struct _zend_async_scope_s { bool (*catch_or_cancel)( zend_async_scope_t *scope, zend_coroutine_t *coroutine, + zend_async_scope_t *from_scope, zend_object *exception, bool transfer_error, - const bool is_safely + const bool is_safely, + const bool is_cancellation ); }; +#define ZEND_ASYNC_SCOPE_CLOSE(scope, is_safely) \ + ((scope)->catch_or_cancel((scope), NULL, NULL, NULL, false, (is_safely), true)) + +#define ZEND_ASYNC_SCOPE_CANCEL(scope, exception, transfer_error, is_safely) \ + ((scope)->catch_or_cancel((scope), NULL, NULL, (exception), (transfer_error), (is_safely), true)) + +#define ZEND_ASYNC_SCOPE_CATCH(scope, coroutine, from_scope, exception, transfer_error, is_safely) \ + ((scope)->catch_or_cancel((scope), (coroutine), (from_scope), (exception), (transfer_error), (is_safely), false)) + #define ZEND_ASYNC_SCOPE_F_CLOSED ZEND_ASYNC_EVENT_F_CLOSED /* scope was closed */ #define ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY /* scope will not free memory in dispose handler */ #define ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY (1u << 14) /* scope will be disposed safely */ From ce5c1d70469dda0c5c1949f81de0ff76c4c9795a Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:33:13 +0300 Subject: [PATCH 076/137] % prepare code for zend_call_destructors --- Zend/zend.c | 10 ++++++++++ Zend/zend_async_API.c | 5 ++++- Zend/zend_async_API.h | 6 +++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index 580a765ae5ba0..3338976ce623c 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1335,7 +1335,11 @@ ZEND_API void zend_activate(void) /* {{{ */ void zend_call_destructors(void) /* {{{ */ { zend_try { +#ifdef PHP_ASYNC_API + async_shutdown_destructors(); +#else shutdown_destructors(); +#endif } zend_end_try(); } /* }}} */ @@ -1352,6 +1356,12 @@ ZEND_API void zend_deactivate(void) /* {{{ */ /* shutdown_executor() takes care of its own bailout handling */ shutdown_executor(); +#ifdef PHP_ASYNC_API + // The execution of the True Async API should end here, + // after the GC has been run. + ZEND_ASYNC_ENGINE_SHUTDOWN(); +#endif + zend_try { zend_ini_deactivate(); } zend_end_try(); diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 0ede7b8a72d9d..b597163b5e5eb 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -82,6 +82,7 @@ zend_async_resume_t zend_async_resume_fn = NULL; zend_async_cancel_t zend_async_cancel_fn = NULL; zend_async_spawn_and_throw_t zend_async_spawn_and_throw_fn = spawn_and_throw; zend_async_shutdown_t zend_async_shutdown_fn = NULL; +zend_async_engine_shutdown_t zend_async_engine_shutdown_fn = NULL; zend_async_get_coroutines_t zend_async_get_coroutines_fn = NULL; zend_async_add_microtask_t zend_async_add_microtask_fn = NULL; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = NULL; @@ -215,7 +216,8 @@ ZEND_API bool zend_async_scheduler_register( zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, zend_async_get_class_ce_t get_class_ce_fn, - zend_async_new_iterator_t new_iterator_fn + zend_async_new_iterator_t new_iterator_fn, + zend_async_engine_shutdown_t engine_shutdown_fn ) { if (zend_atomic_bool_exchange(&scheduler_lock, 1)) { @@ -252,6 +254,7 @@ ZEND_API bool zend_async_scheduler_register( zend_async_get_awaiting_info_fn = get_awaiting_info_fn; zend_async_get_class_ce_fn = get_class_ce_fn; zend_async_new_iterator_fn = new_iterator_fn; + zend_async_engine_shutdown_fn = engine_shutdown_fn; zend_atomic_bool_store(&scheduler_lock, 0); diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 21ad12f974358..c03a745f5ecfc 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -193,6 +193,7 @@ typedef void (*zend_async_resume_t)(zend_coroutine_t *coroutine, zend_object * e typedef void (*zend_async_cancel_t)(zend_coroutine_t *coroutine, zend_object * error, bool transfer_error, const bool is_safely); typedef bool (*zend_async_spawn_and_throw_t)(zend_object *exception, zend_async_scope_t *scope, int32_t priority); typedef void (*zend_async_shutdown_t)(void); +typedef void (*zend_async_engine_shutdown_t)(void); typedef zend_array* (*zend_async_get_coroutines_t)(void); typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); @@ -1113,6 +1114,7 @@ ZEND_API extern zend_async_resume_t zend_async_resume_fn; ZEND_API extern zend_async_cancel_t zend_async_cancel_fn; ZEND_API extern zend_async_spawn_and_throw_t zend_async_spawn_and_throw_fn; ZEND_API extern zend_async_shutdown_t zend_async_shutdown_fn; +ZEND_API extern zend_async_engine_shutdown_t zend_async_engine_shutdown_fn; ZEND_API extern zend_async_get_coroutines_t zend_async_get_coroutines_fn; ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; @@ -1190,7 +1192,8 @@ ZEND_API bool zend_async_scheduler_register( zend_async_add_microtask_t add_microtask_fn, zend_async_get_awaiting_info_t get_awaiting_info_fn, zend_async_get_class_ce_t get_class_ce_fn, - zend_async_new_iterator_t new_iterator_fn + zend_async_new_iterator_t new_iterator_fn, + zend_async_engine_shutdown_t engine_shutdown_fn ); ZEND_API bool zend_async_reactor_register( @@ -1330,6 +1333,7 @@ END_EXTERN_C() */ #define ZEND_ASYNC_SPAWN_AND_THROW(exception, scope, priority) zend_async_spawn_and_throw_fn(exception, scope, priority) #define ZEND_ASYNC_SHUTDOWN() zend_async_shutdown_fn() +#define ZEND_ASYNC_ENGINE_SHUTDOWN() zend_async_engine_shutdown_fn() #define ZEND_ASYNC_GET_COROUTINES() zend_async_get_coroutines_fn() #define ZEND_ASYNC_ADD_MICROTASK(microtask) zend_async_add_microtask_fn(microtask) #define ZEND_ASYNC_GET_AWAITING_INFO(coroutine) zend_async_get_awaiting_info_fn(coroutine) From 1f109f669a1a8f8d74953e006c524da038ebea99 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 28 Jun 2025 11:25:34 +0300 Subject: [PATCH 077/137] + shutdown_destructors_async Support for concurrent iteration of the global variable table --- Zend/zend.c | 2 +- Zend/zend_execute.h | 3 ++ Zend/zend_execute_API.c | 114 ++++++++++++++++++++++++++++++++++++++++ Zend/zend_globals.h | 12 +++++ main/main.c | 4 ++ 5 files changed, 134 insertions(+), 1 deletion(-) diff --git a/Zend/zend.c b/Zend/zend.c index 3338976ce623c..d52459a204d17 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1336,7 +1336,7 @@ void zend_call_destructors(void) /* {{{ */ { zend_try { #ifdef PHP_ASYNC_API - async_shutdown_destructors(); + shutdown_destructors_async(); #else shutdown_destructors(); #endif diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index cf15c9e3b2db5..6b3bc50256a59 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -40,6 +40,9 @@ ZEND_API extern zend_class_entry *(*zend_autoload)(zend_string *name, zend_strin void init_executor(void); void shutdown_executor(void); void shutdown_destructors(void); +#ifdef PHP_ASYNC_API +void shutdown_destructors_async(void); +#endif ZEND_API void zend_shutdown_executor_values(bool fast_shutdown); ZEND_API void zend_init_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 9a7803e44e66e..362432e9fa4b4 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -199,6 +199,13 @@ void init_executor(void) /* {{{ */ EG(filename_override) = NULL; EG(lineno_override) = -1; +#ifdef PHP_ASYNC_API + EG(shutdown_context) = (zend_shutdown_context_t) { + .coroutine = NULL, + .idx = 0 + }; +#endif + zend_max_execution_timer_init(); zend_fiber_init(); zend_weakrefs_init(); @@ -267,6 +274,113 @@ void shutdown_destructors(void) /* {{{ */ } /* }}} */ +#ifdef PHP_ASYNC_API +#include "zend_async_API.h" + +static void shutdown_destructors_coroutine_dtor(zend_coroutine_t *coroutine) +{ + zend_shutdown_context_t *shutdown_context = &EG(shutdown_context); + + if (shutdown_context->coroutine == coroutine) { + shutdown_context->coroutine = NULL; + zend_error(E_CORE_ERROR, "Shutdown destructors coroutine was not finished property"); + EG(symbol_table).pDestructor = zend_unclean_zval_ptr_dtor; + shutdown_destructors(); + } +} + +static bool shutdown_destructors_context_switch_handler( + zend_coroutine_t *coroutine, + bool is_enter, + bool is_finishing +) { + zend_coroutine_t *shutdown_coroutine = ZEND_ASYNC_NEW_COROUTINE(ZEND_ASYNC_MAIN_SCOPE); + + shutdown_coroutine->internal_entry = shutdown_destructors_async; + shutdown_coroutine->extended_dispose = shutdown_destructors_coroutine_dtor; + + return false; +} + +void shutdown_destructors_async(void) /* {{{ */ +{ + zend_coroutine_t *coroutine = ZEND_ASYNC_CURRENT_COROUTINE; + bool should_continue = false; + + zend_shutdown_context_t *shutdown_context = &EG(shutdown_context); + + if (coroutine == NULL) { + ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(shutdown_destructors_context_switch_handler); + } else { + ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, shutdown_destructors_context_switch_handler); + } + + HashTable *symbol_table = &EG(symbol_table); + + if (shutdown_context->coroutine == NULL) { + shutdown_context->coroutine = coroutine; + shutdown_context->num_elements = zend_hash_num_elements(symbol_table); + shutdown_context->idx = symbol_table->nNumUsed; + } + + if (CG(unclean_shutdown)) { + EG(symbol_table).pDestructor = zend_unclean_zval_ptr_dtor; + } + + zend_try { + do { + if (should_continue) { + shutdown_context->num_elements = zend_hash_num_elements(symbol_table); + shutdown_context->idx = symbol_table->nNumUsed; + } else { + should_continue = true; + } + + while (shutdown_context->idx > 0) { + + shutdown_context->idx--; + + Bucket *p = symbol_table->arData + shutdown_context->idx; + + if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) { + continue; + } + + zval *zv = &p->val; + if (Z_TYPE_P(zv) == IS_INDIRECT) { + zv = Z_INDIRECT_P(zv); + } + + if (Z_TYPE_P(zv) == IS_OBJECT && Z_REFCOUNT_P(zv) == 1) { + zend_hash_del_bucket(symbol_table, p); + } + + // If the coroutine has changed + if (coroutine != ZEND_ASYNC_CURRENT_COROUTINE) { + should_continue = false; + break; + } + } + + if (false == should_continue) { + break; + } + + } while (shutdown_context->num_elements != zend_hash_num_elements(symbol_table)); + + if (should_continue) { + shutdown_context->coroutine = NULL; + zend_objects_store_call_destructors(&EG(objects_store)); + } + } zend_catch { + EG(symbol_table).pDestructor = zend_unclean_zval_ptr_dtor; + shutdown_destructors(); + zend_bailout(); + } zend_end_try(); +} +/* }}} */ +#endif + /* Free values held by the executor. */ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) { diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 079bfb99caccf..e6ae7ef9a7d58 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -163,6 +163,13 @@ struct _zend_compiler_globals { #endif }; +#ifdef PHP_ASYNC_API +typedef struct { + void *coroutine; + uint32_t num_elements; + uint32_t idx; +} zend_shutdown_context_t; +#endif struct _zend_executor_globals { zval uninitialized_zval; @@ -259,6 +266,11 @@ struct _zend_executor_globals { const zend_op *opline_before_exception; zend_op exception_op[3]; +#ifdef PHP_ASYNC_API + // Used to track the state of shutdown destructors in coroutines + zend_shutdown_context_t shutdown_context; +#endif + struct _zend_module_entry *current_module; bool active; diff --git a/main/main.c b/main/main.c index cf7544d8ae500..3102ca129a171 100644 --- a/main/main.c +++ b/main/main.c @@ -1935,6 +1935,10 @@ void php_request_shutdown(void *dummy) zend_call_destructors(); } zend_end_try(); +#ifdef PHP_ASYNC_API + ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN(); +#endif + /* 3. Flush all output buffers */ zend_try { php_output_end_all(); From 31999c5662b88369cca82af18d6cff63b26f78c7 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 28 Jun 2025 13:54:15 +0300 Subject: [PATCH 078/137] + shutdown_destructors_async Support for concurrent iteration of the global variable table --- Zend/zend_execute_API.c | 17 ++++++-- Zend/zend_globals.h | 1 + Zend/zend_objects_API.c | 89 +++++++++++++++++++++++++++++++++++++++++ Zend/zend_objects_API.h | 3 ++ 4 files changed, 106 insertions(+), 4 deletions(-) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 362432e9fa4b4..1d39b5a334da7 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -201,7 +201,7 @@ void init_executor(void) /* {{{ */ #ifdef PHP_ASYNC_API EG(shutdown_context) = (zend_shutdown_context_t) { - .coroutine = NULL, + .is_started = NULL, .idx = 0 }; #endif @@ -294,8 +294,15 @@ static bool shutdown_destructors_context_switch_handler( bool is_enter, bool is_finishing ) { - zend_coroutine_t *shutdown_coroutine = ZEND_ASYNC_NEW_COROUTINE(ZEND_ASYNC_MAIN_SCOPE); + if (is_enter) { + return true; + } + + if (is_finishing) { + return false; + } + zend_coroutine_t *shutdown_coroutine = ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(ZEND_ASYNC_MAIN_SCOPE, 1); shutdown_coroutine->internal_entry = shutdown_destructors_async; shutdown_coroutine->extended_dispose = shutdown_destructors_coroutine_dtor; @@ -317,7 +324,8 @@ void shutdown_destructors_async(void) /* {{{ */ HashTable *symbol_table = &EG(symbol_table); - if (shutdown_context->coroutine == NULL) { + if (false == shutdown_context->is_started) { + shutdown_context->is_started = true; shutdown_context->coroutine = coroutine; shutdown_context->num_elements = zend_hash_num_elements(symbol_table); shutdown_context->idx = symbol_table->nNumUsed; @@ -369,8 +377,9 @@ void shutdown_destructors_async(void) /* {{{ */ } while (shutdown_context->num_elements != zend_hash_num_elements(symbol_table)); if (should_continue) { + shutdown_context->is_started = false; shutdown_context->coroutine = NULL; - zend_objects_store_call_destructors(&EG(objects_store)); + zend_objects_store_call_destructors_async(&EG(objects_store)); } } zend_catch { EG(symbol_table).pDestructor = zend_unclean_zval_ptr_dtor; diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index e6ae7ef9a7d58..968056621d9a0 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -165,6 +165,7 @@ struct _zend_compiler_globals { #ifdef PHP_ASYNC_API typedef struct { + bool is_started; void *coroutine; uint32_t num_elements; uint32_t idx; diff --git a/Zend/zend_objects_API.c b/Zend/zend_objects_API.c index c19873cf3be30..043a3dfe747e6 100644 --- a/Zend/zend_objects_API.c +++ b/Zend/zend_objects_API.c @@ -23,6 +23,8 @@ #include "zend_variables.h" #include "zend_API.h" #include "zend_objects_API.h" + +#include "zend_async_API.h" #include "zend_fibers.h" ZEND_API void ZEND_FASTCALL zend_objects_store_init(zend_objects_store *objects, uint32_t init_size) @@ -63,6 +65,93 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_sto } } +#ifdef PHP_ASYNC_API +static void store_call_destructors_coroutine_dtor(zend_coroutine_t *coroutine) +{ + zend_shutdown_context_t *shutdown_context = &EG(shutdown_context); + + if (shutdown_context->coroutine == coroutine) { + shutdown_context->coroutine = NULL; + zend_error(E_CORE_ERROR, "Shutdown destructors coroutine was not finished property"); + shutdown_destructors(); + } +} + +static void store_call_destructors_entry(void) +{ + zend_objects_store_call_destructors_async(&EG(objects_store)); +} + +static bool store_call_destructors_context_switch_handler( + zend_coroutine_t *coroutine, + bool is_enter, + bool is_finishing +) { + if (is_enter) { + return true; + } + + if (is_finishing) { + return false; + } + + zend_coroutine_t *shutdown_coroutine = ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(ZEND_ASYNC_MAIN_SCOPE, 1); + shutdown_coroutine->internal_entry = store_call_destructors_entry; + shutdown_coroutine->extended_dispose = store_call_destructors_coroutine_dtor; + + return false; +} + +ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors_async(zend_objects_store *objects) +{ + if (objects->top <= 1) { + return; + } + + EG(flags) |= EG_FLAGS_OBJECT_STORE_NO_REUSE; + + zend_class_entry *coroutine_ce = ZEND_ASYNC_GET_CE(ZEND_ASYNC_CLASS_COROUTINE); + + zend_coroutine_t *coroutine = ZEND_ASYNC_CURRENT_COROUTINE; + + zend_shutdown_context_t *shutdown_context = &EG(shutdown_context); + + if (coroutine == NULL) { + ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(store_call_destructors_context_switch_handler); + } else { + ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, store_call_destructors_context_switch_handler); + } + + if (false == shutdown_context->is_started) { + shutdown_context->is_started = true; + shutdown_context->coroutine = coroutine; + shutdown_context->idx = 1; + } + + for (uint32_t i = shutdown_context->idx; i < objects->top; i++) { + zend_object *obj = objects->object_buckets[i]; + if (IS_OBJ_VALID(obj) && obj->ce != coroutine_ce) { + if (!(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { + GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + + if (obj->handlers->dtor_obj != zend_objects_destroy_object || obj->ce->destructor) { + shutdown_context->idx = i; + GC_ADDREF(obj); + obj->handlers->dtor_obj(obj); + GC_DELREF(obj); + + if (coroutine != ZEND_ASYNC_CURRENT_COROUTINE) { + return; + } + } + } + } + } + + shutdown_context->is_started = false; +} +#endif + ZEND_API void ZEND_FASTCALL zend_objects_store_mark_destructed(zend_objects_store *objects) { if (objects->object_buckets && objects->top > 1) { diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h index 242bf212ba9c6..2e4bbb50cfe35 100644 --- a/Zend/zend_objects_API.h +++ b/Zend/zend_objects_API.h @@ -55,6 +55,9 @@ typedef struct _zend_objects_store { BEGIN_EXTERN_C() ZEND_API void ZEND_FASTCALL zend_objects_store_init(zend_objects_store *objects, uint32_t init_size); ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_store *objects); +#ifdef PHP_ASYNC_API +ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors_async(zend_objects_store *objects); +#endif ZEND_API void ZEND_FASTCALL zend_objects_store_mark_destructed(zend_objects_store *objects); ZEND_API void ZEND_FASTCALL zend_objects_store_free_object_storage(zend_objects_store *objects, bool fast_shutdown); ZEND_API void ZEND_FASTCALL zend_objects_store_destroy(zend_objects_store *objects); From 36ab8a91ba98b0f5a794d83461288b81a2a14763 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 28 Jun 2025 23:45:58 +0300 Subject: [PATCH 079/137] + shutdown_destructors_async Support for concurrent iteration of the global variable table --- main/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/main/main.c b/main/main.c index 3102ca129a171..a687a03714c26 100644 --- a/main/main.c +++ b/main/main.c @@ -2615,6 +2615,7 @@ PHPAPI bool php_execute_script_ex(zend_file_handle *primary_file, zval *retval) } #ifdef PHP_ASYNC_API ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN(); + ZEND_ASYNC_INITIALIZE; #endif } zend_catch { result = false; From 818cd16f4768d8202a5a7033f7629b64ece20da2 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 29 Jun 2025 15:05:26 +0300 Subject: [PATCH 080/137] * fix issue with structure initialization --- Zend/zend_execute_API.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 1d39b5a334da7..d6d9a7c2b0dc2 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -201,7 +201,9 @@ void init_executor(void) /* {{{ */ #ifdef PHP_ASYNC_API EG(shutdown_context) = (zend_shutdown_context_t) { - .is_started = NULL, + .is_started = false, + .coroutine = NULL, + .num_elements = 0, .idx = 0 }; #endif From 1fa4663143ec672b67130cf16a82840d5c9cbcde Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 30 Jun 2025 12:55:58 +0300 Subject: [PATCH 081/137] * Change --enable-experimental-async-api to --disable-async-api. --- configure.ac | 17 +++++++++-------- win32/build/config.w32 | 5 ++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/configure.ac b/configure.ac index 5386e9509ae80..cbb736ae23e87 100644 --- a/configure.ac +++ b/configure.ac @@ -1052,15 +1052,16 @@ PHP_ARG_ENABLE([undefined-sanitizer],, [Enable undefined sanitizer])], [no], [no]) -PHP_ARG_ENABLE([experimental-async-api],, - [AS_HELP_STRING([--enable-experimental-async-api], - [Enable experimental async API support])], - [no], +PHP_ARG_ENABLE([async-api],, + [AS_HELP_STRING([--disable-async-api], + [Disable async API support])], + [yes], [no]) -if test "$PHP_EXPERIMENTAL_ASYNC_API" = "yes"; then - AC_DEFINE([PHP_ASYNC_API], 1, [Enable async API support]) -fi +AS_VAR_IF([PHP_ASYNC_API], [yes], [ + AC_DEFINE([PHP_ASYNC_API], [1], [Define to 1 if async API support is enabled.]) +]) + dnl Extension configuration. dnl ---------------------------------------------------------------------------- @@ -1801,7 +1802,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ ]), [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $fiber_asm_cflag]) -if test "$PHP_EXPERIMENTAL_ASYNC_API" = "yes"; then +if test "$PHP_ASYNC_API" = "yes"; then PHP_ADD_SOURCES([Zend], m4_normalize([zend_async_API.c]), [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $fiber_asm_cflag]) fi diff --git a/win32/build/config.w32 b/win32/build/config.w32 index cf69a83fe9bf5..c16700209277f 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -408,9 +408,8 @@ ARG_ENABLE('snapshot-build', 'Build a snapshot: turns on everything it can and i ARG_ENABLE('vs-link-compat', 'Allow linking of libraries built with compatible versions of VS toolset', 'yes'); -ARG_ENABLE("experimental-async-api", "Enable experimental async API support", "no"); +ARG_ENABLE("async-api", "Disable async API support", "yes"); -if (PHP_EXPERIMENTAL_ASYNC_API == "yes") { - AC_DEFINE('PHP_ASYNC_API', 1, 'Enable experimental async API support'); +if (PHP_ASYNC_API == "yes") { ADD_SOURCES("Zend", "zend_async_API.c"); } \ No newline at end of file From e8fb89d36d48e3d0827e7d6b21cd51f255eddbf5 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:16:22 +0300 Subject: [PATCH 082/137] * fix bug with PHP_ASYNC_API --- win32/build/config.w32 | 1 + 1 file changed, 1 insertion(+) diff --git a/win32/build/config.w32 b/win32/build/config.w32 index c16700209277f..8771bf7fb0cec 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -411,5 +411,6 @@ ARG_ENABLE('vs-link-compat', 'Allow linking of libraries built with compatible v ARG_ENABLE("async-api", "Disable async API support", "yes"); if (PHP_ASYNC_API == "yes") { + AC_DEFINE('PHP_ASYNC_API', 1); ADD_SOURCES("Zend", "zend_async_API.c"); } \ No newline at end of file From e054b53a7d59a461939789886f576312620f6f06 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:43:56 +0300 Subject: [PATCH 083/137] % change Trye Async API version to 0.2 --- Zend/zend_async_API.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index c03a745f5ecfc..eaf7637d81a84 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -19,9 +19,9 @@ #include "zend_fibers.h" #include "zend_globals.h" -#define ZEND_ASYNC_API "TrueAsync API v1.0.0-dev" -#define ZEND_ASYNC_API_VERSION_MAJOR 1 -#define ZEND_ASYNC_API_VERSION_MINOR 0 +#define ZEND_ASYNC_API "TrueAsync API v0.2.0" +#define ZEND_ASYNC_API_VERSION_MAJOR 0 +#define ZEND_ASYNC_API_VERSION_MINOR 2 #define ZEND_ASYNC_API_VERSION_PATCH 0 #define ZEND_ASYNC_API_VERSION_NUMBER \ From 2f99cee8e3c2ce445ddebac07151abf5eb488e28 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:42:20 +0300 Subject: [PATCH 084/137] * fix issue with API stub --- Zend/zend_async_API.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index b597163b5e5eb..071acefbcf5c7 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -42,6 +42,16 @@ static void enqueue_coroutine(zend_coroutine_t *coroutine) ASYNC_THROW_ERROR("Async API is not enabled"); } +static void engine_shutdown_stub(void) {} + +static void shutdown_stub(void) {} + +static zend_array* get_coroutines_stub(void) { return NULL; } + +static void add_microtask_stub(zend_async_microtask_t *microtask) {} + +static zend_array* get_awaiting_info_stub(zend_coroutine_t *coroutine) { return NULL; } + static bool spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, int32_t priority) { ASYNC_THROW_ERROR("Async API is not enabled"); @@ -81,11 +91,11 @@ zend_async_enqueue_coroutine_t zend_async_enqueue_coroutine_fn = enqueue_corouti zend_async_resume_t zend_async_resume_fn = NULL; zend_async_cancel_t zend_async_cancel_fn = NULL; zend_async_spawn_and_throw_t zend_async_spawn_and_throw_fn = spawn_and_throw; -zend_async_shutdown_t zend_async_shutdown_fn = NULL; -zend_async_engine_shutdown_t zend_async_engine_shutdown_fn = NULL; -zend_async_get_coroutines_t zend_async_get_coroutines_fn = NULL; -zend_async_add_microtask_t zend_async_add_microtask_fn = NULL; -zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = NULL; +zend_async_shutdown_t zend_async_shutdown_fn = shutdown_stub; +zend_async_engine_shutdown_t zend_async_engine_shutdown_fn = engine_shutdown_stub; +zend_async_get_coroutines_t zend_async_get_coroutines_fn = get_coroutines_stub; +zend_async_add_microtask_t zend_async_add_microtask_fn = add_microtask_stub; +zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = get_awaiting_info_stub; zend_async_get_class_ce_t zend_async_get_class_ce_fn = get_class_ce; static zend_atomic_bool reactor_lock = {0}; From 79459906698dbbe1f082a83a3537161ee74a10ca Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:50:57 +0300 Subject: [PATCH 085/137] fix: add explicit pointer casts for C++ compatibility in async API --- Zend/zend_async_API.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index eaf7637d81a84..807504583e7cf 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -561,7 +561,7 @@ static zend_always_inline void zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t *callback) { if (event->callbacks.data == NULL) { - event->callbacks.data = safe_emalloc(4, sizeof(zend_async_event_callback_t *), 0); + event->callbacks.data = (zend_async_event_callback_t **)safe_emalloc(4, sizeof(zend_async_event_callback_t *), 0); event->callbacks.capacity = 4; } @@ -569,7 +569,7 @@ zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t if (vector->length == vector->capacity) { vector->capacity = vector->capacity ? vector->capacity * 2 : 4; - vector->data = safe_erealloc(vector->data, + vector->data = (zend_async_event_callback_t **)safe_erealloc(vector->data, vector->capacity, sizeof(zend_async_event_callback_t *), 0); @@ -770,13 +770,13 @@ zend_async_scope_add_child(zend_async_scope_t *parent_scope, zend_async_scope_t child_scope->parent_scope = parent_scope; if (vector->data == NULL) { - vector->data = safe_emalloc(4, sizeof(zend_async_scope_t *), 0); + vector->data = (zend_async_scope_t **)safe_emalloc(4, sizeof(zend_async_scope_t *), 0); vector->capacity = 4; } if (vector->length == vector->capacity) { vector->capacity *= 2; - vector->data = safe_erealloc(vector->data, vector->capacity, sizeof(zend_async_scope_t *), 0); + vector->data = (zend_async_scope_t **)safe_erealloc(vector->data, vector->capacity, sizeof(zend_async_scope_t *), 0); } vector->data[vector->length++] = child_scope; From 8fca959787db611873cc6a703ea7dc723a99267d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:56:11 +0300 Subject: [PATCH 086/137] * Fixed an issue with incorrect cleanup of coroutine interceptor handler state. --- Zend/zend_async_API.c | 14 ++++++++++++++ Zend/zend_async_API.h | 6 ++++++ Zend/zend_execute_API.c | 10 ++++++++++ Zend/zend_objects_API.c | 7 +++++++ 4 files changed, 37 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 071acefbcf5c7..1b98f4a64b8a6 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -1283,6 +1283,13 @@ ZEND_API uint32_t zend_coroutine_add_switch_handler( return 0; } + /* Check for duplicate handler */ + for (uint32_t i = 0; i < vector->length; i++) { + if (vector->data[i].handler == handler) { + return i; + } + } + /* Expand array if needed */ if (vector->length == vector->capacity) { vector->capacity = vector->capacity ? vector->capacity * 2 : 4; @@ -1384,6 +1391,13 @@ ZEND_API void zend_async_add_main_coroutine_start_handler( { zend_coroutine_switch_handlers_vector_t *vector = &global_main_coroutine_start_handlers; + /* Check for duplicate handler */ + for (uint32_t i = 0; i < vector->length; i++) { + if (vector->data[i].handler == handler) { + return; + } + } + /* Expand vector if needed */ if (vector->length >= vector->capacity) { uint32_t new_capacity = vector->capacity ? vector->capacity * 2 : 4; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 807504583e7cf..8df5b26fa40cd 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1423,5 +1423,11 @@ END_EXTERN_C() /* Global Main Coroutine Switch Handlers API Macros */ #define ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler) zend_async_add_main_coroutine_start_handler(handler) +#define ZEND_ASYNC_ADD_SWITCH_HANDLER(handler) \ + if (ZEND_ASYNC_CURRENT_COROUTINE) { \ + zend_coroutine_add_switch_handler(ZEND_ASYNC_CURRENT_COROUTINE, handler); \ + } else { \ + zend_async_add_main_coroutine_start_handler(handler); \ + } #endif //ZEND_ASYNC_API_H \ No newline at end of file diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index d6d9a7c2b0dc2..6361dae8b0282 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -285,6 +285,7 @@ static void shutdown_destructors_coroutine_dtor(zend_coroutine_t *coroutine) if (shutdown_context->coroutine == coroutine) { shutdown_context->coroutine = NULL; + shutdown_context->is_started = false; zend_error(E_CORE_ERROR, "Shutdown destructors coroutine was not finished property"); EG(symbol_table).pDestructor = zend_unclean_zval_ptr_dtor; shutdown_destructors(); @@ -304,6 +305,12 @@ static bool shutdown_destructors_context_switch_handler( return false; } + zend_shutdown_context_t *shutdown_context = &EG(shutdown_context); + + if (false == shutdown_context->is_started) { + return false; + } + zend_coroutine_t *shutdown_coroutine = ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(ZEND_ASYNC_MAIN_SCOPE, 1); shutdown_coroutine->internal_entry = shutdown_destructors_async; shutdown_coroutine->extended_dispose = shutdown_destructors_coroutine_dtor; @@ -385,9 +392,12 @@ void shutdown_destructors_async(void) /* {{{ */ } } zend_catch { EG(symbol_table).pDestructor = zend_unclean_zval_ptr_dtor; + shutdown_context->is_started = false; shutdown_destructors(); zend_bailout(); } zend_end_try(); + + shutdown_context->is_started = false; } /* }}} */ #endif diff --git a/Zend/zend_objects_API.c b/Zend/zend_objects_API.c index 043a3dfe747e6..08d3997ab8a3a 100644 --- a/Zend/zend_objects_API.c +++ b/Zend/zend_objects_API.c @@ -72,6 +72,7 @@ static void store_call_destructors_coroutine_dtor(zend_coroutine_t *coroutine) if (shutdown_context->coroutine == coroutine) { shutdown_context->coroutine = NULL; + shutdown_context->is_started = false; zend_error(E_CORE_ERROR, "Shutdown destructors coroutine was not finished property"); shutdown_destructors(); } @@ -95,6 +96,12 @@ static bool store_call_destructors_context_switch_handler( return false; } + zend_shutdown_context_t *shutdown_context = &EG(shutdown_context); + + if (false == shutdown_context->is_started) { + return false; + } + zend_coroutine_t *shutdown_coroutine = ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(ZEND_ASYNC_MAIN_SCOPE, 1); shutdown_coroutine->internal_entry = store_call_destructors_entry; shutdown_coroutine->extended_dispose = store_call_destructors_coroutine_dtor; From 9645e1e37c01ea54bdeb486e38e0c8737997dddf Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 2 Jul 2025 12:49:38 +0300 Subject: [PATCH 087/137] #6: * Fixed GC behavior in the context of coroutines. Infinite garbage collection loop issue resolved. --- Zend/zend_async_API.h | 20 ++++++++++++++++++++ Zend/zend_gc.c | 5 +++++ 2 files changed, 25 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 8df5b26fa40cd..ee3fe214c5845 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -294,6 +294,26 @@ struct _zend_async_microtask_s { uint32_t ref_count; }; +#define ZEND_ASYNC_MICROTASK_ADD_REF(microtask) \ + do { \ + if (microtask != NULL) { \ + microtask->ref_count++; \ + } \ + } while (0) + +#define ZEND_ASYNC_MICROTASK_RELEASE(microtask) \ + do { \ + if (microtask != NULL && microtask->ref_count > 1) { \ + microtask->ref_count--; \ + } else { \ + microtask->ref_count = 0; \ + if (microtask->dtor) { \ + microtask->dtor(microtask); \ + } \ + efree(microtask); \ + } \ + } while (0) + /////////////////////////////////////////////////////////////////// /// Async iterator structures /////////////////////////////////////////////////////////////////// diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 11621782e32d7..95a33e3fc3dcc 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -1975,6 +1975,11 @@ static void zend_gc_collect_cycles_coroutine(void) ZEND_ASYNC_ADD_MICROTASK(GC_G(microtask)); zend_gc_collect_cycles(); + + if (GC_G(microtask) != NULL) { + GC_G(microtask)->is_cancelled = true; + ZEND_ASYNC_MICROTASK_RELEASE(GC_G(microtask)); + } } static zend_string* coroutine_info(zend_async_event_t *event) From 9f3bd239270b6d576d8b2785d708b1b566544c56 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:40:04 +0300 Subject: [PATCH 088/137] #7: Refactoring of Zend GC logic for coroutines. Added GC context for traversing nodes. Edge cases fixed. --- Zend/zend_gc.c | 233 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 200 insertions(+), 33 deletions(-) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 95a33e3fc3dcc..1d07287d6c29f 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -251,6 +251,39 @@ typedef struct _gc_root_buffer { zend_refcounted *ref; } gc_root_buffer; +typedef struct _gc_stack gc_stack; + +#define GC_STACK_SEGMENT_SIZE (((4096 - ZEND_MM_OVERHEAD) / sizeof(void*)) - 2) + +struct _gc_stack { + gc_stack *prev; + gc_stack *next; + zend_refcounted *data[GC_STACK_SEGMENT_SIZE]; +}; + +#ifdef PHP_ASYNC_API + +typedef enum { + GC_ASYNC_STATE_NONE = 0, + GC_ASYNC_STATE_INIT, // initial state + GC_ASYNC_STATE_RUNNING, // GC is running + GC_ASYNC_STATE_CONTINUE // GC is called from GC coroutine +} gc_async_state_t; + +typedef struct { + gc_async_state_t state; // state + int total_count; // total freed objects in this GC run + int count; // freed objects in this GC run + + uint32_t gc_flags; // GC_HAS_DESTRUCTORS, etc. + bool should_rerun_gc; // run GC again after destructors + bool did_rerun_gc; // guard against endless reruns + + zend_hrtime_t start_time; // start of full GC pass + zend_hrtime_t dtor_start_time; // start of dtor phase +} gc_async_context_t; +#endif + typedef struct _zend_gc_globals { gc_root_buffer *buf; /* preallocated arrays of buffers */ @@ -278,9 +311,11 @@ typedef struct _zend_gc_globals { zend_fiber *dtor_fiber; bool dtor_fiber_running; #ifdef PHP_ASYNC_API - zend_coroutine_t * dtor_coroutine; - zend_async_scope_t * dtor_scope; - zend_async_microtask_t * microtask; + gc_async_context_t async_context; /* async context for gc */ + gc_stack *gc_stack; /* local mark/scan stack */ + zend_coroutine_t *dtor_coroutine; + zend_async_scope_t *dtor_scope; + zend_async_microtask_t *microtask; #endif #if GC_BENCH @@ -316,17 +351,6 @@ static zend_gc_globals gc_globals; # define GC_BENCH_PEAK(peak, counter) #endif - -#define GC_STACK_SEGMENT_SIZE (((4096 - ZEND_MM_OVERHEAD) / sizeof(void*)) - 2) - -typedef struct _gc_stack gc_stack; - -struct _gc_stack { - gc_stack *prev; - gc_stack *next; - zend_refcounted *data[GC_STACK_SEGMENT_SIZE]; -}; - #define GC_STACK_DCL(init) \ gc_stack *_stack = init; \ size_t _top = 0; @@ -516,6 +540,8 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals) gc_globals->dtor_coroutine = NULL; gc_globals->dtor_scope = NULL; gc_globals->microtask = NULL; + gc_globals->async_context.state = GC_ASYNC_STATE_NONE; + gc_globals->gc_stack = NULL; #endif #if GC_BENCH @@ -542,6 +568,12 @@ void gc_globals_dtor(void) #ifndef ZTS root_buffer_dtor(&gc_globals); #endif + +#ifdef PHP_ASYNC_API + if (GC_G(dtor_scope)) { + GC_G(dtor_scope) = NULL; + } +#endif } void gc_reset(void) @@ -1973,9 +2005,34 @@ static void zend_gc_collect_cycles_coroutine(void) GC_G(microtask) = task; } + if (GC_ASYNC_STATE_NONE == GC_G(async_context).state) { + gc_async_context_t *context = &GC_G(async_context); + context->state = GC_ASYNC_STATE_INIT; + context->total_count = 0; + context->count = 0; + context->start_time = zend_hrtime(); + context->should_rerun_gc = 0; + context->did_rerun_gc = 0; + context->gc_flags = 0; + } else if (GC_G(async_context).state == GC_ASYNC_STATE_RUNNING) { + GC_G(async_context).state = GC_ASYNC_STATE_CONTINUE; + } + + if (GC_G(gc_stack) == NULL) { + gc_stack *stack = ecalloc(1, sizeof(gc_stack)); + stack->prev = NULL; + stack->next = NULL; + GC_G(gc_stack) = stack; + } + ZEND_ASYNC_ADD_MICROTASK(GC_G(microtask)); zend_gc_collect_cycles(); + // Coroutines were separated. + if (GC_G(dtor_coroutine) != ZEND_ASYNC_CURRENT_COROUTINE) { + return; + } + if (GC_G(microtask) != NULL) { GC_G(microtask)->is_cancelled = true; ZEND_ASYNC_MICROTASK_RELEASE(GC_G(microtask)); @@ -2006,11 +2063,30 @@ static void coroutine_dispose(zend_coroutine_t *coroutine) if (coroutine == GC_G(dtor_coroutine)) { GC_TRACE("GC coroutine finished"); GC_G(dtor_coroutine) = NULL; + GC_G(dtor_scope) = NULL; if (GC_G(microtask) != NULL) { GC_G(microtask)->is_cancelled = true; zend_gc_collect_cycles_microtask_dtor(GC_G(microtask)); } + + if (GC_G(gc_stack) != NULL) { + gc_stack *stack = GC_G(gc_stack); + GC_G(gc_stack) = NULL; + gc_stack_free(stack); + efree(stack); + } + + if (GC_G(async_context.state)) { + gc_async_context_t *context = &GC_G(async_context); + context->state = GC_ASYNC_STATE_NONE; + context->total_count = 0; + context->count = 0; + context->start_time = 0; + context->should_rerun_gc = 0; + context->did_rerun_gc = 0; + context->gc_flags = 0; + } } } @@ -2074,30 +2150,97 @@ ZEND_API int zend_gc_collect_cycles(void) #endif +#ifdef PHP_ASYNC_API +#define GC_COLLECT_TOTAL_COUNT (context->total_count) +#define GC_COLLECT_COUNT (context->count) +#define GC_COLLECT_START_TIME (context->start_time) +#define GC_COLLECT_SHOULD_RERUN_GC (context->should_rerun_gc) +#define GC_COLLECT_DID_RERUN_GC (context->did_rerun_gc) +#define GC_COLLECT_GC_FLAGS (context->gc_flags) +#define GC_COLLECT_STACK (stack) +#define GC_COLLECT_FREE_STACK GC_G(gc_stack) = NULL;\ + gc_stack_free(stack); \ + efree(stack) +#define GC_COLLECT_FINISH_0 GC_G(async_context).state = GC_ASYNC_STATE_NONE; \ + return 0 + + // Someone is trying to invoke GC from within a destructor? + // We don’t know what that is, but we have nothing to do here. + if (UNEXPECTED(GC_G(async_context).state == GC_ASYNC_STATE_RUNNING)) { + return 0; + } + + // + // We might enter this context from different coroutines, so we don’t initialize anything here. + // + gc_async_context_t *context = &GC_G(async_context); + gc_stack *stack = GC_G(gc_stack); + + if (UNEXPECTED(context->state == GC_ASYNC_STATE_CONTINUE)) { + // If we reach this point, it means the destructor call was interrupted by a suspend() operation, + // so we continue the process from the next element. + GC_G(async_context).state = GC_ASYNC_STATE_RUNNING; + GC_G(dtor_idx)++; + goto continue_calling_destructors; + } else { + context->state = GC_ASYNC_STATE_INIT; + context->total_count = 0; + context->count = 0; + context->start_time = zend_hrtime(); + context->should_rerun_gc = 0; + context->did_rerun_gc = 0; + context->gc_flags = 0; + + if (GC_G(gc_stack) == NULL) { + stack = ecalloc(1, sizeof(gc_stack)); + stack->prev = NULL; + stack->next = NULL; + GC_G(gc_stack) = stack; + } else { + stack = GC_G(gc_stack); + } + } + + context->state = GC_ASYNC_STATE_RUNNING; + +#else +#define GC_COLLECT_COUNT count +#define GC_COLLECT_TOTAL_COUNT total_count +#define GC_COLLECT_START_TIME start_time +#define GC_COLLECT_SHOULD_RERUN_GC should_rerun_gc +#define GC_COLLECT_DID_RERUN_GC did_rerun_gc +#define GC_COLLECT_GC_FLAGS gc_flags +#define GC_COLLECT_STACK (&stack) +#define GC_COLLECT_FREE_STACK gc_stack_free(&stack); +#define GC_COLLECT_FINISH_0 return 0 + int total_count = 0; bool should_rerun_gc = 0; bool did_rerun_gc = 0; zend_hrtime_t start_time = zend_hrtime(); +#endif + if (GC_G(num_roots) && !GC_G(gc_active)) { zend_gc_remove_root_tmpvars(); } rerun_gc: if (GC_G(num_roots)) { - int count; gc_root_buffer *current, *last; zend_refcounted *p; uint32_t gc_flags = 0; uint32_t idx, end; +#ifndef PHP_ASYNC_API + int count; gc_stack stack; - stack.prev = NULL; stack.next = NULL; +#endif if (GC_G(gc_active)) { - GC_G(collector_time) += zend_hrtime() - start_time; - return 0; + GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; + GC_COLLECT_FINISH_0; } GC_TRACE("Collecting cycles"); @@ -2105,17 +2248,17 @@ ZEND_API int zend_gc_collect_cycles(void) GC_G(gc_active) = 1; GC_TRACE("Marking roots"); - gc_mark_roots(&stack); + gc_mark_roots(GC_COLLECT_STACK); GC_TRACE("Scanning roots"); - gc_scan_roots(&stack); + gc_scan_roots(GC_COLLECT_STACK); GC_TRACE("Collecting roots"); - count = gc_collect_roots(&gc_flags, &stack); + GC_COLLECT_COUNT = gc_collect_roots(&gc_flags, GC_COLLECT_STACK); if (!GC_G(num_roots)) { /* nothing to free */ GC_TRACE("Nothing to free"); - gc_stack_free(&stack); + GC_COLLECT_FREE_STACK; GC_G(gc_active) = 0; goto finish; } @@ -2130,7 +2273,7 @@ ZEND_API int zend_gc_collect_cycles(void) * modify any refcounts, so we have no real way to detect this situation * short of rerunning full GC tracing. What we do instead is to only run * destructors at this point and automatically re-run GC afterwards. */ - should_rerun_gc = 1; + GC_COLLECT_SHOULD_RERUN_GC = 1; /* Mark all roots for which a dtor will be invoked as DTOR_GARBAGE. Additionally * color them purple. This serves a double purpose: First, they should be @@ -2164,7 +2307,7 @@ ZEND_API int zend_gc_collect_cycles(void) while (idx != end) { if (GC_IS_DTOR_GARBAGE(current->ref)) { p = GC_GET_PTR(current->ref); - count -= gc_remove_nested_data_from_buffer(p, current, &stack); + GC_COLLECT_COUNT -= gc_remove_nested_data_from_buffer(p, current, GC_COLLECT_STACK); } current++; idx++; @@ -2173,7 +2316,21 @@ ZEND_API int zend_gc_collect_cycles(void) /* Actually call destructors. */ zend_hrtime_t dtor_start_time = zend_hrtime(); if (EXPECTED(!EG(active_fiber))) { +#ifdef PHP_ASYNC_API +continue_calling_destructors: + if (UNEXPECTED(FAILURE == gc_call_destructors(GC_G(dtor_idx), GC_G(first_unused), NULL))) { + // + // gc_call_destructors returns FAILURE when a destructor interrupts execution, + // i.e. calls suspend(). + // At this point, we are inside a foreign coroutine from which we must immediately exit, + // because the process continues elsewhere. + // + GC_G(dtor_time) += zend_hrtime() - dtor_start_time; + return GC_COLLECT_TOTAL_COUNT; + } +#else gc_call_destructors(GC_FIRST_ROOT, end, NULL); +#endif } else { gc_call_destructors_in_fiber(end); } @@ -2182,12 +2339,16 @@ ZEND_API int zend_gc_collect_cycles(void) if (GC_G(gc_protected)) { /* something went wrong */ zend_get_gc_buffer_release(); - GC_G(collector_time) += zend_hrtime() - start_time; - return 0; + GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; + GC_COLLECT_FINISH_0; } } - gc_stack_free(&stack); + GC_COLLECT_FREE_STACK; + +#ifdef PHP_ASYNC_API + end = GC_G(first_unused); +#endif /* Destroy zvals. The root buffer may be reallocated. */ GC_TRACE("Destroying zvals"); @@ -2245,8 +2406,8 @@ ZEND_API int zend_gc_collect_cycles(void) GC_G(free_time) += zend_hrtime() - free_start_time; GC_TRACE("Collection finished"); - GC_G(collected) += count; - total_count += count; + GC_G(collected) += GC_COLLECT_COUNT; + GC_COLLECT_TOTAL_COUNT += GC_COLLECT_COUNT; GC_G(gc_active) = 0; } @@ -2255,8 +2416,8 @@ ZEND_API int zend_gc_collect_cycles(void) /* Objects with destructors were removed from this GC run. Rerun GC right away to clean them * up. We do this only once: If we encounter more destructors on the second run, we'll not * run GC another time. */ - if (should_rerun_gc && !did_rerun_gc) { - did_rerun_gc = 1; + if (GC_COLLECT_SHOULD_RERUN_GC && !GC_COLLECT_DID_RERUN_GC) { + GC_COLLECT_DID_RERUN_GC = 1; goto rerun_gc; } @@ -2269,8 +2430,14 @@ ZEND_API int zend_gc_collect_cycles(void) zend_gc_check_root_tmpvars(); GC_G(gc_active) = 0; - GC_G(collector_time) += zend_hrtime() - start_time; - return total_count; + GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; +#ifdef PHP_ASYNC_API + GC_G(async_context).state = GC_ASYNC_STATE_NONE; + if (GC_G(gc_stack) != NULL) { + GC_COLLECT_FREE_STACK; + } +#endif + return GC_COLLECT_TOTAL_COUNT; } ZEND_API void zend_gc_get_status(zend_gc_status *status) From 5802acef51a1d18eafbb8e7b91cbaea62b41a77e Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:03:09 +0300 Subject: [PATCH 089/137] #7: Fixed a bug with incorrect memory release of a microtask. --- Zend/zend_gc.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 1d07287d6c29f..6c220aba4b637 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2034,8 +2034,10 @@ static void zend_gc_collect_cycles_coroutine(void) } if (GC_G(microtask) != NULL) { - GC_G(microtask)->is_cancelled = true; - ZEND_ASYNC_MICROTASK_RELEASE(GC_G(microtask)); + zend_async_microtask_t *microtask = GC_G(microtask); + GC_G(microtask) = NULL; + microtask->is_cancelled = true; + ZEND_ASYNC_MICROTASK_RELEASE(microtask); } } @@ -2066,8 +2068,10 @@ static void coroutine_dispose(zend_coroutine_t *coroutine) GC_G(dtor_scope) = NULL; if (GC_G(microtask) != NULL) { - GC_G(microtask)->is_cancelled = true; - zend_gc_collect_cycles_microtask_dtor(GC_G(microtask)); + zend_async_microtask_t *microtask = GC_G(microtask); + GC_G(microtask) = NULL; + microtask->is_cancelled = true; + ZEND_ASYNC_MICROTASK_RELEASE(microtask); } if (GC_G(gc_stack) != NULL) { From c768c1fe69e5616c777e8fd019b324ac4086aaf6 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:12:09 +0300 Subject: [PATCH 090/137] #7: zend_hrtime_t dtor_start_time Fixed a variable initialization state violation during a goto operation. --- Zend/zend_gc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 6c220aba4b637..2f9e3e468edfb 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2154,6 +2154,8 @@ ZEND_API int zend_gc_collect_cycles(void) #endif + zend_hrtime_t dtor_start_time = 0; + #ifdef PHP_ASYNC_API #define GC_COLLECT_TOTAL_COUNT (context->total_count) #define GC_COLLECT_COUNT (context->count) @@ -2185,6 +2187,7 @@ ZEND_API int zend_gc_collect_cycles(void) // so we continue the process from the next element. GC_G(async_context).state = GC_ASYNC_STATE_RUNNING; GC_G(dtor_idx)++; + dtor_start_time = zend_hrtime(); goto continue_calling_destructors; } else { context->state = GC_ASYNC_STATE_INIT; @@ -2318,7 +2321,7 @@ ZEND_API int zend_gc_collect_cycles(void) } /* Actually call destructors. */ - zend_hrtime_t dtor_start_time = zend_hrtime(); + dtor_start_time = zend_hrtime(); if (EXPECTED(!EG(active_fiber))) { #ifdef PHP_ASYNC_API continue_calling_destructors: From 3a89d8f7a8d0796c50fb6bc16e9cecad028803c8 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 3 Jul 2025 22:53:13 +0300 Subject: [PATCH 091/137] #19: Memory leaks fixed for GC and coroutine tests. --- Zend/zend_fibers.c | 5 ++++- Zend/zend_gc.c | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 97b7cdcc911b7..5c37408e27b41 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -461,11 +461,14 @@ ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context) { zend_observer_fiber_destroy_notify(context); + // This code allows freeing the memory of the context independently of the stack memory. + zend_fiber_stack *stack = context->stack; + if (context->cleanup) { context->cleanup(context); } - zend_fiber_stack_free(context->stack); + zend_fiber_stack_free(stack); } ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 2f9e3e468edfb..ca379b0737b7e 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2238,7 +2238,10 @@ ZEND_API int zend_gc_collect_cycles(void) zend_refcounted *p; uint32_t gc_flags = 0; uint32_t idx, end; -#ifndef PHP_ASYNC_API +#ifdef PHP_ASYNC_API + stack->next = NULL; + stack->prev = NULL; +#else int count; gc_stack stack; stack.prev = NULL; @@ -2351,7 +2354,7 @@ ZEND_API int zend_gc_collect_cycles(void) } } - GC_COLLECT_FREE_STACK; + gc_stack_free(GC_COLLECT_STACK); #ifdef PHP_ASYNC_API end = GC_G(first_unused); From 5fdaf599ff6aac79394f5ae1a4b69d571fa92e6c Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 4 Jul 2025 12:50:55 +0300 Subject: [PATCH 092/137] #19: Changes in the Zend Async API shutdown mechanism --- Zend/zend_async_API.c | 6 ++++-- Zend/zend_async_API.h | 2 +- main/main.c | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 1b98f4a64b8a6..4f16e18fe73d5 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -197,11 +197,13 @@ void zend_async_globals_dtor(void) #endif } -void zend_async_shutdown(void) +void zend_async_api_shutdown(void) { - zend_async_globals_dtor(); zend_async_internal_context_api_shutdown(); zend_async_main_handlers_shutdown(); +#ifndef ZTS + zend_async_globals_dtor(); +#endif } ZEND_API int zend_async_get_api_version_number(void) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index ee3fe214c5845..503bd0125bfa4 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1111,7 +1111,7 @@ ZEND_API bool zend_async_is_enabled(void); ZEND_API bool zend_scheduler_is_enabled(void); void zend_async_init(void); -void zend_async_shutdown(void); +void zend_async_api_shutdown(void); void zend_async_globals_ctor(void); void zend_async_globals_dtor(void); diff --git a/main/main.c b/main/main.c index 061924524c141..e7e55046a1ac1 100644 --- a/main/main.c +++ b/main/main.c @@ -2515,12 +2515,13 @@ void php_module_shutdown(void) module_initialized = false; +#ifdef PHP_ASYNC_API + zend_async_api_shutdown(); +#endif + #ifndef ZTS core_globals_dtor(&core_globals); gc_globals_dtor(); - #ifdef PHP_ASYNC_API - zend_async_globals_dtor(); - #endif #else ts_free_id(core_globals_id); #endif From ac1f0eb1d362b963856bc2307e3cfa065ef16372 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 4 Jul 2025 21:25:15 +0300 Subject: [PATCH 093/137] #19: * The ref_count logic for event callbacks has been improved. Special macros have been added for working with references. --- Zend/zend_async_API.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 503bd0125bfa4..e5b1a49e2f195 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -363,6 +363,30 @@ struct _zend_async_event_callback_s { zend_async_event_callback_dispose_fn dispose; }; +#define ZEND_ASYNC_EVENT_CALLBACK_ADD_REF(callback) \ + if (callback != NULL) { \ + callback->ref_count++; \ + } + +// +// For a callback, +// it’s crucial that the reference count is always greater than zero, +// because a value of zero is a special case triggered from a destructor. +// If you need to “retain” ownership of the object, +// you **MUST** use either this macro or ZEND_ASYNC_EVENT_CALLBACK_RELEASE. +// +#define ZEND_ASYNC_EVENT_CALLBACK_DEC_REF(callback) \ + if(callback != NULL && callback->ref_count > 1) { \ + callback->ref_count--; \ + } + +#define ZEND_ASYNC_EVENT_CALLBACK_RELEASE(callback) \ + if (callback != NULL && callback->ref_count > 1) { \ + callback->ref_count--; \ + } else { \ + coroutine_event_callback_dispose(callback, NULL); \ + } + struct _zend_coroutine_event_callback_s { zend_async_event_callback_t base; zend_coroutine_t *coroutine; From 9d59043c73e93010c0b3b60e26098177fa615e02 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 5 Jul 2025 09:42:05 +0300 Subject: [PATCH 094/137] #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. --- Zend/zend_async_API.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index e5b1a49e2f195..5824e748d4880 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1376,6 +1376,9 @@ END_EXTERN_C() * @param priority Priority level for the exception-throwing coroutine */ #define ZEND_ASYNC_SPAWN_AND_THROW(exception, scope, priority) zend_async_spawn_and_throw_fn(exception, scope, priority) +/** + * The API method initiates graceful shutdown mode. + */ #define ZEND_ASYNC_SHUTDOWN() zend_async_shutdown_fn() #define ZEND_ASYNC_ENGINE_SHUTDOWN() zend_async_engine_shutdown_fn() #define ZEND_ASYNC_GET_COROUTINES() zend_async_get_coroutines_fn() From f8c81f62452806c7588a582098ecd006815b0503 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 5 Jul 2025 22:11:00 +0300 Subject: [PATCH 095/137] #9: * Fixes in the zend_iterator logic in the context of concurrent iteration. Added a constant for high priority. --- Zend/zend_async_API.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 5824e748d4880..c748a3a640b31 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -88,6 +88,11 @@ typedef enum { ZEND_ASYNC_EXEC_MODE_SHELL_EXEC } zend_async_exec_mode; +typedef enum { + ZEND_COROUTINE_NORMAL = 0, + ZEND_COROUTINE_HI_PRIORITY = 255 +} zend_coroutine_priority; + typedef enum { ZEND_ASYNC_CLASS_NO = 0, From ef9a619586eba3d52039b32135cbfc049cd80a41 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:55:47 +0300 Subject: [PATCH 096/137] #9: * Fix event callback disposal and improve error handling in TrueAsync API * Fixes for zend_async_resume_when + Add zend_async_waker_is_event_exists --- Zend/zend_async_API.c | 47 +++++++++++++++++++++++++++++++++++-------- Zend/zend_async_API.h | 12 +++++++---- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 4f16e18fe73d5..d1beba8757efa 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -406,7 +406,7 @@ ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_ return event_callback; } -static void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event); +void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event); ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size @@ -552,14 +552,18 @@ static void coroutine_event_callback_dispose(zend_async_event_callback_t *callba callback->ref_count = 0; - zend_async_waker_t * waker = ((zend_coroutine_event_callback_t *) callback)->coroutine->waker; + const zend_coroutine_t * coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; - if (event != NULL && waker != NULL) { - // remove the event from the waker - zend_hash_index_del(&waker->events, (zend_ulong)event); + if (EXPECTED(coroutine != NULL)) { + zend_async_waker_t * waker = coroutine->waker; - if (waker->triggered_events != NULL) { - zend_hash_index_del(waker->triggered_events, (zend_ulong)event); + if (event != NULL && waker != NULL) { + // remove the event from the waker + zend_hash_index_del(&waker->events, (zend_ulong)event); + + if (waker->triggered_events != NULL) { + zend_hash_index_del(waker->triggered_events, (zend_ulong)event); + } } } @@ -582,6 +586,15 @@ ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, } } +ZEND_API bool zend_async_waker_is_event_exists(zend_coroutine_t *coroutine, zend_async_event_t *event) +{ + if (UNEXPECTED(coroutine->waker == NULL)) { + return false; + } + + return zend_hash_index_find(&coroutine->waker->events, (zend_ulong)event) != NULL; +} + ZEND_API void zend_async_resume_when( zend_coroutine_t *coroutine, zend_async_event_t *event, @@ -590,6 +603,8 @@ ZEND_API void zend_async_resume_when( zend_coroutine_event_callback_t *event_callback ) { + bool locally_allocated_callback = false; + if (UNEXPECTED(ZEND_ASYNC_EVENT_IS_CLOSED(event))) { zend_throw_error(NULL, "The event cannot be used after it has been terminated"); return; @@ -610,6 +625,7 @@ ZEND_API void zend_async_resume_when( event_callback->base.ref_count = 1; event_callback->base.callback = callback; event_callback->base.dispose = coroutine_event_callback_dispose; + locally_allocated_callback = true; } // Set up the default dispose function if not set @@ -627,6 +643,10 @@ ZEND_API void zend_async_resume_when( event->add_callback(event, &event_callback->base); if (UNEXPECTED(EG(exception) != NULL)) { + if (locally_allocated_callback) { + event_callback->base.dispose(&event_callback->base, event); + } + if (trans_event) { event->dispose(event); } @@ -640,7 +660,18 @@ ZEND_API void zend_async_resume_when( trigger->callback = &event_callback->base; if (UNEXPECTED(zend_hash_index_add_ptr(&coroutine->waker->events, (zend_ulong)event, trigger) == NULL)) { - zend_throw_error(NULL, "Failed to add event to the waker"); + efree(trigger); + + if (locally_allocated_callback) { + event_callback->base.dispose(&event_callback->base, event); + } + + if (trans_event) { + event->dispose(event); + } + + zend_throw_error(NULL, "Failed to add event to the waker: maybe event already exists"); + return; } } diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index c748a3a640b31..e7299e111931e 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -19,7 +19,7 @@ #include "zend_fibers.h" #include "zend_globals.h" -#define ZEND_ASYNC_API "TrueAsync API v0.2.0" +#define ZEND_ASYNC_API "TrueAsync API v0.3.0" #define ZEND_ASYNC_API_VERSION_MAJOR 0 #define ZEND_ASYNC_API_VERSION_MINOR 2 #define ZEND_ASYNC_API_VERSION_PATCH 0 @@ -386,10 +386,12 @@ struct _zend_async_event_callback_s { } #define ZEND_ASYNC_EVENT_CALLBACK_RELEASE(callback) \ - if (callback != NULL && callback->ref_count > 1) { \ - callback->ref_count--; \ + if ((callback) != NULL && (callback)->ref_count > 1) { \ + (callback)->ref_count--; \ + } else if((callback)->dispose != NULL) { \ + (callback)->dispose((callback), NULL); \ } else { \ - coroutine_event_callback_dispose(callback, NULL); \ + coroutine_event_callback_dispose((callback), NULL); \ } struct _zend_coroutine_event_callback_s { @@ -1287,6 +1289,7 @@ ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_ ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size ); +ZEND_API void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event); /* Waker API */ ZEND_API zend_async_waker_t *zend_async_waker_new(zend_coroutine_t *coroutine); @@ -1298,6 +1301,7 @@ ZEND_API bool zend_async_waker_apply_error( ); ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine); ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, zend_async_event_t *event); +ZEND_API bool zend_async_waker_is_event_exists(zend_coroutine_t *coroutine, zend_async_event_t *event); #define ZEND_ASYNC_WAKER_APPLY_ERROR(waker, error, transfer) zend_async_waker_apply_error((waker), (error), (transfer), true, false) #define ZEND_ASYNC_WAKER_APPEND_ERROR(waker, error, transfer) zend_async_waker_apply_error((waker), (error), (transfer), false, false) From fe7b894f530ee8fa7223d36a6b65ac525da0d4ae Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:46:10 +0300 Subject: [PATCH 097/137] =?UTF-8?q?#9:=20+=20Added=20functionality=20for?= =?UTF-8?q?=20proper=20handling=20of=20exceptions=20from=20`Zend=20iterato?= =?UTF-8?q?rs`=20(`\Iterator`=20and=20`generators`).=20An=20exception=20th?= =?UTF-8?q?at=20occurs=20in=20the=20iterator=20can=20now=20be=20handled=20?= =?UTF-8?q?by=20the=20iterator=E2=80=99s=20owner.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Zend/zend_async_API.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index e7299e111931e..aeb39c2d68ada 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -21,7 +21,7 @@ #define ZEND_ASYNC_API "TrueAsync API v0.3.0" #define ZEND_ASYNC_API_VERSION_MAJOR 0 -#define ZEND_ASYNC_API_VERSION_MINOR 2 +#define ZEND_ASYNC_API_VERSION_MINOR 3 #define ZEND_ASYNC_API_VERSION_PATCH 0 #define ZEND_ASYNC_API_VERSION_NUMBER \ @@ -339,7 +339,9 @@ typedef void (*zend_async_iterator_method_t)(zend_async_iterator_t *iterator); /* The maximum number of concurrent tasks that can be executed at the same time */ \ unsigned int concurrency; \ /* Priority for coroutines created by this iterator */ \ - int32_t priority; + int32_t priority; \ + /* NULLABLE. Exception that stopped the iterator */ \ + zend_object * exception; struct _zend_async_iterator_s { ZEND_ASYNC_ITERATOR_FIELDS From 70d3b0cfdaecb3a28a7dd22a1ad5a431a94cc802 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:14:30 +0300 Subject: [PATCH 098/137] #9: fix issue with static void coroutine_event_callback_dispose --- Zend/zend_async_API.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index d1beba8757efa..ed5ff39b4dd0f 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -537,7 +537,7 @@ ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine) efree(waker); } -static void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) +void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) { if (callback->ref_count > 1) { // If the callback is still referenced, we cannot dispose it yet From 72786c3375b409f2bc3f21f83c621ae395403b5f Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 9 Jul 2025 20:24:49 +0300 Subject: [PATCH 099/137] #9: + add ZEND_ASYNC_SCOPE_SET_DISPOSED --- Zend/zend_async_API.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index aeb39c2d68ada..1440ba1ff73d5 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -798,11 +798,13 @@ struct _zend_async_scope_s { #define ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY /* scope will not free memory in dispose handler */ #define ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY (1u << 14) /* scope will be disposed safely */ #define ZEND_ASYNC_SCOPE_F_CANCELLED (1u << 15) /* scope was cancelled */ +#define ZEND_ASYNC_SCOPE_F_DISPOSED (1u << 16) /* scope was disposed */ #define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CLOSED) != 0) #define ZEND_ASYNC_SCOPE_IS_NO_FREE_MEMORY(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) != 0) #define ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) != 0) #define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CANCELLED) != 0) +#define ZEND_ASYNC_SCOPE_IS_DISPOSED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSED) != 0) #define ZEND_ASYNC_SCOPE_SET_CLOSED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CLOSED) #define ZEND_ASYNC_SCOPE_CLR_CLOSED(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_CLOSED) @@ -815,6 +817,8 @@ struct _zend_async_scope_s { #define ZEND_ASYNC_SCOPE_SET_CANCELLED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CANCELLED) +#define ZEND_ASYNC_SCOPE_SET_DISPOSED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_DISPOSED) + static zend_always_inline void zend_async_scope_add_child(zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) { From bd76291ca9e2a08f01c69b4d091d94fa4b361b60 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 9 Jul 2025 20:50:10 +0300 Subject: [PATCH 100/137] #9: + ZEND_ASYNC_SCOPE_CLR_DISPOSING --- Zend/zend_async_API.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 1440ba1ff73d5..5fefb9040de69 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -798,13 +798,13 @@ struct _zend_async_scope_s { #define ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY /* scope will not free memory in dispose handler */ #define ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY (1u << 14) /* scope will be disposed safely */ #define ZEND_ASYNC_SCOPE_F_CANCELLED (1u << 15) /* scope was cancelled */ -#define ZEND_ASYNC_SCOPE_F_DISPOSED (1u << 16) /* scope was disposed */ +#define ZEND_ASYNC_SCOPE_F_DISPOSING (1u << 16) /* scope disposing */ #define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CLOSED) != 0) #define ZEND_ASYNC_SCOPE_IS_NO_FREE_MEMORY(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) != 0) #define ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) != 0) #define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CANCELLED) != 0) -#define ZEND_ASYNC_SCOPE_IS_DISPOSED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSED) != 0) +#define ZEND_ASYNC_SCOPE_IS_DISPOSING(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSING) != 0) #define ZEND_ASYNC_SCOPE_SET_CLOSED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CLOSED) #define ZEND_ASYNC_SCOPE_CLR_CLOSED(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_CLOSED) @@ -817,7 +817,8 @@ struct _zend_async_scope_s { #define ZEND_ASYNC_SCOPE_SET_CANCELLED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CANCELLED) -#define ZEND_ASYNC_SCOPE_SET_DISPOSED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_DISPOSED) +#define ZEND_ASYNC_SCOPE_SET_DISPOSING(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_DISPOSING) +#define ZEND_ASYNC_SCOPE_CLR_DISPOSING(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_DISPOSING) static zend_always_inline void zend_async_scope_add_child(zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) From 9f57f11a0785cc6955d77c16566a43ef5204c8dd Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 10 Jul 2025 23:36:06 +0300 Subject: [PATCH 101/137] #25: * fix Zend GC issue with Fiber case --- Zend/zend_gc.c | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index ca379b0737b7e..9fc429a8462c9 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2176,11 +2176,21 @@ ZEND_API int zend_gc_collect_cycles(void) return 0; } + const bool in_fiber = EG(active_fiber) != NULL; + // // We might enter this context from different coroutines, so we don’t initialize anything here. // - gc_async_context_t *context = &GC_G(async_context); - gc_stack *stack = GC_G(gc_stack); + gc_async_context_t *context = NULL; + gc_stack *stack = NULL; + + if (in_fiber) { + context = emalloc(sizeof(gc_async_context_t)); + stack = emalloc(sizeof(gc_stack)); + } else { + context = &GC_G(async_context); + stack = GC_G(gc_stack); + } if (UNEXPECTED(context->state == GC_ASYNC_STATE_CONTINUE)) { // If we reach this point, it means the destructor call was interrupted by a suspend() operation, @@ -2197,14 +2207,21 @@ ZEND_API int zend_gc_collect_cycles(void) context->should_rerun_gc = 0; context->did_rerun_gc = 0; context->gc_flags = 0; + // reset the destructor index + GC_G(dtor_idx) = GC_FIRST_ROOT; - if (GC_G(gc_stack) == NULL) { - stack = ecalloc(1, sizeof(gc_stack)); + if (false == in_fiber) { + if (GC_G(gc_stack) == NULL) { + stack = emalloc(sizeof(gc_stack)); + stack->prev = NULL; + stack->next = NULL; + GC_G(gc_stack) = stack; + } else { + stack = GC_G(gc_stack); + } + } else { stack->prev = NULL; stack->next = NULL; - GC_G(gc_stack) = stack; - } else { - stack = GC_G(gc_stack); } } @@ -2357,7 +2374,9 @@ ZEND_API int zend_gc_collect_cycles(void) gc_stack_free(GC_COLLECT_STACK); #ifdef PHP_ASYNC_API - end = GC_G(first_unused); + if (false == in_fiber) { + end = GC_G(first_unused); + } #endif /* Destroy zvals. The root buffer may be reallocated. */ @@ -2442,9 +2461,15 @@ ZEND_API int zend_gc_collect_cycles(void) GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; #ifdef PHP_ASYNC_API - GC_G(async_context).state = GC_ASYNC_STATE_NONE; - if (GC_G(gc_stack) != NULL) { - GC_COLLECT_FREE_STACK; + if (in_fiber) { + efree(context); + gc_stack_free(stack); + efree(stack); + } else { + GC_G(async_context).state = GC_ASYNC_STATE_NONE; + if (GC_G(gc_stack) != NULL) { + GC_COLLECT_FREE_STACK; + } } #endif return GC_COLLECT_TOTAL_COUNT; From 1a398f907afa2bd7b569e6bd45daec63f5a78497 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 11 Jul 2025 07:51:30 +0300 Subject: [PATCH 102/137] #25: * fix Zend GC issue after refactoring --- Zend/zend_gc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 9fc429a8462c9..73e6f761607e6 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2185,7 +2185,7 @@ ZEND_API int zend_gc_collect_cycles(void) gc_stack *stack = NULL; if (in_fiber) { - context = emalloc(sizeof(gc_async_context_t)); + context = ecalloc(1, sizeof(gc_async_context_t)); stack = emalloc(sizeof(gc_stack)); } else { context = &GC_G(async_context); @@ -2462,13 +2462,15 @@ ZEND_API int zend_gc_collect_cycles(void) GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; #ifdef PHP_ASYNC_API if (in_fiber) { + const int total_count = GC_COLLECT_TOTAL_COUNT; efree(context); - gc_stack_free(stack); efree(stack); + return total_count; } else { GC_G(async_context).state = GC_ASYNC_STATE_NONE; if (GC_G(gc_stack) != NULL) { - GC_COLLECT_FREE_STACK; + GC_G(gc_stack) = NULL; + efree(stack); } } #endif From 29207833dbc527f452e3cb05d9dceb36148ea7cf Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 11 Jul 2025 08:38:42 +0300 Subject: [PATCH 103/137] #25: * fix warning for end var --- Zend/zend_gc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 73e6f761607e6..d3a020d9bf199 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2265,6 +2265,8 @@ ZEND_API int zend_gc_collect_cycles(void) stack.next = NULL; #endif + end = 0; + if (GC_G(gc_active)) { GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; GC_COLLECT_FINISH_0; From 6b98adb574e5b79ca095bcfab882fa23e6767d5f Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 11 Jul 2025 10:24:28 +0300 Subject: [PATCH 104/137] #23: + edge cases for fiber from spawn and spawn from fiber --- Zend/zend_fibers.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 5c37408e27b41..13b530952f0a8 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -32,6 +32,8 @@ #include "zend_generators.h" #include "zend_fibers.h" + +#include "zend_async_API.h" #include "zend_fibers_arginfo.h" #ifdef HAVE_VALGRIND @@ -562,6 +564,19 @@ static void zend_fiber_cleanup(zend_fiber_context *context) fiber->caller = NULL; } +#ifdef PHP_ASYNC_API +static zend_always_inline bool can_use_fiber(void) +{ + if (UNEXPECTED(ZEND_ASYNC_IS_ACTIVE)) { + zend_throw_error(zend_ce_fiber_error, "Cannot create a fiber while an True Async is active"); + return false; + } + + ZEND_ASYNC_DEACTIVATE; + return true; +} +#endif + static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer) { ZEND_ASSERT(Z_TYPE(transfer->value) == IS_NULL && "Initial transfer value to fiber context must be NULL"); @@ -711,6 +726,12 @@ ZEND_API zend_result zend_fiber_start(zend_fiber *fiber, zval *return_value) { ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_INIT); +#ifdef PHP_ASYNC_API + if (UNEXPECTED(false == can_use_fiber())) { + return FAILURE; + } +#endif + if (zend_fiber_init_context(&fiber->context, zend_ce_fiber, zend_fiber_execute, EG(fiber_stack_size)) == FAILURE) { return FAILURE; } @@ -726,6 +747,12 @@ ZEND_API zend_result zend_fiber_start(zend_fiber *fiber, zval *return_value) ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value) { +#ifdef PHP_ASYNC_API + if (UNEXPECTED(false == can_use_fiber())) { + return; + } +#endif + ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL); fiber->stack_bottom->prev_execute_data = EG(current_execute_data); @@ -737,6 +764,12 @@ ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_val ZEND_API void zend_fiber_resume_exception(zend_fiber *fiber, zval *exception, zval *return_value) { +#ifdef PHP_ASYNC_API + if (UNEXPECTED(false == can_use_fiber())) { + return; + } +#endif + ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL); fiber->stack_bottom->prev_execute_data = EG(current_execute_data); @@ -748,6 +781,12 @@ ZEND_API void zend_fiber_resume_exception(zend_fiber *fiber, zval *exception, zv ZEND_API void zend_fiber_suspend(zend_fiber *fiber, zval *value, zval *return_value) { +#ifdef PHP_ASYNC_API + if (UNEXPECTED(false == can_use_fiber())) { + return; + } +#endif + fiber->stack_bottom->prev_execute_data = NULL; zend_fiber_transfer transfer = zend_fiber_suspend_internal(fiber, value); @@ -876,6 +915,12 @@ ZEND_METHOD(Fiber, __construct) Z_PARAM_FUNC(fci, fcc) ZEND_PARSE_PARAMETERS_END(); +#ifdef PHP_ASYNC_API + if (UNEXPECTED(false == can_use_fiber())) { + RETURN_THROWS(); + } +#endif + zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_INIT || Z_TYPE(fiber->fci.function_name) != IS_UNDEF)) { @@ -898,6 +943,12 @@ ZEND_METHOD(Fiber, start) Z_PARAM_VARIADIC_WITH_NAMED(fiber->fci.params, fiber->fci.param_count, fiber->fci.named_params); ZEND_PARSE_PARAMETERS_END(); +#ifdef PHP_ASYNC_API + if (UNEXPECTED(false == can_use_fiber())) { + RETURN_THROWS(); + } +#endif + if (UNEXPECTED(zend_fiber_switch_blocked())) { zend_throw_error(zend_ce_fiber_error, "Cannot switch fibers in current execution context"); RETURN_THROWS(); From d17f623ba38babe207dc461fc4964d7d503c1c0f Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 12 Jul 2025 08:12:06 +0300 Subject: [PATCH 105/137] Add zend_future_t structure to async API Introduce Future data structure following coroutine architecture patterns: - Inherits from zend_async_event_t for event system integration - Contains result/exception storage with debug location tracking - Provides resolve method pointer for flexible resolution strategies - Supports thread-safe creation parameter - Uses function pointer pattern consistent with existing async API - Includes stub implementation for disabled async mode Structure enables passive result containers that integrate with existing callback and replay mechanisms --- Zend/zend_async_API.c | 7 +++++++ Zend/zend_async_API.h | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index ed5ff39b4dd0f..3c5d1d1e36964 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -48,6 +48,12 @@ static void shutdown_stub(void) {} static zend_array* get_coroutines_stub(void) { return NULL; } +static zend_future_t* future_create_stub(bool thread_safe, size_t extra_size) +{ + ASYNC_THROW_ERROR("Async API is not enabled"); + return NULL; +} + static void add_microtask_stub(zend_async_microtask_t *microtask) {} static zend_array* get_awaiting_info_stub(zend_coroutine_t *coroutine) { return NULL; } @@ -97,6 +103,7 @@ zend_async_get_coroutines_t zend_async_get_coroutines_fn = get_coroutines_stub; zend_async_add_microtask_t zend_async_add_microtask_fn = add_microtask_stub; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = get_awaiting_info_stub; zend_async_get_class_ce_t zend_async_get_class_ce_fn = get_class_ce; +zend_async_future_create_t zend_async_future_create_fn = future_create_stub; static zend_atomic_bool reactor_lock = {0}; static char * reactor_module_name = NULL; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 5fefb9040de69..f030179223b12 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -119,6 +119,11 @@ typedef enum * zend_coroutine_t is a Basic data structure that represents a coroutine in the Zend Engine. */ typedef struct _zend_coroutine_s zend_coroutine_t; + +/** + * zend_future_t is a data structure that represents a future result container. + */ +typedef struct _zend_future_s zend_future_t; typedef struct _zend_async_context_s zend_async_context_t; typedef struct _zend_async_waker_s zend_async_waker_t; typedef struct _zend_async_microtask_s zend_async_microtask_t; @@ -127,6 +132,9 @@ typedef struct _zend_async_iterator_s zend_async_iterator_t; typedef struct _zend_fcall_s zend_fcall_t; typedef void (*zend_coroutine_entry_t)(void); +/* Future resolve function type */ +typedef void (*zend_future_resolve_t)(zend_future_t *future, zval *value, zend_object *exception, bool transfer_error); + /* Coroutine Switch Handlers */ typedef struct _zend_coroutine_switch_handler_s zend_coroutine_switch_handler_t; typedef struct _zend_coroutine_switch_handlers_vector_s zend_coroutine_switch_handlers_vector_t; @@ -203,6 +211,7 @@ typedef zend_array* (*zend_async_get_coroutines_t)(void); typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); typedef zend_class_entry* (*zend_async_get_class_ce_t)(zend_async_class type); +typedef zend_future_t* (*zend_async_future_create_t)(bool thread_safe, size_t extra_size); typedef void (*zend_async_reactor_startup_t)(void); typedef void (*zend_async_reactor_shutdown_t)(void); @@ -963,6 +972,25 @@ struct _zend_coroutine_s { zend_coroutine_switch_handlers_vector_t *switch_handlers; }; +/** + * zend_future_t structure represents a future result container. + * It inherits from zend_async_event_t to participate in the event system. + */ +struct _zend_future_s { + zend_async_event_t event; /* Event inheritance (first member) */ + zval result; /* Result value (UNDEF = pending) */ + zend_object *exception; /* Exception object (NULL = no error) */ + + /* Debug information */ + zend_string *filename; /* Creation file */ + uint32_t lineno; /* Creation line */ + zend_string *resolved_filename; /* Resolution file */ + uint32_t resolved_lineno; /* Resolution line */ + + /* Resolution method */ + zend_future_resolve_t resolve; +}; + /** * The macro evaluates to TRUE if the coroutine is in a waiting state — * either waiting for events or waiting in the execution queue. @@ -1177,6 +1205,7 @@ ZEND_API extern zend_async_get_coroutines_t zend_async_get_coroutines_fn; ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; +ZEND_API extern zend_async_future_create_t zend_async_future_create_fn; /* Iterator API */ ZEND_API extern zend_async_new_iterator_t zend_async_new_iterator_fn; @@ -1361,6 +1390,10 @@ ZEND_API void zend_async_add_main_coroutine_start_handler( ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *main_coroutine); +/* Future API Functions */ +#define ZEND_ASYNC_NEW_FUTURE(thread_safe) zend_async_future_create_fn(thread_safe, 0) +#define ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) zend_async_future_create_fn(thread_safe, extra_size) + END_EXTERN_C() #define ZEND_ASYNC_IS_ENABLED() zend_async_is_enabled() From e1e7d6c49d2dee687146468ec77341b373fbd1fd Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 12 Jul 2025 08:22:20 +0300 Subject: [PATCH 106/137] Add zend_async_channel_t abstract structure to async API Introduce Channel communication abstraction following async API patterns: - Inherits from zend_async_event_t for event system integration - Contains send/receive method pointers for implementation flexibility - Uses existing event.stop() method for channel closure - Supports creation parameters: buffer_size, resizable, thread_safe - Provides both ZEND_ASYNC_NEW_CHANNEL and _EX variants - Uses function pointer pattern consistent with async API architecture - Includes stub implementation for disabled async mode Abstract structure enables flexible channel implementations while maintaining consistent API interface for communication primitives. --- Zend/zend_async_API.c | 7 +++++++ Zend/zend_async_API.h | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 3c5d1d1e36964..a77c6eda686c3 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -54,6 +54,12 @@ static zend_future_t* future_create_stub(bool thread_safe, size_t extra_size) return NULL; } +static zend_async_channel_t* channel_create_stub(size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size) +{ + ASYNC_THROW_ERROR("Async API is not enabled"); + return NULL; +} + static void add_microtask_stub(zend_async_microtask_t *microtask) {} static zend_array* get_awaiting_info_stub(zend_coroutine_t *coroutine) { return NULL; } @@ -104,6 +110,7 @@ zend_async_add_microtask_t zend_async_add_microtask_fn = add_microtask_stub; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = get_awaiting_info_stub; zend_async_get_class_ce_t zend_async_get_class_ce_fn = get_class_ce; zend_async_future_create_t zend_async_future_create_fn = future_create_stub; +zend_async_channel_create_t zend_async_channel_create_fn = channel_create_stub; static zend_atomic_bool reactor_lock = {0}; static char * reactor_module_name = NULL; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index f030179223b12..5ee4514994692 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -19,9 +19,9 @@ #include "zend_fibers.h" #include "zend_globals.h" -#define ZEND_ASYNC_API "TrueAsync API v0.3.0" +#define ZEND_ASYNC_API "TrueAsync API v0.4.0" #define ZEND_ASYNC_API_VERSION_MAJOR 0 -#define ZEND_ASYNC_API_VERSION_MINOR 3 +#define ZEND_ASYNC_API_VERSION_MINOR 4 #define ZEND_ASYNC_API_VERSION_PATCH 0 #define ZEND_ASYNC_API_VERSION_NUMBER \ @@ -124,6 +124,11 @@ typedef struct _zend_coroutine_s zend_coroutine_t; * zend_future_t is a data structure that represents a future result container. */ typedef struct _zend_future_s zend_future_t; + +/** + * zend_async_channel_t is a data structure that represents a communication channel. + */ +typedef struct _zend_async_channel_s zend_async_channel_t; typedef struct _zend_async_context_s zend_async_context_t; typedef struct _zend_async_waker_s zend_async_waker_t; typedef struct _zend_async_microtask_s zend_async_microtask_t; @@ -135,6 +140,10 @@ typedef void (*zend_coroutine_entry_t)(void); /* Future resolve function type */ typedef void (*zend_future_resolve_t)(zend_future_t *future, zval *value, zend_object *exception, bool transfer_error); +/* Channel method function types */ +typedef bool (*zend_channel_send_t)(zend_async_channel_t *channel, zval *value); +typedef bool (*zend_channel_receive_t)(zend_async_channel_t *channel, zval *result); + /* Coroutine Switch Handlers */ typedef struct _zend_coroutine_switch_handler_s zend_coroutine_switch_handler_t; typedef struct _zend_coroutine_switch_handlers_vector_s zend_coroutine_switch_handlers_vector_t; @@ -212,6 +221,7 @@ typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); typedef zend_class_entry* (*zend_async_get_class_ce_t)(zend_async_class type); typedef zend_future_t* (*zend_async_future_create_t)(bool thread_safe, size_t extra_size); +typedef zend_async_channel_t* (*zend_async_channel_create_t)(size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size); typedef void (*zend_async_reactor_startup_t)(void); typedef void (*zend_async_reactor_shutdown_t)(void); @@ -991,6 +1001,22 @@ struct _zend_future_s { zend_future_resolve_t resolve; }; +/** + * zend_async_channel_t structure represents a communication channel. + * It inherits from zend_async_event_t to participate in the event system. + */ +struct _zend_async_channel_s { + zend_async_event_t event; /* Event inheritance (first member) */ + + /* Debug information */ + zend_string *filename; /* Creation file */ + uint32_t lineno; /* Creation line */ + + /* Channel-specific method pointers */ + zend_channel_send_t send; /* Send method */ + zend_channel_receive_t receive; /* Receive method */ +}; + /** * The macro evaluates to TRUE if the coroutine is in a waiting state — * either waiting for events or waiting in the execution queue. @@ -1206,6 +1232,7 @@ ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; ZEND_API extern zend_async_future_create_t zend_async_future_create_fn; +ZEND_API extern zend_async_channel_create_t zend_async_channel_create_fn; /* Iterator API */ ZEND_API extern zend_async_new_iterator_t zend_async_new_iterator_fn; @@ -1394,6 +1421,10 @@ ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *ma #define ZEND_ASYNC_NEW_FUTURE(thread_safe) zend_async_future_create_fn(thread_safe, 0) #define ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) zend_async_future_create_fn(thread_safe, extra_size) +/* Channel API Functions */ +#define ZEND_ASYNC_NEW_CHANNEL(buffer_size, resizable, thread_safe) zend_async_channel_create_fn(buffer_size, resizable, thread_safe, 0) +#define ZEND_ASYNC_NEW_CHANNEL_EX(buffer_size, resizable, thread_safe, extra_size) zend_async_channel_create_fn(buffer_size, resizable, thread_safe, extra_size) + END_EXTERN_C() #define ZEND_ASYNC_IS_ENABLED() zend_async_is_enabled() From 94227f5f9463e91c54f9204be06e824f34111359 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 12 Jul 2025 11:04:15 +0300 Subject: [PATCH 107/137] #13: Initial classes for Future --- Zend/zend_async_API.h | 74 +++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 5ee4514994692..c6659c3903171 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -137,9 +137,6 @@ typedef struct _zend_async_iterator_s zend_async_iterator_t; typedef struct _zend_fcall_s zend_fcall_t; typedef void (*zend_coroutine_entry_t)(void); -/* Future resolve function type */ -typedef void (*zend_future_resolve_t)(zend_future_t *future, zval *value, zend_object *exception, bool transfer_error); - /* Channel method function types */ typedef bool (*zend_channel_send_t)(zend_async_channel_t *channel, zval *value); typedef bool (*zend_channel_receive_t)(zend_async_channel_t *channel, zval *result); @@ -982,41 +979,6 @@ struct _zend_coroutine_s { zend_coroutine_switch_handlers_vector_t *switch_handlers; }; -/** - * zend_future_t structure represents a future result container. - * It inherits from zend_async_event_t to participate in the event system. - */ -struct _zend_future_s { - zend_async_event_t event; /* Event inheritance (first member) */ - zval result; /* Result value (UNDEF = pending) */ - zend_object *exception; /* Exception object (NULL = no error) */ - - /* Debug information */ - zend_string *filename; /* Creation file */ - uint32_t lineno; /* Creation line */ - zend_string *resolved_filename; /* Resolution file */ - uint32_t resolved_lineno; /* Resolution line */ - - /* Resolution method */ - zend_future_resolve_t resolve; -}; - -/** - * zend_async_channel_t structure represents a communication channel. - * It inherits from zend_async_event_t to participate in the event system. - */ -struct _zend_async_channel_s { - zend_async_event_t event; /* Event inheritance (first member) */ - - /* Debug information */ - zend_string *filename; /* Creation file */ - uint32_t lineno; /* Creation line */ - - /* Channel-specific method pointers */ - zend_channel_send_t send; /* Send method */ - zend_channel_receive_t receive; /* Receive method */ -}; - /** * The macro evaluates to TRUE if the coroutine is in a waiting state — * either waiting for events or waiting in the execution queue. @@ -1103,6 +1065,42 @@ struct _zend_async_context_s { zend_async_context_dispose_t dispose; }; +/////////////////////////////////////////////////////////////// +/// Future +/////////////////////////////////////////////////////////////// + +/** + * zend_future_t structure represents a future result container. + * It inherits from zend_async_event_t to participate in the event system. + */ +struct _zend_future_s { + zend_async_event_t event; /* Event inheritance (first member) */ + zval result; /* Result value */ + zend_object *exception; /* Exception object (NULL = no error) */ +}; + +#define ZEND_FUTURE_F_THREAD_SAFE (1u << 10) +#define ZEND_FUTURE_F_IGNORE (1u << 11) + +/////////////////////////////////////////////////////////////// +/// Channel +/////////////////////////////////////////////////////////////// + +/** + * zend_async_channel_t structure represents a communication channel. + * It inherits from zend_async_event_t to participate in the event system. + */ +struct _zend_async_channel_s { + zend_async_event_t event; /* Event inheritance (first member) */ + + /* Channel-specific method pointers */ + zend_channel_send_t send; /* Send method */ + zend_channel_receive_t receive; /* Receive method */ +}; + +#define ZEND_ASYNC_CHANNEL_F_THREAD_SAFE (1u << 10) + + /////////////////////////////////////////////////////////////// /// Global Macros /////////////////////////////////////////////////////////////// From 518b411b78325051497a11bf387001683ff14bad Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 12 Jul 2025 15:24:04 +0300 Subject: [PATCH 108/137] #13: Future methods --- Zend/zend_async_API.h | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index c6659c3903171..2ef670e18810b 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1077,10 +1077,39 @@ struct _zend_future_s { zend_async_event_t event; /* Event inheritance (first member) */ zval result; /* Result value */ zend_object *exception; /* Exception object (NULL = no error) */ + /* Created file and line number */ + uint32_t lineno; + uint32_t completed_lineno; + /* Completed file and line number */ + zend_string *filename; + zend_string *completed_filename; }; #define ZEND_FUTURE_F_THREAD_SAFE (1u << 10) -#define ZEND_FUTURE_F_IGNORE (1u << 11) +#define ZEND_FUTURE_F_IGNORED (1u << 11) + +#define ZEND_FUTURE_IS_COMPLETED(future) (((future)->event.flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) + +#define ZEND_FUTURE_SET_THREAD_SAFE(future) ((future)->event.flags |= ZEND_FUTURE_F_THREAD_SAFE) +#define ZEND_FUTURE_SET_IGNORED(future) ((future)->event.flags |= ZEND_FUTURE_F_IGNORED) + + +#define ZEND_FUTURE_COMPLETE(future, result) do { \ + if(ZEND_ASYNC_EVENT_IS_CLOSED(&(future)->event)) { \ + break; \ + } \ + ZVAL_COPY(&(future)->result, (result)); \ + (future)->event.stop(&(future)->event); \ + } while (0) + +#define ZEND_FUTURE_REJECT(future, error) do { \ + if(ZEND_ASYNC_EVENT_IS_CLOSED(&(future)->event)) { \ + break; \ + } \ + (future)->exception = error; \ + GC_ADDREF(error); \ + (future)->event.stop(&(future)->event); \ + } while (0) /////////////////////////////////////////////////////////////// /// Channel @@ -1092,7 +1121,6 @@ struct _zend_future_s { */ struct _zend_async_channel_s { zend_async_event_t event; /* Event inheritance (first member) */ - /* Channel-specific method pointers */ zend_channel_send_t send; /* Send method */ zend_channel_receive_t receive; /* Receive method */ From bbed9c90f37bd759dd5f27abd3a525348f549163 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:44:21 +0300 Subject: [PATCH 109/137] #31: * Fixed a memory leak when zend_async_resume_when fails. --- Zend/zend_async_API.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index a77c6eda686c3..95ae324a13d3d 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -676,6 +676,9 @@ ZEND_API void zend_async_resume_when( if (UNEXPECTED(zend_hash_index_add_ptr(&coroutine->waker->events, (zend_ulong)event, trigger) == NULL)) { efree(trigger); + event_callback->coroutine = NULL; + event->del_callback(event, &event_callback->base); + if (locally_allocated_callback) { event_callback->base.dispose(&event_callback->base, event); } From e5e9b2a03cac209f22882964457f5b33fd7feabf Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:35:49 +0300 Subject: [PATCH 110/137] #31: + Added support for waiting on identical objects, even if they have different keys. --- Zend/zend_async_API.c | 110 +++++++++++++++++++++++++++++++++--------- Zend/zend_async_API.h | 6 ++- 2 files changed, 91 insertions(+), 25 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 95ae324a13d3d..c5c08402959bb 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -442,6 +442,33 @@ ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( /* Waker API */ ////////////////////////////////////////////////////////////////////// +static zend_always_inline zend_async_waker_trigger_t *waker_trigger_create(zend_async_event_t *event, uint32_t initial_capacity) +{ + size_t total_size = sizeof(zend_async_waker_trigger_t) + initial_capacity * sizeof(zend_async_event_callback_t *); + zend_async_waker_trigger_t *trigger = (zend_async_waker_trigger_t *)emalloc(total_size); + + trigger->length = 0; + trigger->capacity = initial_capacity; + trigger->event = event; + + return trigger; +} + +static zend_always_inline zend_async_waker_trigger_t *waker_trigger_add_callback(zend_async_waker_trigger_t *trigger, zend_async_event_callback_t *callback) +{ + if (trigger->length >= trigger->capacity) { + uint32_t new_capacity = trigger->capacity * 2; + size_t total_size = sizeof(zend_async_waker_trigger_t) + new_capacity * sizeof(zend_async_event_callback_t *); + + zend_async_waker_trigger_t *new_trigger = (zend_async_waker_trigger_t *)erealloc(trigger, total_size); + new_trigger->capacity = new_capacity; + trigger = new_trigger; + } + + trigger->data[trigger->length++] = callback; + return trigger; +} + static void waker_events_dtor(zval *item) { zend_async_waker_trigger_t * trigger = Z_PTR_P(item); @@ -449,7 +476,12 @@ static void waker_events_dtor(zval *item) trigger->event = NULL; if (event != NULL) { - event->del_callback(event, trigger->callback); + // Remove all callbacks from the event + for (uint32_t i = 0; i < trigger->length; i++) { + if (trigger->data[i] != NULL) { + event->del_callback(event, trigger->data[i]); + } + } // // At this point, we explicitly stop the event because it is no longer being listened to by our handlers. // However, this does not mean the object is destroyed—it may remain in memory if something still holds a reference to it. @@ -458,6 +490,7 @@ static void waker_events_dtor(zval *item) ZEND_ASYNC_EVENT_RELEASE(event); } + // Free the entire trigger (includes flexible array member) efree(trigger); } @@ -572,11 +605,29 @@ void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zen zend_async_waker_t * waker = coroutine->waker; if (event != NULL && waker != NULL) { - // remove the event from the waker - zend_hash_index_del(&waker->events, (zend_ulong)event); - - if (waker->triggered_events != NULL) { - zend_hash_index_del(waker->triggered_events, (zend_ulong)event); + // Find the trigger for this event + zval *trigger_zval = zend_hash_index_find(&waker->events, (zend_ulong)event); + + if (trigger_zval != NULL) { + zend_async_waker_trigger_t *trigger = Z_PTR_P(trigger_zval); + + // Remove only this specific callback from the trigger + for (uint32_t i = 0; i < trigger->length; i++) { + if (trigger->data[i] == callback) { + // Move last element to current position (O(1) removal) + trigger->data[i] = trigger->data[--trigger->length]; + break; + } + } + + // If no more callbacks in trigger, remove the entire event + if (trigger->length == 0) { + zend_hash_index_del(&waker->events, (zend_ulong)event); + + if (waker->triggered_events != NULL) { + zend_hash_index_del(waker->triggered_events, (zend_ulong)event); + } + } } } } @@ -669,33 +720,46 @@ ZEND_API void zend_async_resume_when( } if (EXPECTED(coroutine->waker != NULL)) { - zend_async_waker_trigger_t *trigger = emalloc(sizeof(zend_async_waker_trigger_t)); - trigger->event = event; - trigger->callback = &event_callback->base; + zval *trigger_zval = zend_hash_index_find(&coroutine->waker->events, (zend_ulong)event); + zend_async_waker_trigger_t *trigger; + + if (UNEXPECTED(trigger_zval != NULL)) { + // Event already exists, add callback to existing trigger + trigger = Z_PTR_P(trigger_zval); + trigger = waker_trigger_add_callback(trigger, &event_callback->base); + // Update the hash table entry with potentially new pointer after realloc + Z_PTR_P(trigger_zval) = trigger; + } else { + // New event, create new trigger + trigger = waker_trigger_create(event, 1); + trigger = waker_trigger_add_callback(trigger, &event_callback->base); - if (UNEXPECTED(zend_hash_index_add_ptr(&coroutine->waker->events, (zend_ulong)event, trigger) == NULL)) { - efree(trigger); + if (UNEXPECTED(zend_hash_index_add_ptr(&coroutine->waker->events, (zend_ulong)event, trigger) == NULL)) { + // This should not happen with new events, but handle gracefully + efree(trigger); event_callback->coroutine = NULL; event->del_callback(event, &event_callback->base); - if (locally_allocated_callback) { - event_callback->base.dispose(&event_callback->base, event); - } + event_callback->coroutine = NULL; + event->del_callback(event, &event_callback->base); - if (trans_event) { - event->dispose(event); - } + if (locally_allocated_callback) { + event_callback->base.dispose(&event_callback->base, event); + } - zend_throw_error(NULL, "Failed to add event to the waker: maybe event already exists"); + if (trans_event) { + event->dispose(event); + } - return; + zend_throw_error(NULL, "Failed to add event to the waker"); + } else { + if (false == trans_event) { + ZEND_ASYNC_EVENT_ADD_REF(event); + } + } } } - - if (false == trans_event) { - ZEND_ASYNC_EVENT_ADD_REF(event); - } } ZEND_API void zend_async_waker_callback_resolve( diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 2ef670e18810b..e24951fd74e31 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -418,8 +418,10 @@ struct _zend_coroutine_event_callback_s { }; struct _zend_async_waker_trigger_s { - zend_async_event_t *event; - zend_async_event_callback_t *callback; + uint32_t length; /* current number of callbacks */ + uint32_t capacity; /* allocated slots in the array */ + zend_async_event_t *event; + zend_async_event_callback_t *data[]; /* flexible array member */ }; /* Dynamic array of async event callbacks with single iterator protection */ From 9779f45fed37ea5c76a499032fe36d973afcca26 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:43:05 +0300 Subject: [PATCH 111/137] #31: + Fix MSVC C4200 warning: replace flexible array with MSVC-compatible syntax - Change `data[]` to `data[1]` in zend_async_waker_trigger_s structure - Adjust memory allocation calculations to account for included array element - Ensures compatibility with MSVC compiler while maintaining functionality --- Zend/zend_async_API.c | 4 ++-- Zend/zend_async_API.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index c5c08402959bb..a66d7f84b83bd 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -444,7 +444,7 @@ ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( static zend_always_inline zend_async_waker_trigger_t *waker_trigger_create(zend_async_event_t *event, uint32_t initial_capacity) { - size_t total_size = sizeof(zend_async_waker_trigger_t) + initial_capacity * sizeof(zend_async_event_callback_t *); + size_t total_size = sizeof(zend_async_waker_trigger_t) + (initial_capacity - 1) * sizeof(zend_async_event_callback_t *); zend_async_waker_trigger_t *trigger = (zend_async_waker_trigger_t *)emalloc(total_size); trigger->length = 0; @@ -458,7 +458,7 @@ static zend_always_inline zend_async_waker_trigger_t *waker_trigger_add_callback { if (trigger->length >= trigger->capacity) { uint32_t new_capacity = trigger->capacity * 2; - size_t total_size = sizeof(zend_async_waker_trigger_t) + new_capacity * sizeof(zend_async_event_callback_t *); + size_t total_size = sizeof(zend_async_waker_trigger_t) + (new_capacity - 1) * sizeof(zend_async_event_callback_t *); zend_async_waker_trigger_t *new_trigger = (zend_async_waker_trigger_t *)erealloc(trigger, total_size); new_trigger->capacity = new_capacity; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index e24951fd74e31..9510543510603 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -421,7 +421,7 @@ struct _zend_async_waker_trigger_s { uint32_t length; /* current number of callbacks */ uint32_t capacity; /* allocated slots in the array */ zend_async_event_t *event; - zend_async_event_callback_t *data[]; /* flexible array member */ + zend_async_event_callback_t *data[1]; /* flexible array member */ }; /* Dynamic array of async event callbacks with single iterator protection */ From 582258282a9f750412d052db7312acb31313dad2 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:56:14 +0300 Subject: [PATCH 112/137] #32: + Cannot resume when there is an active exception in the engine. --- Zend/zend_async_API.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index a66d7f84b83bd..0559bc82d45b9 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -668,6 +668,12 @@ ZEND_API void zend_async_resume_when( zend_coroutine_event_callback_t *event_callback ) { + ZEND_ASSERT(EG(exception) == NULL && "Cannot resume when there is an active exception in the engine."); + + if (UNEXPECTED(EG(exception))) { + return; + } + bool locally_allocated_callback = false; if (UNEXPECTED(ZEND_ASYNC_EVENT_IS_CLOSED(event))) { From d3a5996571e76a525c3094fc75ffd44a5c00ca6d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Tue, 15 Jul 2025 18:28:21 +0300 Subject: [PATCH 113/137] * remove CI pipe --- .circleci/config-true-async.yml | 191 -------------------------------- 1 file changed, 191 deletions(-) delete mode 100644 .circleci/config-true-async.yml diff --git a/.circleci/config-true-async.yml b/.circleci/config-true-async.yml deleted file mode 100644 index ae2f7458e2e18..0000000000000 --- a/.circleci/config-true-async.yml +++ /dev/null @@ -1,191 +0,0 @@ -version: 2.1 - -jobs: - arm: - resource_class: arm.medium - docker: - - image: cimg/base:current-22.04 - - image: mysql:8.3 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: true - MYSQL_ROOT_PASSWORD: '' - MYSQL_DATABASE: test - - image: postgres:16 - environment: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: test - environment: - LANGUAGE: '' - LANG: en_US.UTF-8 - MYSQL_TEST_HOST: '127.0.0.1' - MYSQL_TEST_PASSWD: '' - MYSQL_TEST_USER: root - PDO_MYSQL_TEST_DSN: 'mysql:host=127.0.0.1;dbname=test' - PDO_MYSQL_TEST_PASS: '' - PDO_MYSQL_TEST_USER: root - PDO_PGSQL_TEST_DSN: 'pgsql:host=127.0.0.1 port=5432 dbname=test user=postgres password=postgres' - steps: - - checkout - - run: - name: Install system dependencies (including true async + libuv) - command: | - export DEBIAN_FRONTEND=noninteractive - sudo apt-get update -y - sudo apt-get install -y \ - gcc \ - g++ \ - autoconf \ - bison \ - re2c \ - locales \ - locales-all \ - ldap-utils \ - openssl \ - slapd \ - libgmp-dev \ - libicu-dev \ - libtidy-dev \ - libenchant-2-dev \ - libsasl2-dev \ - libxpm-dev \ - libzip-dev \ - libbz2-dev \ - libsqlite3-dev \ - libwebp-dev \ - libonig-dev \ - libcurl4-openssl-dev \ - libxml2-dev \ - libxslt1-dev \ - libpq-dev \ - libreadline-dev \ - libldap2-dev \ - libsodium-dev \ - libargon2-0-dev \ - libmm-dev \ - libsnmp-dev \ - snmpd \ - freetds-dev \ - dovecot-core \ - dovecot-pop3d \ - dovecot-imapd \ - sendmail \ - firebird-dev \ - liblmdb-dev \ - libtokyocabinet-dev \ - libdb-dev \ - libqdbm-dev \ - libjpeg-dev \ - libpng-dev \ - libfreetype6-dev \ - libuv1-dev - - run: - name: Download php-async (main) into ext/ - command: | - git clone --depth=1 --branch=main https://github.com/true-async/php-async /tmp/php-async - cp -r /tmp/php-async/* ./ext/ - - run: - name: ./configure - command: | - ./buildconf -f - ./configure \ - --enable-debug \ - --enable-zts \ - --enable-option-checking=fatal \ - --prefix=/usr \ - --enable-phpdbg \ - --enable-fpm \ - --enable-opcache \ - --with-pdo-mysql=mysqlnd \ - --with-mysqli=mysqlnd \ - --with-pgsql \ - --with-pdo-pgsql \ - --with-pdo-sqlite \ - --enable-intl \ - --without-pear \ - --enable-gd \ - --with-jpeg \ - --with-webp \ - --with-freetype \ - --with-xpm \ - --enable-exif \ - --with-zip \ - --with-zlib \ - --enable-soap \ - --enable-xmlreader \ - --with-xsl \ - --with-tidy \ - --enable-sysvsem \ - --enable-sysvshm \ - --enable-shmop \ - --enable-pcntl \ - --with-readline \ - --enable-mbstring \ - --with-curl \ - --with-gettext \ - --enable-sockets \ - --with-bz2 \ - --with-openssl \ - --with-gmp \ - --enable-bcmath \ - --enable-calendar \ - --enable-ftp \ - --with-enchant=/usr \ - --enable-sysvmsg \ - --with-ffi \ - --enable-zend-test \ - --enable-dl-test=shared \ - --with-ldap \ - --with-ldap-sasl \ - --with-password-argon2 \ - --with-mhash \ - --with-sodium \ - --enable-dba \ - --with-cdb \ - --enable-flatfile \ - --enable-inifile \ - --with-tcadb \ - --with-lmdb \ - --with-qdbm \ - --with-snmp \ - --with-config-file-path=/etc \ - --with-config-file-scan-dir=/etc/php.d \ - --with-pdo-firebird \ - --enable-experimental-async-api \ # <-- PHP true async API support - --enable-async \ # <-- PHP true async extension - --disable-phpdbg - - run: - name: make - no_output_timeout: 30m - command: make -j2 > /dev/null - - run: - name: make install - command: | - sudo make install - sudo mkdir -p /etc/php.d - sudo chmod 777 /etc/php.d - echo opcache.enable_cli=1 > /etc/php.d/opcache.ini - echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini - - run: - name: Test - no_output_timeout: 30m - command: | - sapi/cli/php run-tests.php \ - -d zend_extension=opcache.so \ - -d opcache.enable_cli=1 \ - -d opcache.jit_buffer_size=64M \ - -d opcache.jit=tracing \ - -d zend_test.observer.enabled=1 \ - -d zend_test.observer.show_output=0 \ - -P -q -x -j2 \ - -g FAIL,BORK,LEAK,XLEAK \ - --no-progress \ - --offline \ - --show-diff \ - --show-slow 1000 \ - --set-timeout 120 \ - --repeat 2 - -workflows: - push-workflow: - jobs: - - arm From 6cebd7dce1bbda092c52d28d6ee0c62ebe74cf0a Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 07:08:59 +0300 Subject: [PATCH 114/137] % update docs --- docs/source/miscellaneous/writing-tests.rst | 22 +- docs/source/true_async_api/api-reference.rst | 423 +++++++++++++ docs/source/true_async_api/examples.rst | 452 ++++++++++++++ .../true_async_api/implementation-guide.rst | 4 +- docs/source/true_async_api/index.rst | 6 +- docs/source/true_async_api/patterns.rst | 567 ++++++++++++++++++ 6 files changed, 1458 insertions(+), 16 deletions(-) create mode 100644 docs/source/true_async_api/api-reference.rst create mode 100644 docs/source/true_async_api/examples.rst create mode 100644 docs/source/true_async_api/patterns.rst diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.rst index fd09d80f1275e..46f03f94703ad 100644 --- a/docs/source/miscellaneous/writing-tests.rst +++ b/docs/source/miscellaneous/writing-tests.rst @@ -95,13 +95,13 @@ might use a variation tests to test boundary conditions. How big is a test case? ======================= -Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The reason -for this is that if we break something in PHP and it breaks your test case we need to be able to -find out quite quickly what we broke, going through 1000s of line of test case output is not easy. -Having said that it's sometimes just not practical to stay within the 10 line guideline, in this -case you can help a lot by commenting the output. You may find plenty of much longer tests in PHP - -the small tests message is something that we learnt over time, in fact we are slowly going through -and splitting tests up when we need to. +Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The +reason for this is that if we break something in PHP and it breaks your test case we need to be able +to find out quite quickly what we broke, going through 1000s of line of test case output is not +easy. Having said that it's sometimes just not practical to stay within the 10 line guideline, in +this case you can help a lot by commenting the output. You may find plenty of much longer tests in +PHP - the small tests message is something that we learnt over time, in fact we are slowly going +through and splitting tests up when we need to. Comments ======== @@ -183,10 +183,10 @@ Testing your test cases Most people who write tests for PHP don't have access to a huge number of operating systems but the tests are run on every system that runs PHP. It's good to test your test on as many platforms as you -can — Linux and Windows are the most important, it's increasingly important to make sure that tests -run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — don't -worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of failures on -other platforms. If you don't have karma to commit have a look at the next section. +can — Linux and Windows are the most important, it's increasingly important to make sure that +tests run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — +don't worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of +failures on other platforms. If you don't have karma to commit have a look at the next section. When you are testing your test case it's really important to make sure that you clean up any temporary resources (eg files) that you used in the test. There is a special ``--CLEAN--`` section diff --git a/docs/source/true_async_api/api-reference.rst b/docs/source/true_async_api/api-reference.rst new file mode 100644 index 0000000000000..0f51f417b993c --- /dev/null +++ b/docs/source/true_async_api/api-reference.rst @@ -0,0 +1,423 @@ +############### + API Reference +############### + +This section provides comprehensive documentation for the TrueAsync API functions, macros, and data structures based on the actual implementation in ``Zend/zend_async_API.h``. + +****************** + API Version +****************** + +.. code:: c + + #define ZEND_ASYNC_API "TrueAsync API v0.4.0" + #define ZEND_ASYNC_API_VERSION_MAJOR 0 + #define ZEND_ASYNC_API_VERSION_MINOR 4 + #define ZEND_ASYNC_API_VERSION_PATCH 0 + +******************** + Core API Macros +******************** + +Enablement Check +================ + +.. code:: c + + ZEND_ASYNC_IS_ENABLED() + +Returns ``true`` if async functionality is available, ``false`` otherwise. +Always check this before using any async APIs. + +.. code:: c + + ZEND_ASYNC_REACTOR_IS_ENABLED() + +Returns ``true`` if reactor (event loop) is available and functional. + +********************* + Coroutine Operations +********************* + +Coroutine Creation +================== + +.. code:: c + + ZEND_ASYNC_SPAWN() + ZEND_ASYNC_SPAWN_WITH(scope) + ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider) + ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority) + ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority) + +Creates and spawns new coroutines with various configuration options: + +* ``ZEND_ASYNC_SPAWN()`` - Create coroutine with default settings +* ``ZEND_ASYNC_SPAWN_WITH(scope)`` - Create coroutine within specific scope +* ``ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider)`` - Use scope provider object +* ``ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority)`` - Set coroutine priority +* ``ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority)`` - Both scope and priority + +**Priority levels:** + +.. code:: c + + typedef enum { + ZEND_COROUTINE_NORMAL = 0, + ZEND_COROUTINE_HI_PRIORITY = 255 + } zend_coroutine_priority; + +Coroutine Management +==================== + +.. code:: c + + ZEND_ASYNC_NEW_COROUTINE(scope) + +Creates a new coroutine object without starting it. + +.. code:: c + + ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine) + +Adds coroutine to execution queue for scheduling. + +.. code:: c + + ZEND_ASYNC_SUSPEND() + ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN() + +Suspends current coroutine execution: + +* ``SUSPEND()`` - Normal suspension +* ``RUN_SCHEDULER_AFTER_MAIN()`` - Suspend from main context + +Coroutine Control +================= + +.. code:: c + + ZEND_ASYNC_RESUME(coroutine) + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error) + +Resume coroutine execution: + +* ``RESUME(coroutine)`` - Normal resume +* ``RESUME_WITH_ERROR(coroutine, error, transfer_error)`` - Resume with exception + +.. code:: c + + ZEND_ASYNC_CANCEL(coroutine, error, transfer_error) + ZEND_ASYNC_CANCEL_EX(coroutine, error, transfer_error, is_safely) + +Cancel coroutine execution: + +* ``CANCEL()`` - Cancel with default safety +* ``CANCEL_EX()`` - Cancel with explicit safety flag + +******************** + Scope API +******************** + +Scope Creation +============== + +.. code:: c + + ZEND_ASYNC_NEW_SCOPE(parent) + ZEND_ASYNC_NEW_SCOPE_WITH_OBJECT(parent) + +Create new async scopes for coroutine isolation: + +* ``NEW_SCOPE(parent)`` - Create scope with parent reference +* ``NEW_SCOPE_WITH_OBJECT(parent)`` - Create scope backed by zend_object + +Scope Flags +=========== + +.. code:: c + + #define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) + #define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) + #define ZEND_ASYNC_SCOPE_IS_DISPOSING(scope) + +Check scope status: + +* ``IS_CLOSED`` - Scope has been closed +* ``IS_CANCELLED`` - Scope was cancelled +* ``IS_DISPOSING`` - Scope is being disposed + +******************** + Event API +******************** + +Event Lifecycle +================ + +All async events implement these core function pointers: + +.. code:: c + + typedef void (*zend_async_event_start_t)(zend_async_event_t *event); + typedef void (*zend_async_event_stop_t)(zend_async_event_t *event); + typedef void (*zend_async_event_dispose_t)(zend_async_event_t *event); + +Event State Checking +===================== + +.. code:: c + + ZEND_ASYNC_EVENT_IS_CLOSED(event) + +Check if event has been closed. + +Event Reference Counting +========================= + +.. code:: c + + ZEND_ASYNC_EVENT_REF(event) + ZEND_ASYNC_EVENT_ADD_REF(event) + ZEND_ASYNC_EVENT_DEL_REF(event) + +Manage event reference counts for memory safety. + +Event Flags +=========== + +.. code:: c + + #define ZEND_ASYNC_EVENT_F_CLOSED (1u << 0) /* event was closed */ + #define ZEND_ASYNC_EVENT_F_RESULT_USED (1u << 1) /* result will be used */ + #define ZEND_ASYNC_EVENT_F_EXC_CAUGHT (1u << 2) /* error was caught */ + #define ZEND_ASYNC_EVENT_F_ZVAL_RESULT (1u << 3) /* result is zval */ + +.. code:: c + + ZEND_ASYNC_EVENT_SET_CLOSED(event) + ZEND_ASYNC_EVENT_CLR_CLOSED(event) + +Manipulate event flags. + +******************** + Reactor API +******************** + +Reactor Control +=============== + +.. code:: c + + ZEND_ASYNC_REACTOR_STARTUP() + +Start the async reactor (event loop). + +Poll Events +=========== + +.. code:: c + + typedef enum { + ASYNC_READABLE = 1, + ASYNC_WRITABLE = 2, + ASYNC_DISCONNECT = 4, + ASYNC_PRIORITIZED = 8 + } async_poll_event; + +Event flags for socket polling operations. + +******************** + Signal Constants +******************** + +.. code:: c + + #define ZEND_ASYNC_SIGHUP 1 + #define ZEND_ASYNC_SIGINT 2 + #define ZEND_ASYNC_SIGQUIT 3 + #define ZEND_ASYNC_SIGILL 4 + #define ZEND_ASYNC_SIGTERM 15 + #define ZEND_ASYNC_SIGBREAK 21 + #define ZEND_ASYNC_SIGABRT 22 + #define ZEND_ASYNC_SIGWINCH 28 + +Cross-platform signal constants for async signal handling. + +******************** + Process Execution +******************** + +Execution Modes +=============== + +.. code:: c + + typedef enum { + ZEND_ASYNC_EXEC_MODE_EXEC, /* exec() - last line only */ + ZEND_ASYNC_EXEC_MODE_SYSTEM, /* system() - print + return last */ + ZEND_ASYNC_EXEC_MODE_EXEC_ARRAY, /* exec() with array output */ + ZEND_ASYNC_EXEC_MODE_PASSTHRU, /* passthru() - binary output */ + ZEND_ASYNC_EXEC_MODE_SHELL_EXEC /* shell_exec() - buffer output */ + } zend_async_exec_mode; + +*********************** + Future and Channel API +*********************** + +Futures +======= + +.. code:: c + + ZEND_ASYNC_NEW_FUTURE(thread_safe) + ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) + +Create future objects for async result handling. + +Channels +======== + +.. code:: c + + ZEND_ASYNC_NEW_CHANNEL(buffer_size, resizable, thread_safe) + ZEND_ASYNC_NEW_CHANNEL_EX(buffer_size, resizable, thread_safe, extra_size) + +Create channels for coroutine communication. + +**************************** + API Context Switch Handlers +**************************** + +Registration +============ + +.. code:: c + + uint32_t zend_coroutine_add_switch_handler( + zend_coroutine_t *coroutine, + zend_coroutine_switch_handler_fn handler + ); + + bool zend_coroutine_remove_switch_handler( + zend_coroutine_t *coroutine, + uint32_t handler_index + ); + +Add/remove context switch handlers for individual coroutines. + +Global Handlers +=============== + +.. code:: c + + void zend_async_add_main_coroutine_start_handler( + zend_coroutine_switch_handler_fn handler + ); + +Add global handlers that are called when main coroutines start. + +Handler Function Type +===================== + +.. code:: c + + typedef bool (*zend_coroutine_switch_handler_fn)( + zend_coroutine_t *coroutine, + bool is_enter, /* true = entering, false = leaving */ + bool is_finishing /* true = coroutine finishing */ + ); + +Returns ``true`` to keep handler, ``false`` to remove it after execution. + +******************** + Utility Functions +******************** + +System Management +================= + +.. code:: c + + ZEND_ASYNC_SHUTDOWN() + ZEND_ASYNC_ENGINE_SHUTDOWN() + +Initiate graceful shutdown of async subsystem. + +.. code:: c + + ZEND_ASYNC_GET_COROUTINES() + ZEND_ASYNC_GET_AWAITING_INFO(coroutine) + +Get information about active coroutines and their await status. + +Microtasks +========== + +.. code:: c + + ZEND_ASYNC_ADD_MICROTASK(microtask) + +Add microtask to execution queue. + +Exception Handling +================== + +.. code:: c + + ZEND_ASYNC_SPAWN_AND_THROW(exception, scope, priority) + +Spawn coroutine specifically for throwing an exception within a scope. + +*********************** + API Error Management +*********************** + +API Exception Classes +====================== + +.. code:: c + + typedef enum { + ZEND_ASYNC_CLASS_NO = 0, + ZEND_ASYNC_CLASS_AWAITABLE = 1, + ZEND_ASYNC_CLASS_COROUTINE = 2 + } zend_async_class; + + ZEND_ASYNC_GET_CE(type) + ZEND_ASYNC_GET_EXCEPTION_CE(type) + +Get class entries for async-related classes and exceptions. + +******************** + Data Types +******************** + +Platform Types +=============== + +.. code:: c + + #ifdef PHP_WIN32 + typedef HANDLE zend_file_descriptor_t; + typedef DWORD zend_process_id_t; + typedef HANDLE zend_process_t; + typedef SOCKET zend_socket_t; + #else + typedef int zend_file_descriptor_t; + typedef pid_t zend_process_id_t; + typedef pid_t zend_process_t; + typedef int zend_socket_t; + #endif + +Cross-platform type definitions for file descriptors, processes, and sockets. + +************************ + API Best Practices +************************ + +1. **Always check enablement** with ``ZEND_ASYNC_IS_ENABLED()`` before using APIs +2. **Use appropriate scopes** for coroutine isolation +3. **Handle reference counting** properly with event objects +4. **Register cleanup handlers** using context switch handlers +5. **Check reactor availability** before using event-based operations +6. **Use priority levels** appropriately for coroutine scheduling \ No newline at end of file diff --git a/docs/source/true_async_api/examples.rst b/docs/source/true_async_api/examples.rst new file mode 100644 index 0000000000000..cd366e447adf2 --- /dev/null +++ b/docs/source/true_async_api/examples.rst @@ -0,0 +1,452 @@ +########## + Examples +########## + +This section provides practical C code examples for using the TrueAsync API in PHP extensions. + +****************** + Basic Examples +****************** + +Checking API Availability +========================== + +Always check if async is enabled before using the API: + +.. code:: c + + #include "Zend/zend_async_API.h" + + void my_async_function(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) { + // Fallback to synchronous operation + my_sync_function(); + return; + } + + // Use async API + my_actual_async_function(); + } + +Simple Coroutine Creation +========================= + +Creating and spawning a basic coroutine: + +.. code:: c + + static void my_coroutine_entry(void *arg) + { + php_printf("Hello from coroutine!\n"); + } + + void spawn_example(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + if (!coro) { + php_error(E_WARNING, "Failed to create coroutine"); + return; + } + + coro->internal_entry = my_coroutine_entry; + coro->internal_arg = NULL; + + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + } + +Using Scopes +============ + +Creating coroutines with custom scopes: + +.. code:: c + + void scope_example(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + // Create new scope + zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(NULL); + if (!scope) return; + + // Spawn coroutine within scope + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH(scope); + if (coro) { + coro->internal_entry = my_coroutine_entry; + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + } + } + +******************************** + Context Switch Handler Examples +******************************** + +Per-Coroutine Handler +===================== + +Adding context switch handlers to specific coroutines: + +.. code:: c + + static bool my_switch_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (is_finishing) { + php_printf("Coroutine finishing\n"); + return false; // Remove handler + } + + if (is_enter) { + php_printf("Entering coroutine\n"); + } else { + php_printf("Leaving coroutine\n"); + } + + return true; // Keep handler + } + + void add_handler_example(void) + { + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + if (!coro) return; + + uint32_t handler_id = zend_coroutine_add_switch_handler(coro, my_switch_handler); + + coro->internal_entry = my_coroutine_entry; + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + + // Later: remove handler if needed + // zend_coroutine_remove_switch_handler(coro, handler_id); + } + +Global Main Coroutine Handler +============================= + +Handler that applies to all main coroutines: + +.. code:: c + + static uint32_t my_context_key = 0; + + static bool main_coroutine_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (!is_enter) return true; + + // Initialize extension data for this coroutine + my_extension_data_t *data = ecalloc(1, sizeof(my_extension_data_t)); + data->initialized = true; + + // Store in coroutine internal context + zval data_zval; + ZVAL_PTR(&data_zval, data); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &data_zval); + + return false; // Remove handler after initialization + } + + PHP_MINIT_FUNCTION(my_extension) + { + my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); + zend_async_add_main_coroutine_start_handler(main_coroutine_handler); + return SUCCESS; + } + +****************** + Event Operations +****************** + +Basic Event Usage +================= + +Working with async events (using timer as example): + +.. code:: c + + static void timer_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + if (exception) { + php_printf("Timer error occurred\n"); + } else { + php_printf("Timer fired!\n"); + } + + // Clean up + ZEND_ASYNC_EVENT_DEL_REF(event); + } + + void timer_example(void) + { + if (!ZEND_ASYNC_REACTOR_IS_ENABLED()) return; + + // This would need actual reactor implementation + // zend_async_timer_event_t *timer = zend_async_new_timer_event_fn(1000, 0, false, 0); + // if (!timer) return; + + // zend_async_event_callback_t *callback = ...; + // timer->base.add_callback(&timer->base, callback); + // timer->base.start(&timer->base); + } + +*************************** + Internal Context Examples +*************************** + +Storing Extension Data +====================== + +Using internal context to store per-coroutine data: + +.. code:: c + + static uint32_t cache_context_key = 0; + + typedef struct { + HashTable *cached_results; + time_t last_access; + } cache_data_t; + + PHP_MINIT_FUNCTION(cache_extension) + { + cache_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("cache_data"); + return SUCCESS; + } + + cache_data_t* get_cache_data(zend_coroutine_t *coroutine) + { + zval *cached = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, cache_context_key); + if (cached && Z_TYPE_P(cached) == IS_PTR) { + return (cache_data_t *)Z_PTR_P(cached); + } + + // Create new cache data + cache_data_t *data = ecalloc(1, sizeof(cache_data_t)); + ALLOC_HASHTABLE(data->cached_results); + zend_hash_init(data->cached_results, 16, NULL, ZVAL_PTR_DTOR, 0); + data->last_access = time(NULL); + + // Store in context + zval data_zval; + ZVAL_PTR(&data_zval, data); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, cache_context_key, &data_zval); + + return data; + } + +************************* + Error Handling Examples +************************* + +Exception Creation +================== + +Creating and handling exceptions in async context: + +.. code:: c + + void async_operation_with_error(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + if (!coro) { + zend_throw_error(NULL, "Failed to create coroutine"); + return; + } + + // Set up coroutine that might throw + coro->internal_entry = error_prone_operation; + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + } + + static void error_prone_operation(void *arg) + { + // Simulate error condition + if (some_error_condition()) { + zend_object *exception = zend_throw_exception( + zend_ce_exception, "Async operation failed", 0 + ); + + // Resume current coroutine with error + zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; + if (current) { + ZEND_ASYNC_RESUME_WITH_ERROR(current, exception, true); + } + return; + } + + php_printf("Operation completed successfully\n"); + } + +*************************** + Memory Management Examples +*************************** + +Reference Counting Examples +============================= + +Proper event reference management: + +.. code:: c + + void event_reference_example(void) + { + // Assuming we have an event from somewhere + zend_async_event_t *event = get_some_event(); + + // Add reference when storing event + ZEND_ASYNC_EVENT_ADD_REF(event); + + // Store event somewhere + store_event_reference(event); + + // Later: remove reference when done + ZEND_ASYNC_EVENT_DEL_REF(event); + + // Or use RELEASE which decrements and disposes if needed + // ZEND_ASYNC_EVENT_RELEASE(event); + } + +******************************* + Futures and Channels Examples +******************************* + +Future Creation +=============== + +Creating and using futures for async results: + +.. code:: c + + void future_example(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + // Create thread-safe future + zend_future_t *future = ZEND_ASYNC_NEW_FUTURE(true); + if (!future) return; + + // Future can be resolved from any thread + // zend_future_resolve(future, &result_zval); + + // Or with exception + // zend_future_reject(future, exception); + } + +Channel Communication +===================== + +Creating channels for coroutine communication: + +.. code:: c + + void channel_example(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + // Create buffered channel + zend_async_channel_t *channel = ZEND_ASYNC_NEW_CHANNEL(10, true, false); + if (!channel) return; + + // Channels support send/receive operations + // channel->send(channel, &value); + // channel->receive(channel, &result); + } + +************************ + Extension Integration +************************ + +Complete Extension Example +========================== + +Full example of async-aware extension: + +.. code:: c + + static uint32_t my_ext_context_key = 0; + + typedef struct { + bool async_enabled; + HashTable *pending_operations; + } my_extension_context_t; + + static bool my_ext_main_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (is_finishing) { + // Cleanup on coroutine completion + zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_ext_context_key); + if (ctx_zval && Z_TYPE_P(ctx_zval) == IS_PTR) { + my_extension_context_t *ctx = (my_extension_context_t *)Z_PTR_P(ctx_zval); + if (ctx->pending_operations) { + zend_hash_destroy(ctx->pending_operations); + FREE_HASHTABLE(ctx->pending_operations); + } + efree(ctx); + } + return false; + } + + if (!is_enter) return true; + + // Initialize context for new main coroutine + my_extension_context_t *ctx = ecalloc(1, sizeof(my_extension_context_t)); + ctx->async_enabled = true; + + ALLOC_HASHTABLE(ctx->pending_operations); + zend_hash_init(ctx->pending_operations, 8, NULL, ZVAL_PTR_DTOR, 0); + + zval ctx_zval; + ZVAL_PTR(&ctx_zval, ctx); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_ext_context_key, &ctx_zval); + + return false; // Remove handler after init + } + + PHP_MINIT_FUNCTION(my_extension) + { + my_ext_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); + + if (ZEND_ASYNC_IS_ENABLED()) { + zend_async_add_main_coroutine_start_handler(my_ext_main_handler); + } + + return SUCCESS; + } + + // Extension function that works async-aware + PHP_FUNCTION(my_async_operation) + { + if (!ZEND_ASYNC_IS_ENABLED()) { + // Fallback to sync operation + my_sync_operation(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } + + zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; + if (current) { + my_extension_context_t *ctx = get_my_context(current); + if (ctx && ctx->async_enabled) { + // Perform async operation + perform_async_operation(current); + return; + } + } + + // Default to sync + my_sync_operation(INTERNAL_FUNCTION_PARAM_PASSTHRU); + } + +*************************** + Examples Best Practices +*************************** + +1. **Always check enablement** before using async APIs +2. **Use internal context** for per-coroutine data storage +3. **Handle reference counting** properly for events +4. **Register cleanup handlers** for resource management +5. **Provide sync fallbacks** when async is not available +6. **Use appropriate scopes** for coroutine isolation +7. **Handle exceptions** properly in async context \ No newline at end of file diff --git a/docs/source/true_async_api/implementation-guide.rst b/docs/source/true_async_api/implementation-guide.rst index de00e78edda29..cd18321fef43c 100644 --- a/docs/source/true_async_api/implementation-guide.rst +++ b/docs/source/true_async_api/implementation-guide.rst @@ -83,9 +83,9 @@ All implementations must match the exact function pointer signatures defined in const bool is_periodic, size_t extra_size ); -*********************** +************************ Implementation Patterns -*********************** +************************ Event Structure Pattern ======================= diff --git a/docs/source/true_async_api/index.rst b/docs/source/true_async_api/index.rst index 3d1c455615764..67582e23119fe 100644 --- a/docs/source/true_async_api/index.rst +++ b/docs/source/true_async_api/index.rst @@ -85,9 +85,9 @@ For implementation details, see the :doc:`implementation-guide` section. // Later, in callback: clean up ZEND_ASYNC_FREEADDRINFO(result); -****************** +*********************** Documentation Sections -****************** +*********************** :doc:`architecture` Detailed explanation of the two-repository architecture and component separation. @@ -105,4 +105,4 @@ For implementation details, see the :doc:`implementation-guide` section. Common patterns, best practices, and coding conventions for the API. :doc:`examples` - Practical examples showing how to use different API components. \ No newline at end of file + Practical C code examples showing how to use different API components. \ No newline at end of file diff --git a/docs/source/true_async_api/patterns.rst b/docs/source/true_async_api/patterns.rst new file mode 100644 index 0000000000000..e478f9a9ccb95 --- /dev/null +++ b/docs/source/true_async_api/patterns.rst @@ -0,0 +1,567 @@ +########## + Patterns +########## + +This section covers common patterns and best practices for implementing TrueAsync API in PHP extensions. + +********************* + Fundamental Patterns +********************* + +Async-Aware Function Pattern +============================ + +The standard pattern for making extension functions async-aware: + +.. code:: c + + PHP_FUNCTION(my_extension_function) + { + // 1. Check if async is enabled + if (!ZEND_ASYNC_IS_ENABLED()) { + // Fallback to synchronous implementation + my_sync_implementation(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } + + // 2. Check if we're in coroutine context + zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; + if (!current) { + // Not in coroutine, use sync + my_sync_implementation(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } + + // 3. Perform async operation + my_async_implementation(current, INTERNAL_FUNCTION_PARAM_PASSTHRU); + } + +API Enablement Check Pattern +============================ + +Always check API availability before using async features: + +.. code:: c + + void my_extension_init(void) + { + if (ZEND_ASYNC_IS_ENABLED()) { + // Initialize async-specific resources + init_async_resources(); + + if (ZEND_ASYNC_REACTOR_IS_ENABLED()) { + // Reactor is available, can use events + init_event_handlers(); + } + } + } + +***************************** + Context Management Patterns +***************************** + +Extension Context Initialization +================================ + +Pattern for setting up per-coroutine extension data: + +.. code:: c + + static uint32_t my_context_key = 0; + + typedef struct { + HashTable *cache; + zend_async_timer_event_t *cleanup_timer; + bool initialized; + } my_context_t; + + static bool my_context_initializer(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (is_finishing) { + // Cleanup context + zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_context_key); + if (ctx_zval && Z_TYPE_P(ctx_zval) == IS_PTR) { + my_context_t *ctx = (my_context_t *)Z_PTR_P(ctx_zval); + cleanup_my_context(ctx); + } + return false; // Remove handler + } + + if (!is_enter) return true; + + // Initialize context + my_context_t *ctx = ecalloc(1, sizeof(my_context_t)); + ALLOC_HASHTABLE(ctx->cache); + zend_hash_init(ctx->cache, 16, NULL, ZVAL_PTR_DTOR, 0); + ctx->initialized = true; + + zval ctx_zval; + ZVAL_PTR(&ctx_zval, ctx); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &ctx_zval); + + return false; // One-time initialization + } + + PHP_MINIT_FUNCTION(my_extension) + { + my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); + + if (ZEND_ASYNC_IS_ENABLED()) { + zend_async_add_main_coroutine_start_handler(my_context_initializer); + } + + return SUCCESS; + } + +Context Access Pattern +====================== + +Safe pattern for accessing extension context: + +.. code:: c + + my_context_t* get_my_context(zend_coroutine_t *coroutine) + { + if (!coroutine) { + coroutine = ZEND_ASYNC_CURRENT_COROUTINE; + if (!coroutine) return NULL; + } + + zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_context_key); + if (!ctx_zval || Z_TYPE_P(ctx_zval) != IS_PTR) { + return NULL; + } + + my_context_t *ctx = (my_context_t *)Z_PTR_P(ctx_zval); + return ctx->initialized ? ctx : NULL; + } + +****************************** + Resource Management Patterns +****************************** + +RAII Pattern for Events +======================= + +Resource Acquisition Is Initialization pattern for async events: + +.. code:: c + + typedef struct { + zend_async_event_t *event; + bool owns_reference; + } event_guard_t; + + static event_guard_t* event_guard_create(zend_async_event_t *event, bool add_ref) + { + event_guard_t *guard = emalloc(sizeof(event_guard_t)); + guard->event = event; + guard->owns_reference = add_ref; + + if (add_ref) { + ZEND_ASYNC_EVENT_ADD_REF(event); + } + + return guard; + } + + static void event_guard_destroy(event_guard_t *guard) + { + if (guard->owns_reference && guard->event) { + ZEND_ASYNC_EVENT_DEL_REF(guard->event); + } + efree(guard); + } + + void my_operation_with_event(void) + { + zend_async_event_t *event = create_some_event(); + event_guard_t *guard = event_guard_create(event, true); + + // Use event... + + // Automatic cleanup + event_guard_destroy(guard); + } + +Cleanup Callback Pattern +========================= + +Registering cleanup callbacks for async operations: + +.. code:: c + + typedef struct { + void *user_data; + void (*cleanup_fn)(void *data); + } cleanup_context_t; + + static void cleanup_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + cleanup_context_t *ctx = (cleanup_context_t *)callback->arg; + + if (ctx->cleanup_fn && ctx->user_data) { + ctx->cleanup_fn(ctx->user_data); + } + + efree(ctx); + } + + void register_cleanup(zend_async_event_t *event, void *data, void (*cleanup_fn)(void*)) + { + cleanup_context_t *ctx = emalloc(sizeof(cleanup_context_t)); + ctx->user_data = data; + ctx->cleanup_fn = cleanup_fn; + + zend_async_event_callback_t *callback = zend_async_event_callback_new( + event, cleanup_callback, 0 + ); + callback->arg = ctx; + + event->add_callback(event, callback); + } + +************************* + Error Handling Patterns +************************* + +Exception Propagation Pattern +============================= + +Proper exception handling in async context: + +.. code:: c + + static void async_operation_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + zend_coroutine_t *coroutine = (zend_coroutine_t *)callback->arg; + + if (exception) { + // Propagate exception to coroutine + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, true); + } else { + // Process result and resume normally + process_async_result(result); + ZEND_ASYNC_RESUME(coroutine); + } + + // Cleanup + ZEND_ASYNC_EVENT_DEL_REF(event); + } + +Error Recovery Pattern +====================== + +Handling recoverable errors in async operations: + +.. code:: c + + typedef struct { + int retry_count; + int max_retries; + void *operation_data; + } retry_context_t; + + static void retry_operation_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + retry_context_t *ctx = (retry_context_t *)callback->arg; + + if (exception && ctx->retry_count < ctx->max_retries) { + // Retry the operation + ctx->retry_count++; + php_error(E_NOTICE, "Retrying operation (attempt %d/%d)", + ctx->retry_count, ctx->max_retries); + + retry_async_operation(ctx); + return; + } + + // Either success or max retries reached + if (exception) { + handle_final_error(exception, ctx); + } else { + handle_success(result, ctx); + } + + efree(ctx); + } + +********************* + Performance Patterns +********************* + +Object Pooling Pattern +====================== + +Reusing objects to reduce allocation overhead: + +.. code:: c + + static zend_async_event_callback_t *callback_pool[MAX_POOL_SIZE]; + static int pool_size = 0; + static zend_mutex_t pool_mutex; + + static zend_async_event_callback_t* get_pooled_callback(void) + { + zend_mutex_lock(&pool_mutex); + + zend_async_event_callback_t *callback = NULL; + if (pool_size > 0) { + callback = callback_pool[--pool_size]; + } + + zend_mutex_unlock(&pool_mutex); + + if (!callback) { + callback = emalloc(sizeof(zend_async_event_callback_t)); + memset(callback, 0, sizeof(zend_async_event_callback_t)); + } + + return callback; + } + + static void return_pooled_callback(zend_async_event_callback_t *callback) + { + // Reset callback state + memset(callback, 0, sizeof(zend_async_event_callback_t)); + + zend_mutex_lock(&pool_mutex); + + if (pool_size < MAX_POOL_SIZE) { + callback_pool[pool_size++] = callback; + } else { + efree(callback); + } + + zend_mutex_unlock(&pool_mutex); + } + +Batch Operations Pattern +======================== + +Grouping operations for better performance: + +.. code:: c + + typedef struct { + zend_async_event_t **events; + int event_count; + int completed_count; + void (*completion_callback)(void *batch_data); + void *batch_data; + } batch_operation_t; + + static void batch_item_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + batch_operation_t *batch = (batch_operation_t *)callback->arg; + + __sync_fetch_and_add(&batch->completed_count, 1); + + if (batch->completed_count == batch->event_count) { + // All operations completed + if (batch->completion_callback) { + batch->completion_callback(batch->batch_data); + } + cleanup_batch(batch); + } + } + +********************* + Integration Patterns +********************* + +Reactor Integration Pattern +=========================== + +Integrating with external event loops: + +.. code:: c + + typedef struct { + my_event_loop_t *external_loop; + HashTable *pending_events; + bool integrated; + } reactor_integration_t; + + static void bridge_event_callback(my_external_event_t *ext_event, void *data) + { + zend_async_event_t *async_event = (zend_async_event_t *)data; + + // Convert external event result to async event result + void *result = convert_external_result(ext_event); + zend_object *exception = check_external_error(ext_event); + + // Notify async event callbacks + async_event->callbacks_notify(async_event, result, exception); + } + + bool integrate_with_reactor(my_event_loop_t *loop) + { + if (!ZEND_ASYNC_REACTOR_IS_ENABLED()) { + return false; + } + + reactor_integration_t *integration = emalloc(sizeof(reactor_integration_t)); + integration->external_loop = loop; + + ALLOC_HASHTABLE(integration->pending_events); + zend_hash_init(integration->pending_events, 32, NULL, NULL, 0); + + // Set up bridge between external loop and async events + loop->set_callback(loop, bridge_event_callback); + + return true; + } + +****************** + Testing Patterns +****************** + +Mock Implementation Pattern +=========================== + +Creating testable async code: + +.. code:: c + + #ifdef TESTING_MODE + + typedef struct { + zend_async_event_t base; + void *expected_result; + zend_object *expected_exception; + bool should_fail; + } mock_event_t; + + static void mock_event_start(zend_async_event_t *event) + { + mock_event_t *mock = (mock_event_t *)event; + + // Simulate immediate completion for testing + if (mock->should_fail) { + event->callbacks_notify(event, NULL, mock->expected_exception); + } else { + event->callbacks_notify(event, mock->expected_result, NULL); + } + } + + mock_event_t* create_mock_event(void *result, zend_object *exception) + { + mock_event_t *mock = emalloc(sizeof(mock_event_t)); + memset(mock, 0, sizeof(mock_event_t)); + + mock->base.start = mock_event_start; + mock->expected_result = result; + mock->expected_exception = exception; + mock->should_fail = (exception != NULL); + + return mock; + } + + #endif + +****************** + Advanced Patterns +****************** + +State Machine Pattern +====================== + +Managing complex async state transitions: + +.. code:: c + + typedef enum { + STATE_IDLE, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_SENDING, + STATE_RECEIVING, + STATE_ERROR, + STATE_CLOSED + } connection_state_t; + + typedef struct { + connection_state_t state; + zend_async_event_t *current_event; + void (*state_handlers[STATE_CLOSED + 1])(void *connection); + void *connection_data; + } async_state_machine_t; + + static void transition_state(async_state_machine_t *sm, connection_state_t new_state) + { + if (sm->state != new_state) { + sm->state = new_state; + + if (sm->state_handlers[new_state]) { + sm->state_handlers[new_state](sm->connection_data); + } + } + } + +Producer-Consumer Pattern +========================= + +Implementing async producer-consumer with channels: + +.. code:: c + + typedef struct { + zend_async_channel_t *channel; + zend_coroutine_t *producer_coro; + zend_coroutine_t *consumer_coro; + bool shutdown_requested; + } producer_consumer_t; + + static void producer_coroutine(void *arg) + { + producer_consumer_t *pc = (producer_consumer_t *)arg; + + while (!pc->shutdown_requested) { + // Produce data + zval data; + produce_data(&data); + + // Send to channel (may suspend if channel is full) + if (!pc->channel->send(pc->channel, &data)) { + break; // Channel closed + } + } + } + + static void consumer_coroutine(void *arg) + { + producer_consumer_t *pc = (producer_consumer_t *)arg; + + while (!pc->shutdown_requested) { + zval data; + + // Receive from channel (may suspend if channel is empty) + if (!pc->channel->receive(pc->channel, &data)) { + break; // Channel closed + } + + // Process data + process_data(&data); + zval_dtor(&data); + } + } + +************************ + Best Practices Summary +************************ + +1. **Always check enablement** before using async APIs +2. **Provide sync fallbacks** for non-async environments +3. **Use internal context** for per-coroutine data +4. **Handle reference counting** properly +5. **Register cleanup handlers** for resource management +6. **Use appropriate error handling** patterns +7. **Consider performance** with pooling and batching +8. **Test thoroughly** with mock implementations +9. **Document async behavior** clearly for users +10. **Follow reactor integration** patterns for external loops \ No newline at end of file From 8f4778f339516dd182edb48fdd74d1b1e46caab3 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 08:16:28 +0300 Subject: [PATCH 115/137] * fix GC end var warning --- Zend/zend_gc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index d3a020d9bf199..1d050f19e2ab6 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2265,8 +2265,6 @@ ZEND_API int zend_gc_collect_cycles(void) stack.next = NULL; #endif - end = 0; - if (GC_G(gc_active)) { GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; GC_COLLECT_FINISH_0; @@ -2347,6 +2345,7 @@ ZEND_API int zend_gc_collect_cycles(void) if (EXPECTED(!EG(active_fiber))) { #ifdef PHP_ASYNC_API continue_calling_destructors: + end = GC_G(first_unused); if (UNEXPECTED(FAILURE == gc_call_destructors(GC_G(dtor_idx), GC_G(first_unused), NULL))) { // // gc_call_destructors returns FAILURE when a destructor interrupts execution, From ebfecef9a8f7010d0ee9572ad77e725cba694eab Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:31:42 +0300 Subject: [PATCH 116/137] + APIs for channels and futures have been added. The API for working with the thread pool has been updated. An API for CoroutineGroup has been introduced. --- Zend/zend_async_API.c | 41 ++++++++++++++++++++++++++++++++++---- Zend/zend_async_API.h | 46 +++++++++++++++++++++++++++++++++---------- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 0559bc82d45b9..74cc0452ef715 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -48,13 +48,31 @@ static void shutdown_stub(void) {} static zend_array* get_coroutines_stub(void) { return NULL; } -static zend_future_t* future_create_stub(bool thread_safe, size_t extra_size) +static zend_future_t* new_future_stub(bool thread_safe, size_t extra_size) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; } -static zend_async_channel_t* channel_create_stub(size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size) +static zend_async_channel_t* new_channel_stub(size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size) +{ + ASYNC_THROW_ERROR("Async API is not enabled"); + return NULL; +} + +static zend_async_group_t* new_group_stub(size_t extra_size) +{ + ASYNC_THROW_ERROR("Async API is not enabled"); + return NULL; +} + +static zend_object* new_future_obj_stub(zend_future_t *future) +{ + ASYNC_THROW_ERROR("Async API is not enabled"); + return NULL; +} + +static zend_object* new_channel_obj_stub(zend_async_channel_t *channel) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; @@ -109,8 +127,13 @@ zend_async_get_coroutines_t zend_async_get_coroutines_fn = get_coroutines_stub; zend_async_add_microtask_t zend_async_add_microtask_fn = add_microtask_stub; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = get_awaiting_info_stub; zend_async_get_class_ce_t zend_async_get_class_ce_fn = get_class_ce; -zend_async_future_create_t zend_async_future_create_fn = future_create_stub; -zend_async_channel_create_t zend_async_channel_create_fn = channel_create_stub; +zend_async_new_future_t zend_async_new_future_fn = new_future_stub; +zend_async_new_channel_t zend_async_new_channel_fn = new_channel_stub; +zend_async_new_future_obj_t zend_async_new_future_obj_fn = new_future_obj_stub; +zend_async_new_channel_obj_t zend_async_new_channel_obj_fn = new_channel_obj_stub; + +/* GROUP API */ +zend_async_new_group_t zend_async_new_group_fn = new_group_stub; static zend_atomic_bool reactor_lock = {0}; static char * reactor_module_name = NULL; @@ -243,6 +266,11 @@ ZEND_API bool zend_async_scheduler_register( zend_async_get_awaiting_info_t get_awaiting_info_fn, zend_async_get_class_ce_t get_class_ce_fn, zend_async_new_iterator_t new_iterator_fn, + zend_async_new_future_t new_future_fn, + zend_async_new_channel_t new_channel_fn, + zend_async_new_future_obj_t new_future_obj_fn, + zend_async_new_channel_obj_t new_channel_obj_fn, + zend_async_new_group_t new_group_fn, zend_async_engine_shutdown_t engine_shutdown_fn ) { @@ -280,6 +308,11 @@ ZEND_API bool zend_async_scheduler_register( zend_async_get_awaiting_info_fn = get_awaiting_info_fn; zend_async_get_class_ce_fn = get_class_ce_fn; zend_async_new_iterator_fn = new_iterator_fn; + zend_async_new_future_fn = new_future_fn; + zend_async_new_channel_fn = new_channel_fn; + zend_async_new_future_obj_fn = new_future_obj_fn; + zend_async_new_channel_obj_fn = new_channel_obj_fn; + zend_async_new_group_fn = new_group_fn; zend_async_engine_shutdown_fn = engine_shutdown_fn; zend_atomic_bool_store(&scheduler_lock, 0); diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 9510543510603..4b93a0c4af13d 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -19,9 +19,9 @@ #include "zend_fibers.h" #include "zend_globals.h" -#define ZEND_ASYNC_API "TrueAsync API v0.4.0" +#define ZEND_ASYNC_API "TrueAsync API v0.5.0" #define ZEND_ASYNC_API_VERSION_MAJOR 0 -#define ZEND_ASYNC_API_VERSION_MINOR 4 +#define ZEND_ASYNC_API_VERSION_MINOR 5 #define ZEND_ASYNC_API_VERSION_PATCH 0 #define ZEND_ASYNC_API_VERSION_NUMBER \ @@ -106,6 +106,7 @@ typedef enum ZEND_ASYNC_CLASS_CHANNEL = 10, ZEND_ASYNC_CLASS_FUTURE = 11, + ZEND_ASYNC_CLASS_GROUP = 12, ZEND_ASYNC_EXCEPTION_DEFAULT = 30, ZEND_ASYNC_EXCEPTION_CANCELLATION = 31, @@ -134,6 +135,7 @@ typedef struct _zend_async_waker_s zend_async_waker_t; typedef struct _zend_async_microtask_s zend_async_microtask_t; typedef struct _zend_async_scope_s zend_async_scope_t; typedef struct _zend_async_iterator_s zend_async_iterator_t; +typedef struct _zend_async_group_s zend_async_group_t; typedef struct _zend_fcall_s zend_fcall_t; typedef void (*zend_coroutine_entry_t)(void); @@ -217,8 +219,13 @@ typedef zend_array* (*zend_async_get_coroutines_t)(void); typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); typedef zend_class_entry* (*zend_async_get_class_ce_t)(zend_async_class type); -typedef zend_future_t* (*zend_async_future_create_t)(bool thread_safe, size_t extra_size); -typedef zend_async_channel_t* (*zend_async_channel_create_t)(size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size); +typedef zend_future_t* (*zend_async_new_future_t)(bool thread_safe, size_t extra_size); +typedef zend_async_channel_t* (*zend_async_new_channel_t)(size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size); + +typedef zend_async_group_t* (*zend_async_new_group_t)(size_t extra_size); + +typedef zend_object* (*zend_async_new_future_obj_t)(zend_future_t *future); +typedef zend_object* (*zend_async_new_channel_obj_t)(zend_async_channel_t *channel); typedef void (*zend_async_reactor_startup_t)(void); typedef void (*zend_async_reactor_shutdown_t)(void); @@ -286,6 +293,8 @@ typedef int (* zend_async_exec_t) ( typedef void (*zend_async_queue_task_t)(zend_async_task_t *task); +typedef void (*zend_async_task_run_t)(zend_async_task_t *task); + typedef void (*zend_async_microtask_handler_t)(zend_async_microtask_t *microtask); struct _zend_fcall_s { @@ -732,6 +741,7 @@ struct _zend_async_listen_event_s { struct _zend_async_task_s { zend_async_event_t base; + zend_async_task_run_t run; }; struct _zend_async_trigger_event_s { @@ -1259,8 +1269,13 @@ ZEND_API extern zend_async_get_coroutines_t zend_async_get_coroutines_fn; ZEND_API extern zend_async_add_microtask_t zend_async_add_microtask_fn; ZEND_API extern zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn; ZEND_API extern zend_async_get_class_ce_t zend_async_get_class_ce_fn; -ZEND_API extern zend_async_future_create_t zend_async_future_create_fn; -ZEND_API extern zend_async_channel_create_t zend_async_channel_create_fn; +ZEND_API extern zend_async_new_future_t zend_async_new_future_fn; +ZEND_API extern zend_async_new_channel_t zend_async_new_channel_fn; +ZEND_API extern zend_async_new_future_obj_t zend_async_new_future_obj_fn; +ZEND_API extern zend_async_new_channel_obj_t zend_async_new_channel_obj_fn; + +/* GROUP API */ +ZEND_API extern zend_async_new_group_t zend_async_new_group_fn; /* Iterator API */ ZEND_API extern zend_async_new_iterator_t zend_async_new_iterator_fn; @@ -1335,6 +1350,11 @@ ZEND_API bool zend_async_scheduler_register( zend_async_get_awaiting_info_t get_awaiting_info_fn, zend_async_get_class_ce_t get_class_ce_fn, zend_async_new_iterator_t new_iterator_fn, + zend_async_new_future_t new_future_fn, + zend_async_new_channel_t new_channel_fn, + zend_async_new_future_obj_t new_future_obj_fn, + zend_async_new_channel_obj_t new_channel_obj_fn, + zend_async_new_group_t new_group_fn, zend_async_engine_shutdown_t engine_shutdown_fn ); @@ -1446,12 +1466,18 @@ ZEND_API void zend_async_add_main_coroutine_start_handler( ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *main_coroutine); /* Future API Functions */ -#define ZEND_ASYNC_NEW_FUTURE(thread_safe) zend_async_future_create_fn(thread_safe, 0) -#define ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) zend_async_future_create_fn(thread_safe, extra_size) +#define ZEND_ASYNC_NEW_FUTURE(thread_safe) zend_async_new_future_fn(thread_safe, 0) +#define ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) zend_async_new_future_fn(thread_safe, extra_size) +#define ZEND_ASYNC_NEW_FUTURE_OBJ(future) zend_async_new_future_obj_fn(future) /* Channel API Functions */ -#define ZEND_ASYNC_NEW_CHANNEL(buffer_size, resizable, thread_safe) zend_async_channel_create_fn(buffer_size, resizable, thread_safe, 0) -#define ZEND_ASYNC_NEW_CHANNEL_EX(buffer_size, resizable, thread_safe, extra_size) zend_async_channel_create_fn(buffer_size, resizable, thread_safe, extra_size) +#define ZEND_ASYNC_NEW_CHANNEL(buffer_size, resizable, thread_safe) zend_async_new_channel_fn(buffer_size, resizable, thread_safe, 0) +#define ZEND_ASYNC_NEW_CHANNEL_EX(buffer_size, resizable, thread_safe, extra_size) zend_async_new_channel_fn(buffer_size, resizable, thread_safe, extra_size) +#define ZEND_ASYNC_NEW_CHANNEL_OBJ(channel) zend_async_new_channel_obj_fn(channel) + +/* GROUP API Functions */ +#define ZEND_ASYNC_NEW_GROUP() zend_async_new_group_fn(0) +#define ZEND_ASYNC_NEW_GROUP_EX(extra_size) zend_async_new_group_fn(extra_size) END_EXTERN_C() From aadca213bcdc52a30c9674891b4990ad660be5a9 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:54:08 +0300 Subject: [PATCH 117/137] % refactoring zend_async_resume_when with zend_exception_save/restore --- Zend/zend_async_API.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 74cc0452ef715..df9e1b44010c9 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -701,16 +701,13 @@ ZEND_API void zend_async_resume_when( zend_coroutine_event_callback_t *event_callback ) { - ZEND_ASSERT(EG(exception) == NULL && "Cannot resume when there is an active exception in the engine."); - - if (UNEXPECTED(EG(exception))) { - return; - } + zend_exception_save(); bool locally_allocated_callback = false; if (UNEXPECTED(ZEND_ASYNC_EVENT_IS_CLOSED(event))) { zend_throw_error(NULL, "The event cannot be used after it has been terminated"); + zend_exception_restore(); return; } @@ -721,6 +718,7 @@ ZEND_API void zend_async_resume_when( event->dispose(event); } + zend_exception_restore(); return; } @@ -755,6 +753,7 @@ ZEND_API void zend_async_resume_when( event->dispose(event); } + zend_exception_restore(); return; } @@ -799,6 +798,8 @@ ZEND_API void zend_async_resume_when( } } } + + zend_exception_restore(); } ZEND_API void zend_async_waker_callback_resolve( From f7825de63b9e9799cb1450ba28bfca17e23be7a9 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 10:43:21 +0300 Subject: [PATCH 118/137] * fix DOCS --- docs/source/true_async_api/api-reference.rst | 123 +++++----- docs/source/true_async_api/architecture.rst | 27 +-- docs/source/true_async_api/coroutines.rst | 222 +++++++++-------- docs/source/true_async_api/events.rst | 226 +++++++++--------- docs/source/true_async_api/examples.rst | 152 ++++++------ .../true_async_api/implementation-guide.rst | 156 ++++++------ docs/source/true_async_api/index.rst | 84 +++---- docs/source/true_async_api/patterns.rst | 187 ++++++++------- 8 files changed, 594 insertions(+), 583 deletions(-) diff --git a/docs/source/true_async_api/api-reference.rst b/docs/source/true_async_api/api-reference.rst index 0f51f417b993c..5deecf10dbd7c 100644 --- a/docs/source/true_async_api/api-reference.rst +++ b/docs/source/true_async_api/api-reference.rst @@ -2,11 +2,12 @@ API Reference ############### -This section provides comprehensive documentation for the TrueAsync API functions, macros, and data structures based on the actual implementation in ``Zend/zend_async_API.h``. +This section provides comprehensive documentation for the TrueAsync API functions, macros, and data +structures based on the actual implementation in ``Zend/zend_async_API.h``. -****************** +************* API Version -****************** +************* .. code:: c @@ -15,9 +16,9 @@ This section provides comprehensive documentation for the TrueAsync API function #define ZEND_ASYNC_API_VERSION_MINOR 4 #define ZEND_ASYNC_API_VERSION_PATCH 0 -******************** +***************** Core API Macros -******************** +***************** Enablement Check ================ @@ -26,8 +27,8 @@ Enablement Check ZEND_ASYNC_IS_ENABLED() -Returns ``true`` if async functionality is available, ``false`` otherwise. -Always check this before using any async APIs. +Returns ``true`` if async functionality is available, ``false`` otherwise. Always check this before +using any async APIs. .. code:: c @@ -35,9 +36,9 @@ Always check this before using any async APIs. Returns ``true`` if reactor (event loop) is available and functional. -********************* +********************** Coroutine Operations -********************* +********************** Coroutine Creation ================== @@ -52,11 +53,11 @@ Coroutine Creation Creates and spawns new coroutines with various configuration options: -* ``ZEND_ASYNC_SPAWN()`` - Create coroutine with default settings -* ``ZEND_ASYNC_SPAWN_WITH(scope)`` - Create coroutine within specific scope -* ``ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider)`` - Use scope provider object -* ``ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority)`` - Set coroutine priority -* ``ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority)`` - Both scope and priority +- ``ZEND_ASYNC_SPAWN()`` - Create coroutine with default settings +- ``ZEND_ASYNC_SPAWN_WITH(scope)`` - Create coroutine within specific scope +- ``ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider)`` - Use scope provider object +- ``ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority)`` - Set coroutine priority +- ``ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority)`` - Both scope and priority **Priority levels:** @@ -89,8 +90,8 @@ Adds coroutine to execution queue for scheduling. Suspends current coroutine execution: -* ``SUSPEND()`` - Normal suspension -* ``RUN_SCHEDULER_AFTER_MAIN()`` - Suspend from main context +- ``SUSPEND()`` - Normal suspension +- ``RUN_SCHEDULER_AFTER_MAIN()`` - Suspend from main context Coroutine Control ================= @@ -102,8 +103,8 @@ Coroutine Control Resume coroutine execution: -* ``RESUME(coroutine)`` - Normal resume -* ``RESUME_WITH_ERROR(coroutine, error, transfer_error)`` - Resume with exception +- ``RESUME(coroutine)`` - Normal resume +- ``RESUME_WITH_ERROR(coroutine, error, transfer_error)`` - Resume with exception .. code:: c @@ -112,12 +113,12 @@ Resume coroutine execution: Cancel coroutine execution: -* ``CANCEL()`` - Cancel with default safety -* ``CANCEL_EX()`` - Cancel with explicit safety flag +- ``CANCEL()`` - Cancel with default safety +- ``CANCEL_EX()`` - Cancel with explicit safety flag -******************** +*********** Scope API -******************** +*********** Scope Creation ============== @@ -129,8 +130,8 @@ Scope Creation Create new async scopes for coroutine isolation: -* ``NEW_SCOPE(parent)`` - Create scope with parent reference -* ``NEW_SCOPE_WITH_OBJECT(parent)`` - Create scope backed by zend_object +- ``NEW_SCOPE(parent)`` - Create scope with parent reference +- ``NEW_SCOPE_WITH_OBJECT(parent)`` - Create scope backed by zend_object Scope Flags =========== @@ -138,21 +139,21 @@ Scope Flags .. code:: c #define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) - #define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) + #define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) #define ZEND_ASYNC_SCOPE_IS_DISPOSING(scope) Check scope status: -* ``IS_CLOSED`` - Scope has been closed -* ``IS_CANCELLED`` - Scope was cancelled -* ``IS_DISPOSING`` - Scope is being disposed +- ``IS_CLOSED`` - Scope has been closed +- ``IS_CANCELLED`` - Scope was cancelled +- ``IS_DISPOSING`` - Scope is being disposed -******************** +*********** Event API -******************** +*********** Event Lifecycle -================ +=============== All async events implement these core function pointers: @@ -163,7 +164,7 @@ All async events implement these core function pointers: typedef void (*zend_async_event_dispose_t)(zend_async_event_t *event); Event State Checking -===================== +==================== .. code:: c @@ -172,7 +173,7 @@ Event State Checking Check if event has been closed. Event Reference Counting -========================= +======================== .. code:: c @@ -199,9 +200,9 @@ Event Flags Manipulate event flags. -******************** +************* Reactor API -******************** +************* Reactor Control =============== @@ -219,16 +220,16 @@ Poll Events typedef enum { ASYNC_READABLE = 1, - ASYNC_WRITABLE = 2, + ASYNC_WRITABLE = 2, ASYNC_DISCONNECT = 4, ASYNC_PRIORITIZED = 8 } async_poll_event; Event flags for socket polling operations. -******************** +****************** Signal Constants -******************** +****************** .. code:: c @@ -243,9 +244,9 @@ Event flags for socket polling operations. Cross-platform signal constants for async signal handling. -******************** +******************* Process Execution -******************** +******************* Execution Modes =============== @@ -260,9 +261,9 @@ Execution Modes ZEND_ASYNC_EXEC_MODE_SHELL_EXEC /* shell_exec() - buffer output */ } zend_async_exec_mode; -*********************** +************************ Future and Channel API -*********************** +************************ Futures ======= @@ -284,9 +285,9 @@ Channels Create channels for coroutine communication. -**************************** +***************************** API Context Switch Handlers -**************************** +***************************** Registration ============ @@ -299,7 +300,7 @@ Registration ); bool zend_coroutine_remove_switch_handler( - zend_coroutine_t *coroutine, + zend_coroutine_t *coroutine, uint32_t handler_index ); @@ -329,9 +330,9 @@ Handler Function Type Returns ``true`` to keep handler, ``false`` to remove it after execution. -******************** +******************* Utility Functions -******************** +******************* System Management ================= @@ -368,12 +369,12 @@ Exception Handling Spawn coroutine specifically for throwing an exception within a scope. -*********************** +********************** API Error Management -*********************** +********************** API Exception Classes -====================== +===================== .. code:: c @@ -388,12 +389,12 @@ API Exception Classes Get class entries for async-related classes and exceptions. -******************** +************ Data Types -******************** +************ Platform Types -=============== +============== .. code:: c @@ -411,13 +412,13 @@ Platform Types Cross-platform type definitions for file descriptors, processes, and sockets. -************************ +******************** API Best Practices -************************ +******************** -1. **Always check enablement** with ``ZEND_ASYNC_IS_ENABLED()`` before using APIs -2. **Use appropriate scopes** for coroutine isolation -3. **Handle reference counting** properly with event objects -4. **Register cleanup handlers** using context switch handlers -5. **Check reactor availability** before using event-based operations -6. **Use priority levels** appropriately for coroutine scheduling \ No newline at end of file +#. **Always check enablement** with ``ZEND_ASYNC_IS_ENABLED()`` before using APIs +#. **Use appropriate scopes** for coroutine isolation +#. **Handle reference counting** properly with event objects +#. **Register cleanup handlers** using context switch handlers +#. **Check reactor availability** before using event-based operations +#. **Use priority levels** appropriately for coroutine scheduling diff --git a/docs/source/true_async_api/architecture.rst b/docs/source/true_async_api/architecture.rst index 9077f5a641bda..8a61ab8ae6b2f 100644 --- a/docs/source/true_async_api/architecture.rst +++ b/docs/source/true_async_api/architecture.rst @@ -3,28 +3,25 @@ ############## The TrueAsync API follows a clean separation-of-concerns architecture that splits API definition -from implementation. This design enables modularity, testability, and support for multiple -backend implementations. +from implementation. This design enables modularity, testability, and support for multiple backend +implementations. The **True Async API** is divided into three main functional blocks: -- **Scheduler** - Manages coroutines and the main event loop. - -- **Reactor** - Handles everything related to input/output (I/O), including sockets, timers, and file descriptors. - -- **Thread Pool** - Allows execution of blocking or heavy tasks in a thread pool. +- **Scheduler** Manages coroutines and the main event loop. +- **Reactor** Handles everything related to input/output (I/O), including sockets, timers, and file + descriptors. +- **Thread Pool** Allows execution of blocking or heavy tasks in a thread pool. --- ## Implementation Flexibility -The True Async API provides clearly separated interfaces for each block, allowing them to be implemented independently—even across different PHP extensions. For example: +The True Async API provides clearly separated interfaces for each block, allowing them to be +implemented independently—even across different PHP extensions. For example: -- The Scheduler can be implemented as part of the PHP core. -- The Reactor can be implemented in a separate extension using `libuv`. -- The Thread Pool can be based on native threads or a library like `pthreads`. +- The Scheduler can be implemented as part of the PHP core. +- The Reactor can be implemented in a separate extension using `libuv`. +- The Thread Pool can be based on native threads or a library like `pthreads`. -This approach gives developers maximum flexibility and control over asynchronous execution. \ No newline at end of file +This approach gives developers maximum flexibility and control over asynchronous execution. diff --git a/docs/source/true_async_api/coroutines.rst b/docs/source/true_async_api/coroutines.rst index 3e80e2bccc52e..8e94cc71b9250 100644 --- a/docs/source/true_async_api/coroutines.rst +++ b/docs/source/true_async_api/coroutines.rst @@ -1,25 +1,30 @@ -################ +############ Coroutines -################ +############ -Coroutines are the core building blocks of the TrueAsync API. They represent lightweight execution contexts that can be suspended and resumed, enabling cooperative multitasking without traditional thread overhead. Every coroutine is implemented as an event (``zend_coroutine_t`` extends ``zend_async_event_t``) and participates in the standard event lifecycle. +Coroutines are the core building blocks of the TrueAsync API. They represent lightweight execution +contexts that can be suspended and resumed, enabling cooperative multitasking without traditional +thread overhead. Every coroutine is implemented as an event (``zend_coroutine_t`` extends +``zend_async_event_t``) and participates in the standard event lifecycle. -******************* +****************** Coroutine Basics -******************* +****************** -A coroutine represents a function or code block that can be paused during execution and resumed later. Unlike traditional threads, coroutines are cooperatively scheduled - they yield control voluntarily rather than being preemptively interrupted. +A coroutine represents a function or code block that can be paused during execution and resumed +later. Unlike traditional threads, coroutines are cooperatively scheduled - they yield control +voluntarily rather than being preemptively interrupted. Key characteristics: -* **Lightweight** - Minimal memory overhead compared to threads -* **Cooperative** - Explicit yield points, no race conditions -* **Event-based** - Coroutines are events that can await other events -* **Scoped** - Each coroutine runs within a scope for context isolation +- **Lightweight** - Minimal memory overhead compared to threads +- **Cooperative** - Explicit yield points, no race conditions +- **Event-based** - Coroutines are events that can await other events +- **Scoped** - Each coroutine runs within a scope for context isolation -*********************** +********************* Creating Coroutines -*********************** +********************* The TrueAsync API provides several ways to create coroutines: @@ -27,11 +32,11 @@ The TrueAsync API provides several ways to create coroutines: // Create a new coroutine within default scope zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); - + // Create with specific scope zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(parent_scope); zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH(scope); - + // Create with scope provider object zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider); @@ -40,49 +45,49 @@ Coroutines can be either: **Userland coroutines** Execute PHP code through ``zend_fcall_t`` callback mechanism -**Internal coroutines** +**Internal coroutines** Execute C code through ``zend_coroutine_entry_t`` function pointer -************************* +********************* Coroutine Lifecycle -************************* +********************* Coroutines follow a well-defined lifecycle: -1. **Creation** - Coroutine is spawned but not yet started -2. **Enqueue** - Added to execution queue via ``ZEND_ASYNC_ENQUEUE_COROUTINE()`` -3. **Execution** - Runs until suspension point or completion -4. **Suspension** - Yields control while waiting for events -5. **Resume** - Continues execution when events complete -6. **Completion** - Finishes with result or exception +#. **Creation** - Coroutine is spawned but not yet started +#. **Enqueue** - Added to execution queue via ``ZEND_ASYNC_ENQUEUE_COROUTINE()`` +#. **Execution** - Runs until suspension point or completion +#. **Suspension** - Yields control while waiting for events +#. **Resume** - Continues execution when events complete +#. **Completion** - Finishes with result or exception .. code:: c zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); - + // Set up coroutine execution coro->fcall = &my_function_call; coro->scope = current_scope; - + // Start execution ZEND_ASYNC_ENQUEUE_COROUTINE(coro); - + // Later: resume with result ZEND_ASYNC_RESUME(coro); - + // Or resume with error ZEND_ASYNC_RESUME_WITH_ERROR(coro, exception, transfer_error); -************************* +******************* Coroutine Context -************************* +******************* Each coroutine maintains its own context information: **Scope Context** (``zend_async_scope_t *scope``) Links to parent scope for hierarchical context management -**Async Context** (``zend_async_context_t *context``) +**Async Context** (``zend_async_context_t *context``) User-defined context variables accessible from PHP code **Internal Context** (``HashTable *internal_context``) @@ -91,14 +96,16 @@ Each coroutine maintains its own context information: **Switch Handlers** (``zend_coroutine_switch_handlers_vector_t *switch_handlers``) Functions called when entering/leaving the coroutine -**************************** +************************* Context Switch Handlers -**************************** +************************* -Context switch handlers are one of the most powerful features of the coroutine system. They allow extensions to hook into coroutine context switches and perform necessary cleanup, initialization, or state management. +Context switch handlers are one of the most powerful features of the coroutine system. They allow +extensions to hook into coroutine context switches and perform necessary cleanup, initialization, or +state management. Handler Function Signature -=========================== +========================== .. code:: c @@ -111,14 +118,14 @@ Handler Function Signature The handler receives three parameters: -* **coroutine** - The coroutine being switched to/from -* **is_enter** - ``true`` when entering coroutine, ``false`` when leaving -* **is_finishing** - ``true`` when coroutine is finishing execution +- **coroutine** - The coroutine being switched to/from +- **is_enter** - ``true`` when entering coroutine, ``false`` when leaving +- **is_finishing** - ``true`` when coroutine is finishing execution The return value determines handler lifetime: -* **true** - Keep handler for future context switches -* **false** - Remove handler immediately after execution +- **true** - Keep handler for future context switches +- **false** - Remove handler immediately after execution This allows handlers to perform one-time initialization and then remove themselves automatically. @@ -133,7 +140,7 @@ There are two types of switch handlers: // Add handler to specific coroutine uint32_t handler_id = ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, my_handler); - + // Remove specific handler zend_coroutine_remove_switch_handler(coroutine, handler_id); @@ -144,12 +151,15 @@ There are two types of switch handlers: // Register global handler for all main coroutines ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(my_main_handler); -Global handlers are automatically copied to each main coroutine when it starts and can remove themselves by returning ``false``. +Global handlers are automatically copied to each main coroutine when it starts and can remove +themselves by returning ``false``. Practical Example: Output Buffering -==================================== +=================================== -The ``main/output.c`` module provides an excellent example of context switch handlers in action. The output system needs to isolate output buffers between the main execution context and coroutines to prevent buffer conflicts. +The ``main/output.c`` module provides an excellent example of context switch handlers in action. The +output system needs to isolate output buffers between the main execution context and coroutines to +prevent buffer conflicts. .. code:: c @@ -187,26 +197,27 @@ The ``main/output.c`` module provides an excellent example of context switch han zend_coroutine_event_callback_t *cleanup_callback = zend_async_coroutine_callback_new(coroutine, php_output_coroutine_cleanup_callback, 0); coroutine->event.add_callback(&coroutine->event, &cleanup_callback->base); - + return false; /* Remove handler - initialization is complete */ } This handler: -1. **Detects main coroutine entry** - Only acts when ``is_enter`` is true -2. **Creates isolated context** - Copies output handlers to coroutine-specific storage -3. **Stores in internal_context** - Uses numeric key for fast access -4. **Cleans global state** - Prevents conflicts between main and coroutine contexts -5. **Registers cleanup** - Ensures proper resource cleanup on coroutine completion -6. **Removes itself** - Returns ``false`` since initialization is one-time only +#. **Detects main coroutine entry** - Only acts when ``is_enter`` is true +#. **Creates isolated context** - Copies output handlers to coroutine-specific storage +#. **Stores in internal_context** - Uses numeric key for fast access +#. **Cleans global state** - Prevents conflicts between main and coroutine contexts +#. **Registers cleanup** - Ensures proper resource cleanup on coroutine completion +#. **Removes itself** - Returns ``false`` since initialization is one-time only When to Use Context Switch Handlers -==================================== +=================================== Context switch handlers are ideal for: **Resource Isolation** - Separating global state between main execution and coroutines (like output buffers, error handlers, etc.) + Separating global state between main execution and coroutines (like output buffers, error + handlers, etc.) **Context Migration** Moving data from one execution context to another @@ -220,27 +231,29 @@ Context switch handlers are ideal for: **Performance Optimization** Pre-computing or caching data when entering frequently-used coroutines -************************** +********************** Internal Context API -************************** +********************** -The internal context system provides a type-safe, efficient way for PHP extensions to store private data associated with coroutines. Unlike userland context variables, internal context uses numeric keys for faster access and is completely isolated from PHP code. +The internal context system provides a type-safe, efficient way for PHP extensions to store private +data associated with coroutines. Unlike userland context variables, internal context uses numeric +keys for faster access and is completely isolated from PHP code. Why Internal Context? -====================== +===================== Before internal context, extensions had limited options for storing coroutine-specific data: -* **Global variables** - Not coroutine-safe, cause conflicts -* **Object properties** - Not available for internal coroutines -* **Manual management** - Complex, error-prone cleanup +- **Global variables** - Not coroutine-safe, cause conflicts +- **Object properties** - Not available for internal coroutines +- **Manual management** - Complex, error-prone cleanup Internal context solves these problems by providing: -* **Automatic cleanup** - Data is freed when coroutine completes -* **Type safety** - Values stored as ``zval`` with proper reference counting -* **Uniqueness** - Each extension gets private numeric keys -* **Performance** - Hash table lookup by integer key +- **Automatic cleanup** - Data is freed when coroutine completes +- **Type safety** - Values stored as ``zval`` with proper reference counting +- **Uniqueness** - Each extension gets private numeric keys +- **Performance** - Hash table lookup by integer key Key Allocation ============== @@ -260,10 +273,10 @@ Extensions allocate unique keys during module initialization: The ``ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC`` macro: -* Takes a static string identifier (for debugging) -* Returns a unique numeric key -* Validates that the same string address always gets the same key -* Is thread-safe in ZTS builds +- Takes a static string identifier (for debugging) +- Returns a unique numeric key +- Validates that the same string address always gets the same key +- Is thread-safe in ZTS builds Storing and Retrieving Data =========================== @@ -276,26 +289,26 @@ Storing and Retrieving Data zval my_data; ZVAL_LONG(&my_data, 42); ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_extension_context_key, &my_data); - + // Later: retrieve data zval *stored_data = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_extension_context_key); if (stored_data && Z_TYPE_P(stored_data) == IS_LONG) { long value = Z_LVAL_P(stored_data); // Use value... } - + // Optional: remove data explicitly ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coroutine, my_extension_context_key); } The API automatically handles: -* **Reference counting** - ``zval`` reference counts are managed properly -* **Memory management** - All context data is freed when coroutine ends -* **Thread safety** - Operations are safe in ZTS builds +- **Reference counting** - ``zval`` reference counts are managed properly +- **Memory management** - All context data is freed when coroutine ends +- **Thread safety** - Operations are safe in ZTS builds Advanced Usage Patterns -======================== +======================= **Caching expensive computations:** @@ -318,7 +331,7 @@ Advanced Usage Patterns zval state; ZVAL_LONG(&state, OPERATION_IN_PROGRESS); ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, state_key, &state); - + // After resuming zval *state_val = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, state_key); if (state_val && Z_LVAL_P(state_val) == OPERATION_IN_PROGRESS) { @@ -337,15 +350,15 @@ Advanced Usage Patterns Best Practices ============== -1. **Allocate keys in MINIT** - Ensures keys are available when needed -2. **Use descriptive key names** - Helps with debugging and maintenance -3. **Check return values** - ``FIND`` returns ``NULL`` if key doesn't exist -4. **Validate data types** - Always check ``Z_TYPE_P`` before accessing data -5. **Clean up appropriately** - Use ``UNSET`` for early cleanup if needed +#. **Allocate keys in MINIT** - Ensures keys are available when needed +#. **Use descriptive key names** - Helps with debugging and maintenance +#. **Check return values** - ``FIND`` returns ``NULL`` if key doesn't exist +#. **Validate data types** - Always check ``Z_TYPE_P`` before accessing data +#. **Clean up appropriately** - Use ``UNSET`` for early cleanup if needed -**************** +*************** Coroutine API -**************** +*************** Core Functions ============== @@ -356,13 +369,13 @@ Core Functions zend_coroutine_t *ZEND_ASYNC_SPAWN(); zend_coroutine_t *ZEND_ASYNC_NEW_COROUTINE(scope); void ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine); - + // Execution control void ZEND_ASYNC_SUSPEND(); void ZEND_ASYNC_RESUME(coroutine); void ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error); void ZEND_ASYNC_CANCEL(coroutine, error, transfer_error); - + // Context switch handlers uint32_t ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler); bool zend_coroutine_remove_switch_handler(coroutine, handler_index); @@ -375,11 +388,11 @@ State Checking Macros // Coroutine state ZEND_COROUTINE_IS_STARTED(coroutine) - ZEND_COROUTINE_IS_FINISHED(coroutine) + ZEND_COROUTINE_IS_FINISHED(coroutine) ZEND_COROUTINE_IS_CANCELLED(coroutine) ZEND_COROUTINE_SUSPENDED(coroutine) ZEND_COROUTINE_IS_MAIN(coroutine) - + // Current context ZEND_ASYNC_CURRENT_COROUTINE ZEND_ASYNC_CURRENT_SCOPE @@ -396,25 +409,25 @@ Coroutines follow standard event reference counting: The coroutine automatically cleans up: -* Internal context data -* Switch handlers -* Event callbacks -* Scope references +- Internal context data +- Switch handlers +- Event callbacks +- Scope references -******************** +**************** Best Practices -******************** +**************** -1. **Always check if async is enabled** before using coroutine APIs -2. **Use appropriate scopes** to maintain context isolation -3. **Handle exceptions properly** in coroutine callbacks -4. **Register cleanup handlers** for long-running operations -5. **Use internal context** instead of global variables for coroutine data -6. **Remove one-time handlers** by returning ``false`` from switch handlers -7. **Validate coroutine state** before performing operations +#. **Always check if async is enabled** before using coroutine APIs +#. **Use appropriate scopes** to maintain context isolation +#. **Handle exceptions properly** in coroutine callbacks +#. **Register cleanup handlers** for long-running operations +#. **Use internal context** instead of global variables for coroutine data +#. **Remove one-time handlers** by returning ``false`` from switch handlers +#. **Validate coroutine state** before performing operations Example: Complete Coroutine Usage -================================== +================================= .. code:: c @@ -430,12 +443,12 @@ Example: Complete Coroutine Usage static bool my_main_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) { if (!is_enter) return true; - + // Initialize coroutine-specific data zval init_data; ZVAL_STRING(&init_data, "initialized"); ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &init_data); - + return false; // Remove handler after initialization } @@ -444,12 +457,13 @@ Example: Complete Coroutine Usage if (!ZEND_ASYNC_IS_ENABLED()) { return; // Fallback to synchronous operation } - + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); coro->internal_entry = my_coroutine_function; - + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); ZEND_ASYNC_SUSPEND(); // Wait for completion } -This comprehensive coroutine system enables powerful asynchronous programming patterns while maintaining clean separation of concerns and proper resource management. \ No newline at end of file +This comprehensive coroutine system enables powerful asynchronous programming patterns while +maintaining clean separation of concerns and proper resource management. diff --git a/docs/source/true_async_api/events.rst b/docs/source/true_async_api/events.rst index 3ccce430bc098..1af86924f76c2 100644 --- a/docs/source/true_async_api/events.rst +++ b/docs/source/true_async_api/events.rst @@ -1,89 +1,88 @@ -################ +######## Events -################ +######## -The TrueAsync API revolves around the concept of **events**. An event represents any asynchronous action that can produce a result or error in the future. All specific event types share a common base structure ``zend_async_event_t`` which provides memory management, state flags and a standard lifecycle. +The TrueAsync API revolves around the concept of **events**. An event represents any asynchronous +action that can produce a result or error in the future. All specific event types share a common +base structure ``zend_async_event_t`` which provides memory management, state flags and a standard +lifecycle. -********************** -Creating an Event -********************** +******************* + Creating an Event +******************* -To create an event you typically call one of the ``ZEND_ASYNC_NEW_*`` macros which delegate to the underlying implementation. Each macro returns a structure that contains ``zend_async_event_t`` as its first member. +To create an event you typically call one of the ``ZEND_ASYNC_NEW_*`` macros which delegate to the +underlying implementation. Each macro returns a structure that contains ``zend_async_event_t`` as +its first member. .. code:: c zend_async_timer_event_t *timer = ZEND_ASYNC_NEW_TIMER_EVENT(1000, false); timer->base.start(&timer->base); -The ``base`` field exposes the common functions ``start``, ``stop`` and ``dispose`` as well as reference counting helpers. +The ``base`` field exposes the common functions ``start``, ``stop`` and ``dispose`` as well as +reference counting helpers. -***************** -Event Flags -***************** +************* + Event Flags +************* -Each event has a ``flags`` field that controls its state and behaviour. The most -important flags are: +Each event has a ``flags`` field that controls its state and behaviour. The most important flags +are: ``ZEND_ASYNC_EVENT_F_CLOSED`` - The event has been closed and may no longer be started or stopped. + The event has been closed and may no longer be started or stopped. ``ZEND_ASYNC_EVENT_F_RESULT_USED`` - Indicates that the result will be consumed by an awaiting context. When this - flag is set the awaiting code keeps the result alive until it is processed - in the exception handler. + Indicates that the result will be consumed by an awaiting context. When this flag is set the + awaiting code keeps the result alive until it is processed in the exception handler. ``ZEND_ASYNC_EVENT_F_EXC_CAUGHT`` - Marks that an error produced by the event was caught in a callback and will - not be rethrown automatically. + Marks that an error produced by the event was caught in a callback and will not be rethrown + automatically. ``ZEND_ASYNC_EVENT_F_ZVAL_RESULT`` - Signals that the callback result is a ``zval`` pointer. The TrueAsync core - will properly increment the reference count before inserting the value into - userland arrays. + Signals that the callback result is a ``zval`` pointer. The TrueAsync core will properly + increment the reference count before inserting the value into userland arrays. ``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` - Specifies that the structure also acts as a Zend object implementing - ``Awaitable``. + Specifies that the structure also acts as a Zend object implementing ``Awaitable``. ``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` - The dispose handler must not free the memory of the event itself. This is - used when the event is embedded into another structure. + The dispose handler must not free the memory of the event itself. This is used when the event is + embedded into another structure. ``ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED`` - Set by the callback once it has fully processed an exception. If this flag - is not set the exception will be rethrown after callbacks finish. + Set by the callback once it has fully processed an exception. If this flag is not set the + exception will be rethrown after callbacks finish. ``ZEND_ASYNC_EVENT_F_REFERENCE`` - Indicates that this structure is only an event reference which stores a - pointer to the real event. + Indicates that this structure is only an event reference which stores a pointer to the real + event. ``ZEND_ASYNC_EVENT_F_OBJ_REF`` - Marks that the event keeps a pointer to the Zend object in its - ``extra_offset`` region so reference counting works through the object. + Marks that the event keeps a pointer to the Zend object in its ``extra_offset`` region so + reference counting works through the object. Convenience macros such as ``ZEND_ASYNC_EVENT_SET_CLOSED`` and -``ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED`` are provided to manipulate these -flags. +``ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED`` are provided to manipulate these flags. -****************** -Event Callbacks -****************** +***************** + Event Callbacks +***************** + +The core maintains a dynamic vector of callbacks for each event. Implementations provide the +``add_callback`` and ``del_callback`` methods which internally use ``zend_async_callbacks_push`` and +``zend_async_callbacks_remove``. The ``zend_async_callbacks_notify`` helper iterates over all +registered callbacks and passes the result or exception. If a ``notify_handler`` is set on the event +it is invoked first and can adjust the result or exception as needed. The handler is expected to +call ``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` to forward the values to all listeners. The +``php-async`` extension provides convenience macros ``ZEND_ASYNC_CALLBACKS_NOTIFY`` for regular use, +``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` when called from inside a ``notify_handler``, and +``ZEND_ASYNC_CALLBACKS_NOTIFY_AND_CLOSE`` to close the event before notifying listeners. -The core maintains a dynamic vector of callbacks for each event. Implementations -provide the ``add_callback`` and ``del_callback`` methods which internally use -``zend_async_callbacks_push`` and ``zend_async_callbacks_remove``. The -``zend_async_callbacks_notify`` helper iterates over all registered callbacks -and passes the result or exception. If a ``notify_handler`` is set on the event -it is invoked first and can adjust the result or exception as needed. The -handler is expected to call ``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` to -forward the values to all listeners. The ``php-async`` extension provides -convenience macros ``ZEND_ASYNC_CALLBACKS_NOTIFY`` for regular use, -``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` when called from inside a -``notify_handler``, and ``ZEND_ASYNC_CALLBACKS_NOTIFY_AND_CLOSE`` to close the -event before notifying listeners. - -The following example shows a libuv poll event that dispatches its callbacks -once the underlying handle becomes readable: +The following example shows a libuv poll event that dispatches its callbacks once the underlying +handle becomes readable: .. code:: c @@ -108,11 +107,13 @@ once the underlying handle becomes readable: } } -************************* -Event Coroutines -************************* +****************** + Event Coroutines +****************** -Coroutines themselves are implemented as events using the ``zend_coroutine_t`` structure. When a coroutine yields, its waker waits on multiple events and resumes the coroutine once any of them triggers. +Coroutines themselves are implemented as events using the ``zend_coroutine_t`` structure. When a +coroutine yields, its waker waits on multiple events and resumes the coroutine once any of them +triggers. .. code:: c @@ -121,20 +122,20 @@ Coroutines themselves are implemented as events using the ``zend_coroutine_t`` s zend_async_resume_when(co, &timer->base, false, zend_async_waker_callback_resolve, NULL); zend_async_enqueue_coroutine(co); -When the coroutine finishes execution the event triggers again to deliver the -result or exception. The coroutine implementation marks the callback result as a -``zval`` value using ``ZEND_ASYNC_EVENT_SET_ZVAL_RESULT``. Callback handlers may -also set ``ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED`` to indicate that the thrown -exception has been processed and should not be rethrown by the runtime. +When the coroutine finishes execution the event triggers again to deliver the result or exception. +The coroutine implementation marks the callback result as a ``zval`` value using +``ZEND_ASYNC_EVENT_SET_ZVAL_RESULT``. Callback handlers may also set +``ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED`` to indicate that the thrown exception has been processed +and should not be rethrown by the runtime. -**************************** -Extending Events -**************************** +****************** + Extending Events +****************** -Custom event types embed ``zend_async_event_t`` at the beginning of their -structure and may allocate additional memory beyond the end of the struct. -The ``extra_size`` argument in ``ZEND_ASYNC_NEW_*_EX`` controls how much extra -space is reserved, and ``extra_offset`` records where that region begins. +Custom event types embed ``zend_async_event_t`` at the beginning of their structure and may allocate +additional memory beyond the end of the struct. The ``extra_size`` argument in +``ZEND_ASYNC_NEW_*_EX`` controls how much extra space is reserved, and ``extra_offset`` records +where that region begins. .. code:: c @@ -142,8 +143,8 @@ space is reserved, and ``extra_offset`` records where that region begins. zend_async_poll_event_t *poll = ZEND_ASYNC_NEW_POLL_EVENT_EX(fd, false, sizeof(my_data_t)); my_data_t *data = (my_data_t *)((char*)poll + poll->base.extra_offset); -The libuv backend defines event wrappers that embed libuv handles. A timer -event, for example, extends ``zend_async_timer_event_t`` as follows: +The libuv backend defines event wrappers that embed libuv handles. A timer event, for example, +extends ``zend_async_timer_event_t`` as follows: .. code:: c @@ -159,10 +160,9 @@ event, for example, extends ``zend_async_timer_event_t`` as follows: event->event.base.stop = libuv_timer_stop; event->event.base.dispose = libuv_timer_dispose; -Every extended event defines its own ``start``, ``stop`` and ``dispose`` -functions. The dispose handler must release all resources associated with -the event and is called when the reference count reaches ``1``. It is -common to stop the event first and then close the underlying libuv handle so +Every extended event defines its own ``start``, ``stop`` and ``dispose`` functions. The dispose +handler must release all resources associated with the event and is called when the reference count +reaches ``1``. It is common to stop the event first and then close the underlying libuv handle so that memory gets freed in the ``uv_close`` callback. .. code:: c @@ -185,19 +185,17 @@ that memory gets freed in the ``uv_close`` callback. uv_close((uv_handle_t *)&timer->uv_handle, libuv_close_handle_cb); } -If ``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` is set the dispose handler must not -free the event memory itself because the structure is embedded in another -object (e.g. ``async_coroutine_t``). The libuv close callback will only free -the libuv handle in this case. +If ``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` is set the dispose handler must not free the event memory +itself because the structure is embedded in another object (e.g. ``async_coroutine_t``). The libuv +close callback will only free the libuv handle in this case. -*********************** -Custom Event Callbacks -*********************** +************************ + Custom Event Callbacks +************************ -Callbacks can also be extended to store additional state. The await logic in -``php-async`` defines a callback that inherits from -``zend_coroutine_event_callback_t`` and keeps a reference to the awaiting -context: +Callbacks can also be extended to store additional state. The await logic in ``php-async`` defines a +callback that inherits from ``zend_coroutine_event_callback_t`` and keeps a reference to the +awaiting context: .. code:: c @@ -213,35 +211,37 @@ context: cb->await_context = ctx; zend_async_resume_when(co, awaitable, false, NULL, &cb->callback); -*********************** -Events as Zend Objects -*********************** +************************ + Events as Zend Objects +************************ + +If ``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` is set, the event also acts as a Zend object implementing +``Awaitable``. The ``zend_object_offset`` field stores the location of the ``zend_object`` within +the structure. Reference counting macros automatically use either the internal counter or +``GC_REFCOUNT`` depending on this flag. -If ``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` is set, the event also acts as a Zend object implementing ``Awaitable``. The ``zend_object_offset`` field stores the location of the ``zend_object`` within the structure. Reference counting macros automatically use either the internal counter or ``GC_REFCOUNT`` depending on this flag. +This allows events to be exposed to userland seamlessly while keeping the internal lifecycle +consistent. -This allows events to be exposed to userland seamlessly while keeping the internal lifecycle consistent. +For events that are destroyed asynchronously (e.g. libuv timers) the actual event structure cannot +be a Zend object. Instead a lightweight reference structure is used. ``ZEND_ASYNC_EVENT_REF_PROLOG`` +reserves the required fields in the Zend object and ``ZEND_ASYNC_EVENT_REF_SET`` stores the pointer +to the real event together with the ``zend_object`` offset. The event must then be flagged with +``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` so that reference counting delegates to the object. -For events that are destroyed asynchronously (e.g. libuv timers) the actual -event structure cannot be a Zend object. Instead a lightweight reference -structure is used. ``ZEND_ASYNC_EVENT_REF_PROLOG`` reserves the required fields -in the Zend object and ``ZEND_ASYNC_EVENT_REF_SET`` stores the pointer to the -real event together with the ``zend_object`` offset. The event must then be -flagged with ``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` so that reference counting -delegates to the object. +When accessing the event from userland objects use ``ZEND_ASYNC_OBJECT_TO_EVENT`` and +``ZEND_ASYNC_EVENT_TO_OBJECT`` which handle both direct and reference-based layouts transparently. -When accessing the event from userland objects use -``ZEND_ASYNC_OBJECT_TO_EVENT`` and ``ZEND_ASYNC_EVENT_TO_OBJECT`` which handle -both direct and reference-based layouts transparently. +The ``php-async`` extension provides ``Async\\Timeout`` objects that embed a timer event. Recent +updates introduce a helper API for linking an event with a Zend object. A helper macro +``ZEND_ASYNC_EVENT_REF_PROLOG`` reserves fields at the beginning of the object to hold an event +reference. ``ZEND_ASYNC_EVENT_REF_SET`` stores the pointer to the newly created event and the offset +of the ``zend_object`` inside the structure. ``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` then marks the +event so reference counting will use the Zend object rather than the internal counter. -The ``php-async`` extension provides ``Async\\Timeout`` objects that embed a -timer event. Recent updates introduce a helper API for linking an event with a -Zend object. A helper macro ``ZEND_ASYNC_EVENT_REF_PROLOG`` reserves fields at -the beginning of the object to hold an event reference. ``ZEND_ASYNC_EVENT_REF_SET`` -stores the pointer to the newly created event and the offset of the ``zend_object`` -inside the structure. ``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` then marks the event -so reference counting will use the Zend object rather than the internal counter. +The object factory now uses these helpers when creating the timer: -The object factory now uses these helpers when creating the timer:: +.. code:: static zend_object *async_timeout_create(const zend_ulong ms, const bool is_periodic) { @@ -295,9 +295,7 @@ The object factory now uses these helpers when creating the timer:: .. note:: - Events must not be exposed as Zend objects if their memory is released - asynchronously. Zend assumes that object destruction happens entirely - during the ``zend_object_release`` call and cannot wait for callbacks such as - ``uv_close`` to free the underlying event. The ``Async\\Timeout`` class will - be redesigned to avoid this pattern. - + Events must not be exposed as Zend objects if their memory is released asynchronously. Zend + assumes that object destruction happens entirely during the ``zend_object_release`` call and + cannot wait for callbacks such as ``uv_close`` to free the underlying event. The + ``Async\\Timeout`` class will be redesigned to avoid this pattern. diff --git a/docs/source/true_async_api/examples.rst b/docs/source/true_async_api/examples.rst index cd366e447adf2..aae751a5725bd 100644 --- a/docs/source/true_async_api/examples.rst +++ b/docs/source/true_async_api/examples.rst @@ -4,19 +4,19 @@ This section provides practical C code examples for using the TrueAsync API in PHP extensions. -****************** +**************** Basic Examples -****************** +**************** Checking API Availability -========================== +========================= Always check if async is enabled before using the API: .. code:: c #include "Zend/zend_async_API.h" - + void my_async_function(void) { if (!ZEND_ASYNC_IS_ENABLED()) { @@ -24,7 +24,7 @@ Always check if async is enabled before using the API: my_sync_function(); return; } - + // Use async API my_actual_async_function(); } @@ -40,20 +40,20 @@ Creating and spawning a basic coroutine: { php_printf("Hello from coroutine!\n"); } - + void spawn_example(void) { if (!ZEND_ASYNC_IS_ENABLED()) return; - + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); if (!coro) { php_error(E_WARNING, "Failed to create coroutine"); return; } - + coro->internal_entry = my_coroutine_entry; coro->internal_arg = NULL; - + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); } @@ -67,11 +67,11 @@ Creating coroutines with custom scopes: void scope_example(void) { if (!ZEND_ASYNC_IS_ENABLED()) return; - + // Create new scope zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(NULL); if (!scope) return; - + // Spawn coroutine within scope zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH(scope); if (coro) { @@ -80,9 +80,9 @@ Creating coroutines with custom scopes: } } -******************************** +********************************* Context Switch Handler Examples -******************************** +********************************* Per-Coroutine Handler ===================== @@ -97,26 +97,26 @@ Adding context switch handlers to specific coroutines: php_printf("Coroutine finishing\n"); return false; // Remove handler } - + if (is_enter) { php_printf("Entering coroutine\n"); } else { php_printf("Leaving coroutine\n"); } - + return true; // Keep handler } - + void add_handler_example(void) { zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); if (!coro) return; - + uint32_t handler_id = zend_coroutine_add_switch_handler(coro, my_switch_handler); - + coro->internal_entry = my_coroutine_entry; ZEND_ASYNC_ENQUEUE_COROUTINE(coro); - + // Later: remove handler if needed // zend_coroutine_remove_switch_handler(coro, handler_id); } @@ -129,23 +129,23 @@ Handler that applies to all main coroutines: .. code:: c static uint32_t my_context_key = 0; - + static bool main_coroutine_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) { if (!is_enter) return true; - + // Initialize extension data for this coroutine my_extension_data_t *data = ecalloc(1, sizeof(my_extension_data_t)); data->initialized = true; - + // Store in coroutine internal context zval data_zval; ZVAL_PTR(&data_zval, data); ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &data_zval); - + return false; // Remove handler after initialization } - + PHP_MINIT_FUNCTION(my_extension) { my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); @@ -164,7 +164,7 @@ Working with async events (using timer as example): .. code:: c - static void timer_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + static void timer_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, void *result, zend_object *exception) { if (exception) { @@ -172,19 +172,19 @@ Working with async events (using timer as example): } else { php_printf("Timer fired!\n"); } - + // Clean up ZEND_ASYNC_EVENT_DEL_REF(event); } - + void timer_example(void) { if (!ZEND_ASYNC_REACTOR_IS_ENABLED()) return; - + // This would need actual reactor implementation // zend_async_timer_event_t *timer = zend_async_new_timer_event_fn(1000, 0, false, 0); // if (!timer) return; - + // zend_async_event_callback_t *callback = ...; // timer->base.add_callback(&timer->base, callback); // timer->base.start(&timer->base); @@ -202,36 +202,36 @@ Using internal context to store per-coroutine data: .. code:: c static uint32_t cache_context_key = 0; - + typedef struct { HashTable *cached_results; time_t last_access; } cache_data_t; - + PHP_MINIT_FUNCTION(cache_extension) { cache_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("cache_data"); return SUCCESS; } - + cache_data_t* get_cache_data(zend_coroutine_t *coroutine) { zval *cached = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, cache_context_key); if (cached && Z_TYPE_P(cached) == IS_PTR) { return (cache_data_t *)Z_PTR_P(cached); } - + // Create new cache data cache_data_t *data = ecalloc(1, sizeof(cache_data_t)); ALLOC_HASHTABLE(data->cached_results); zend_hash_init(data->cached_results, 16, NULL, ZVAL_PTR_DTOR, 0); data->last_access = time(NULL); - + // Store in context zval data_zval; ZVAL_PTR(&data_zval, data); ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, cache_context_key, &data_zval); - + return data; } @@ -249,18 +249,18 @@ Creating and handling exceptions in async context: void async_operation_with_error(void) { if (!ZEND_ASYNC_IS_ENABLED()) return; - + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); if (!coro) { zend_throw_error(NULL, "Failed to create coroutine"); return; } - + // Set up coroutine that might throw coro->internal_entry = error_prone_operation; ZEND_ASYNC_ENQUEUE_COROUTINE(coro); } - + static void error_prone_operation(void *arg) { // Simulate error condition @@ -268,7 +268,7 @@ Creating and handling exceptions in async context: zend_object *exception = zend_throw_exception( zend_ce_exception, "Async operation failed", 0 ); - + // Resume current coroutine with error zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; if (current) { @@ -276,16 +276,16 @@ Creating and handling exceptions in async context: } return; } - + php_printf("Operation completed successfully\n"); } -*************************** +**************************** Memory Management Examples -*************************** +**************************** Reference Counting Examples -============================= +=========================== Proper event reference management: @@ -295,16 +295,16 @@ Proper event reference management: { // Assuming we have an event from somewhere zend_async_event_t *event = get_some_event(); - + // Add reference when storing event ZEND_ASYNC_EVENT_ADD_REF(event); - + // Store event somewhere store_event_reference(event); - + // Later: remove reference when done ZEND_ASYNC_EVENT_DEL_REF(event); - + // Or use RELEASE which decrements and disposes if needed // ZEND_ASYNC_EVENT_RELEASE(event); } @@ -323,14 +323,14 @@ Creating and using futures for async results: void future_example(void) { if (!ZEND_ASYNC_IS_ENABLED()) return; - + // Create thread-safe future zend_future_t *future = ZEND_ASYNC_NEW_FUTURE(true); if (!future) return; - + // Future can be resolved from any thread // zend_future_resolve(future, &result_zval); - + // Or with exception // zend_future_reject(future, exception); } @@ -345,19 +345,19 @@ Creating channels for coroutine communication: void channel_example(void) { if (!ZEND_ASYNC_IS_ENABLED()) return; - + // Create buffered channel zend_async_channel_t *channel = ZEND_ASYNC_NEW_CHANNEL(10, true, false); if (!channel) return; - + // Channels support send/receive operations // channel->send(channel, &value); // channel->receive(channel, &result); } -************************ +*********************** Extension Integration -************************ +*********************** Complete Extension Example ========================== @@ -367,12 +367,12 @@ Full example of async-aware extension: .. code:: c static uint32_t my_ext_context_key = 0; - + typedef struct { bool async_enabled; HashTable *pending_operations; } my_extension_context_t; - + static bool my_ext_main_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) { if (is_finishing) { @@ -388,34 +388,34 @@ Full example of async-aware extension: } return false; } - + if (!is_enter) return true; - + // Initialize context for new main coroutine my_extension_context_t *ctx = ecalloc(1, sizeof(my_extension_context_t)); ctx->async_enabled = true; - + ALLOC_HASHTABLE(ctx->pending_operations); zend_hash_init(ctx->pending_operations, 8, NULL, ZVAL_PTR_DTOR, 0); - + zval ctx_zval; ZVAL_PTR(&ctx_zval, ctx); ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_ext_context_key, &ctx_zval); - + return false; // Remove handler after init } - + PHP_MINIT_FUNCTION(my_extension) { my_ext_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); - + if (ZEND_ASYNC_IS_ENABLED()) { zend_async_add_main_coroutine_start_handler(my_ext_main_handler); } - + return SUCCESS; } - + // Extension function that works async-aware PHP_FUNCTION(my_async_operation) { @@ -424,7 +424,7 @@ Full example of async-aware extension: my_sync_operation(INTERNAL_FUNCTION_PARAM_PASSTHRU); return; } - + zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; if (current) { my_extension_context_t *ctx = get_my_context(current); @@ -434,19 +434,19 @@ Full example of async-aware extension: return; } } - + // Default to sync my_sync_operation(INTERNAL_FUNCTION_PARAM_PASSTHRU); } -*************************** +************************* Examples Best Practices -*************************** +************************* -1. **Always check enablement** before using async APIs -2. **Use internal context** for per-coroutine data storage -3. **Handle reference counting** properly for events -4. **Register cleanup handlers** for resource management -5. **Provide sync fallbacks** when async is not available -6. **Use appropriate scopes** for coroutine isolation -7. **Handle exceptions** properly in async context \ No newline at end of file +#. **Always check enablement** before using async APIs +#. **Use internal context** for per-coroutine data storage +#. **Handle reference counting** properly for events +#. **Register cleanup handlers** for resource management +#. **Provide sync fallbacks** when async is not available +#. **Use appropriate scopes** for coroutine isolation +#. **Handle exceptions** properly in async context diff --git a/docs/source/true_async_api/implementation-guide.rst b/docs/source/true_async_api/implementation-guide.rst index cd18321fef43c..4a049e1cea3b3 100644 --- a/docs/source/true_async_api/implementation-guide.rst +++ b/docs/source/true_async_api/implementation-guide.rst @@ -2,24 +2,24 @@ Implementation Guide ###################### -This guide explains how to implement TrueAsync API backends and integrate them with -the PHP core. It covers the registration process, implementation patterns, and best practices. +This guide explains how to implement TrueAsync API backends and integrate them with the PHP core. It +covers the registration process, implementation patterns, and best practices. -********************** +********** Overview -********************** +********** Implementing a TrueAsync API backend involves: -1. **Creating function implementations** that match the API signatures -2. **Registering implementations** with the core during module initialization -3. **Following memory management** and error handling conventions -4. **Implementing event lifecycle** methods (start, stop, dispose) -5. **Handling callbacks** and result delivery +#. **Creating function implementations** that match the API signatures +#. **Registering implementations** with the core during module initialization +#. **Following memory management** and error handling conventions +#. **Implementing event lifecycle** methods (start, stop, dispose) +#. **Handling callbacks** and result delivery -*********************** +********************** Registration Process -*********************** +********************** The registration process connects your implementations to the core API. @@ -36,7 +36,7 @@ Register your implementations during ``PHP_MINIT_FUNCTION``: "MyAsyncBackend", // Module name false, // Allow override my_reactor_startup, // Startup function - my_reactor_shutdown, // Shutdown function + my_reactor_shutdown, // Shutdown function my_reactor_execute, // Execute function my_reactor_loop_alive, // Loop alive check my_new_socket_event, // Socket event factory @@ -52,12 +52,12 @@ Register your implementations during ``PHP_MINIT_FUNCTION``: my_new_exec_event, // Exec event factory my_exec // Exec function ); - + if (!success) { php_error_docref(NULL, E_CORE_ERROR, "Failed to register async backend"); return FAILURE; } - + return SUCCESS; } @@ -71,27 +71,27 @@ All implementations must match the exact function pointer signatures defined in // DNS function signatures typedef zend_async_dns_addrinfo_t* (*zend_async_getaddrinfo_t)( - const char *node, const char *service, + const char *node, const char *service, const struct addrinfo *hints, size_t extra_size ); - + typedef void (*zend_async_freeaddrinfo_t)(struct addrinfo *ai); - + // Timer function signature typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)( const zend_ulong timeout, const zend_ulong nanoseconds, const bool is_periodic, size_t extra_size ); -************************ +************************* Implementation Patterns -************************ +************************* Event Structure Pattern ======================= -All async events follow a common pattern with base event structure plus -implementation-specific data: +All async events follow a common pattern with base event structure plus implementation-specific +data: .. code:: c @@ -116,7 +116,7 @@ Event factory functions create and initialize event structures: // Allocate memory (include extra_size if specified) size_t total_size = sizeof(my_dns_addrinfo_t) + extra_size; my_dns_addrinfo_t *dns = pecalloc(1, total_size, 0); - + // Initialize base event fields dns->event.base.extra_offset = sizeof(my_dns_addrinfo_t); dns->event.base.ref_count = 1; @@ -125,12 +125,12 @@ Event factory functions create and initialize event structures: dns->event.base.start = my_dns_start; dns->event.base.stop = my_dns_stop; dns->event.base.dispose = my_dns_dispose; - + // Store request parameters dns->event.node = node; dns->event.service = service; dns->event.hints = hints; - + // Initialize your backend-specific handle int result = my_event_loop_init_dns(&dns->handle, node, service, hints); if (result != 0) { @@ -138,10 +138,10 @@ Event factory functions create and initialize event structures: pefree(dns, 0); return NULL; } - + // Link handle to event data dns->handle.data = dns; - + return &dns->event; } @@ -155,62 +155,62 @@ Implement the three lifecycle methods for each event type: // Start the async operation static void my_dns_start(zend_async_event_t *event) { EVENT_START_PROLOGUE(event); // Standard prologue - + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; - + int result = my_event_loop_start_dns(&dns->handle, my_dns_callback); if (result != 0) { my_throw_error("Failed to start DNS operation: %s", my_strerror(result)); return; } - + event->loop_ref_count++; ZEND_ASYNC_INCREASE_EVENT_COUNT; } - - // Stop the async operation + + // Stop the async operation static void my_dns_stop(zend_async_event_t *event) { EVENT_STOP_PROLOGUE(event); // Standard prologue - + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; - + my_event_loop_stop_dns(&dns->handle); - + event->loop_ref_count = 0; ZEND_ASYNC_DECREASE_EVENT_COUNT; } - + // Clean up and dispose static void my_dns_dispose(zend_async_event_t *event) { if (ZEND_ASYNC_EVENT_REF(event) > 1) { ZEND_ASYNC_EVENT_DEL_REF(event); return; } - + // Stop if still running if (event->loop_ref_count > 0) { event->loop_ref_count = 1; event->stop(event); } - + // Free callbacks zend_async_callbacks_free(event); - + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; - + // Clean up backend-specific resources my_event_loop_cleanup_dns(&dns->handle); - + // Free the structure pefree(dns, 0); } -******************** +******************* Callback Handling -******************** +******************* Event Completion Callbacks -=========================== +========================== When your backend completes an operation, notify the core through callbacks: @@ -220,21 +220,21 @@ When your backend completes an operation, notify the core through callbacks: static void my_dns_callback(my_event_loop_handle_t *handle, int status, struct addrinfo *result) { my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)handle->data; zend_object *exception = NULL; - + // Close the event after one-time operations ZEND_ASYNC_EVENT_SET_CLOSED(&dns->event.base); dns->event.base.stop(&dns->event.base); - + // Handle errors if (status != 0) { exception = my_new_exception( "DNS resolution failed: %s", my_strerror(status) ); } - + // Notify all registered callbacks zend_async_callbacks_notify(&dns->event.base, result, exception); - + // Release exception if created if (exception) { zend_object_release(exception); @@ -256,9 +256,9 @@ For operations that return allocated memory (like ``getaddrinfo``): **Make sure your ``getaddrinfo`` results can be freed by your ``freeaddrinfo`` implementation.** -******************* +**************** Error Handling -******************* +**************** Error Reporting =============== @@ -273,18 +273,18 @@ Use the standard PHP error reporting mechanisms: va_start(args, format); zend_string *message = zend_vstrpprintf(0, format, args); va_end(args); - + zend_throw_error(NULL, ZSTR_VAL(message)); zend_string_release(message); } - + // For creating exception objects static zend_object* my_new_exception(const char *format, ...) { va_list args; va_start(args, format); zend_string *message = zend_vstrpprintf(0, format, args); va_end(args); - + zend_object *ex = zend_throw_exception(zend_ce_exception, ZSTR_VAL(message), 0); zend_string_release(message); return ex; @@ -299,10 +299,10 @@ Use appropriate exception classes for different error types: // DNS errors zend_object *dns_ex = async_new_exception(async_ce_dns_exception, "DNS error: %s", error_msg); - - // I/O errors + + // I/O errors zend_object *io_ex = async_new_exception(async_ce_input_output_exception, "I/O error: %s", error_msg); - + // Timeout errors zend_object *timeout_ex = async_new_exception(async_ce_timeout_exception, "Operation timed out"); @@ -319,10 +319,10 @@ The TrueAsync API uses reference counting for event objects: // Add reference (when storing event somewhere) ZEND_ASYNC_EVENT_ADD_REF(event); - + // Remove reference (when done with event) ZEND_ASYNC_EVENT_DEL_REF(event); - + // Release event (decrements and disposes if needed) ZEND_ASYNC_EVENT_RELEASE(event); @@ -335,7 +335,7 @@ Use persistent allocation for long-lived structures: // For event structures that may outlive a request my_event_t *event = pecalloc(1, sizeof(my_event_t), 0); // Persistent - + // For temporary data char *buffer = emalloc(size); // Request-scoped @@ -353,44 +353,44 @@ Support the ``extra_size`` parameter for extensions: // Allocate base size + extra space size_t total_size = sizeof(my_timer_event_t) + extra_size; my_timer_event_t *timer = pecalloc(1, total_size, 0); - + // Set extra offset so extensions can find their data timer->event.base.extra_offset = sizeof(my_timer_event_t); - + // ... rest of initialization } -******************* +**************** Best Practices -******************* +**************** Performance Considerations ========================== -* **Minimize allocations** in hot paths (event creation/callback handling) -* **Use object pools** for frequently created/destroyed events -* **Batch operations** when possible in your event loop -* **Profile memory usage** to detect leaks early +- **Minimize allocations** in hot paths (event creation/callback handling) +- **Use object pools** for frequently created/destroyed events +- **Batch operations** when possible in your event loop +- **Profile memory usage** to detect leaks early Thread Safety -============== +============= -* **Document thread safety** guarantees of your implementation -* **Use proper locking** if supporting multi-threaded access -* **Consider thread-local storage** for per-thread state +- **Document thread safety** guarantees of your implementation +- **Use proper locking** if supporting multi-threaded access +- **Consider thread-local storage** for per-thread state Testing ======= -* **Create unit tests** for each API function implementation -* **Test error conditions** and memory cleanup -* **Verify callback behavior** under various scenarios -* **Load test** with many concurrent operations +- **Create unit tests** for each API function implementation +- **Test error conditions** and memory cleanup +- **Verify callback behavior** under various scenarios +- **Load test** with many concurrent operations Documentation ============= -* **Document implementation-specific behavior** and limitations -* **Provide configuration options** for tuning performance -* **Include examples** showing integration with your event loop -* **Document version compatibility** with different backend versions \ No newline at end of file +- **Document implementation-specific behavior** and limitations +- **Provide configuration options** for tuning performance +- **Include examples** showing integration with your event loop +- **Document version compatibility** with different backend versions diff --git a/docs/source/true_async_api/index.rst b/docs/source/true_async_api/index.rst index 67582e23119fe..e9c33c023474a 100644 --- a/docs/source/true_async_api/index.rst +++ b/docs/source/true_async_api/index.rst @@ -1,6 +1,6 @@ -################ +############### TrueAsync API -################ +############### .. toctree:: :hidden: @@ -14,80 +14,80 @@ examples The TrueAsync API provides a comprehensive asynchronous programming interface for PHP extensions, -built on top of the Zend Engine. This API enables non-blocking operations for I/O, timers, -signals, and other asynchronous tasks while maintaining clean separation between the API -definition and its implementation. +built on top of the Zend Engine. This API enables non-blocking operations for I/O, timers, signals, +and other asynchronous tasks while maintaining clean separation between the API definition and its +implementation. .. warning:: TrueAsync API is currently in development. The API may change in future versions. -The TrueAsync API follows a strict architectural pattern where the PHP core (php-src) defines -the API interfaces, while extensions provide concrete implementations. This design ensures -modularity, testability, and allows for multiple backend implementations (libuv, epoll, etc.). +The TrueAsync API follows a strict architectural pattern where the PHP core (php-src) defines the +API interfaces, while extensions provide concrete implementations. This design ensures modularity, +testability, and allows for multiple backend implementations (libuv, epoll, etc.). -****************** +************** Key Features -****************** - -* **Non-blocking I/O operations** - File, network, and socket operations -* **Timer and scheduling support** - Timeout handling and periodic tasks -* **Signal handling** - Asynchronous signal processing -* **DNS resolution** - Async getaddrinfo/getnameinfo with proper memory management -* **Process management** - Spawn and monitor external processes -* **Filesystem monitoring** - Watch for file and directory changes -* **Clean separation** - API definition vs implementation -* **Memory safety** - Proper reference counting and cleanup - -****************** +************** + +- **Non-blocking I/O operations** - File, network, and socket operations +- **Timer and scheduling support** - Timeout handling and periodic tasks +- **Signal handling** - Asynchronous signal processing +- **DNS resolution** - Async getaddrinfo/getnameinfo with proper memory management +- **Process management** - Spawn and monitor external processes +- **Filesystem monitoring** - Watch for file and directory changes +- **Clean separation** - API definition vs implementation +- **Memory safety** - Proper reference counting and cleanup + +************** Architecture -****************** +************** The TrueAsync API consists of two main components: **API Definition Layer** (``Zend/zend_async_API.h``) - Defines function pointer types, structures, and macros that form the public API. - Located in the php-src core and used by all consumers. + Defines function pointer types, structures, and macros that form the public API. Located in the + php-src core and used by all consumers. **Implementation Layer** (``ext/async/``) - Provides concrete implementations using libuv or other event loop libraries. - Registers implementations with the core through registration functions. + Provides concrete implementations using libuv or other event loop libraries. Registers + implementations with the core through registration functions. -This separation allows php-src core to use async functionality without directly depending -on any specific implementation, enabling clean modular design and testing. +This separation allows php-src core to use async functionality without directly depending on any +specific implementation, enabling clean modular design and testing. -****************** +***************** Getting Started -****************** +***************** To use the TrueAsync API in your extension: -1. Include the API header: ``#include "Zend/zend_async_API.h"`` -2. Check if async is enabled: ``ZEND_ASYNC_IS_ENABLED()`` -3. Use API macros like ``ZEND_ASYNC_GETADDRINFO()`` for operations -4. Handle results through callback mechanisms -5. Clean up resources with appropriate free functions +#. Include the API header: ``#include "Zend/zend_async_API.h"`` +#. Check if async is enabled: ``ZEND_ASYNC_IS_ENABLED()`` +#. Use API macros like ``ZEND_ASYNC_GETADDRINFO()`` for operations +#. Handle results through callback mechanisms +#. Clean up resources with appropriate free functions For implementation details, see the :doc:`implementation-guide` section. -****************** +*************** Quick Example -****************** +*************** .. code:: c // Async DNS resolution zend_async_dns_addrinfo_t *event = ZEND_ASYNC_GETADDRINFO("example.com", "80", &hints); - + // Register callback for completion zend_async_resume_when(coroutine, &event->base, true, my_callback, NULL); - + // Later, in callback: clean up ZEND_ASYNC_FREEADDRINFO(result); -*********************** +************************ Documentation Sections -*********************** +************************ :doc:`architecture` Detailed explanation of the two-repository architecture and component separation. @@ -105,4 +105,4 @@ For implementation details, see the :doc:`implementation-guide` section. Common patterns, best practices, and coding conventions for the API. :doc:`examples` - Practical C code examples showing how to use different API components. \ No newline at end of file + Practical C code examples showing how to use different API components. diff --git a/docs/source/true_async_api/patterns.rst b/docs/source/true_async_api/patterns.rst index e478f9a9ccb95..a064be37e7271 100644 --- a/docs/source/true_async_api/patterns.rst +++ b/docs/source/true_async_api/patterns.rst @@ -2,11 +2,12 @@ Patterns ########## -This section covers common patterns and best practices for implementing TrueAsync API in PHP extensions. +This section covers common patterns and best practices for implementing TrueAsync API in PHP +extensions. -********************* +********************** Fundamental Patterns -********************* +********************** Async-Aware Function Pattern ============================ @@ -23,7 +24,7 @@ The standard pattern for making extension functions async-aware: my_sync_implementation(INTERNAL_FUNCTION_PARAM_PASSTHRU); return; } - + // 2. Check if we're in coroutine context zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; if (!current) { @@ -31,7 +32,7 @@ The standard pattern for making extension functions async-aware: my_sync_implementation(INTERNAL_FUNCTION_PARAM_PASSTHRU); return; } - + // 3. Perform async operation my_async_implementation(current, INTERNAL_FUNCTION_PARAM_PASSTHRU); } @@ -48,7 +49,7 @@ Always check API availability before using async features: if (ZEND_ASYNC_IS_ENABLED()) { // Initialize async-specific resources init_async_resources(); - + if (ZEND_ASYNC_REACTOR_IS_ENABLED()) { // Reactor is available, can use events init_event_handlers(); @@ -68,13 +69,13 @@ Pattern for setting up per-coroutine extension data: .. code:: c static uint32_t my_context_key = 0; - + typedef struct { HashTable *cache; zend_async_timer_event_t *cleanup_timer; bool initialized; } my_context_t; - + static bool my_context_initializer(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) { if (is_finishing) { @@ -86,30 +87,30 @@ Pattern for setting up per-coroutine extension data: } return false; // Remove handler } - + if (!is_enter) return true; - + // Initialize context my_context_t *ctx = ecalloc(1, sizeof(my_context_t)); ALLOC_HASHTABLE(ctx->cache); zend_hash_init(ctx->cache, 16, NULL, ZVAL_PTR_DTOR, 0); ctx->initialized = true; - + zval ctx_zval; ZVAL_PTR(&ctx_zval, ctx); ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &ctx_zval); - + return false; // One-time initialization } - + PHP_MINIT_FUNCTION(my_extension) { my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); - + if (ZEND_ASYNC_IS_ENABLED()) { zend_async_add_main_coroutine_start_handler(my_context_initializer); } - + return SUCCESS; } @@ -126,12 +127,12 @@ Safe pattern for accessing extension context: coroutine = ZEND_ASYNC_CURRENT_COROUTINE; if (!coroutine) return NULL; } - + zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_context_key); if (!ctx_zval || Z_TYPE_P(ctx_zval) != IS_PTR) { return NULL; } - + my_context_t *ctx = (my_context_t *)Z_PTR_P(ctx_zval); return ctx->initialized ? ctx : NULL; } @@ -151,20 +152,20 @@ Resource Acquisition Is Initialization pattern for async events: zend_async_event_t *event; bool owns_reference; } event_guard_t; - + static event_guard_t* event_guard_create(zend_async_event_t *event, bool add_ref) { event_guard_t *guard = emalloc(sizeof(event_guard_t)); guard->event = event; guard->owns_reference = add_ref; - + if (add_ref) { ZEND_ASYNC_EVENT_ADD_REF(event); } - + return guard; } - + static void event_guard_destroy(event_guard_t *guard) { if (guard->owns_reference && guard->event) { @@ -172,20 +173,20 @@ Resource Acquisition Is Initialization pattern for async events: } efree(guard); } - + void my_operation_with_event(void) { zend_async_event_t *event = create_some_event(); event_guard_t *guard = event_guard_create(event, true); - + // Use event... - + // Automatic cleanup event_guard_destroy(guard); } Cleanup Callback Pattern -========================= +======================== Registering cleanup callbacks for async operations: @@ -195,30 +196,30 @@ Registering cleanup callbacks for async operations: void *user_data; void (*cleanup_fn)(void *data); } cleanup_context_t; - + static void cleanup_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, void *result, zend_object *exception) { cleanup_context_t *ctx = (cleanup_context_t *)callback->arg; - + if (ctx->cleanup_fn && ctx->user_data) { ctx->cleanup_fn(ctx->user_data); } - + efree(ctx); } - + void register_cleanup(zend_async_event_t *event, void *data, void (*cleanup_fn)(void*)) { cleanup_context_t *ctx = emalloc(sizeof(cleanup_context_t)); ctx->user_data = data; ctx->cleanup_fn = cleanup_fn; - + zend_async_event_callback_t *callback = zend_async_event_callback_new( event, cleanup_callback, 0 ); callback->arg = ctx; - + event->add_callback(event, callback); } @@ -237,7 +238,7 @@ Proper exception handling in async context: void *result, zend_object *exception) { zend_coroutine_t *coroutine = (zend_coroutine_t *)callback->arg; - + if (exception) { // Propagate exception to coroutine ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, true); @@ -246,7 +247,7 @@ Proper exception handling in async context: process_async_result(result); ZEND_ASYNC_RESUME(coroutine); } - + // Cleanup ZEND_ASYNC_EVENT_DEL_REF(event); } @@ -263,35 +264,35 @@ Handling recoverable errors in async operations: int max_retries; void *operation_data; } retry_context_t; - + static void retry_operation_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, void *result, zend_object *exception) { retry_context_t *ctx = (retry_context_t *)callback->arg; - + if (exception && ctx->retry_count < ctx->max_retries) { // Retry the operation ctx->retry_count++; - php_error(E_NOTICE, "Retrying operation (attempt %d/%d)", + php_error(E_NOTICE, "Retrying operation (attempt %d/%d)", ctx->retry_count, ctx->max_retries); - + retry_async_operation(ctx); return; } - + // Either success or max retries reached if (exception) { handle_final_error(exception, ctx); } else { handle_success(result, ctx); } - + efree(ctx); } -********************* +********************** Performance Patterns -********************* +********************** Object Pooling Pattern ====================== @@ -303,39 +304,39 @@ Reusing objects to reduce allocation overhead: static zend_async_event_callback_t *callback_pool[MAX_POOL_SIZE]; static int pool_size = 0; static zend_mutex_t pool_mutex; - + static zend_async_event_callback_t* get_pooled_callback(void) { zend_mutex_lock(&pool_mutex); - + zend_async_event_callback_t *callback = NULL; if (pool_size > 0) { callback = callback_pool[--pool_size]; } - + zend_mutex_unlock(&pool_mutex); - + if (!callback) { callback = emalloc(sizeof(zend_async_event_callback_t)); memset(callback, 0, sizeof(zend_async_event_callback_t)); } - + return callback; } - + static void return_pooled_callback(zend_async_event_callback_t *callback) { // Reset callback state memset(callback, 0, sizeof(zend_async_event_callback_t)); - + zend_mutex_lock(&pool_mutex); - + if (pool_size < MAX_POOL_SIZE) { callback_pool[pool_size++] = callback; } else { efree(callback); } - + zend_mutex_unlock(&pool_mutex); } @@ -353,14 +354,14 @@ Grouping operations for better performance: void (*completion_callback)(void *batch_data); void *batch_data; } batch_operation_t; - + static void batch_item_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, void *result, zend_object *exception) { batch_operation_t *batch = (batch_operation_t *)callback->arg; - + __sync_fetch_and_add(&batch->completed_count, 1); - + if (batch->completed_count == batch->event_count) { // All operations completed if (batch->completion_callback) { @@ -370,9 +371,9 @@ Grouping operations for better performance: } } -********************* +********************** Integration Patterns -********************* +********************** Reactor Integration Pattern =========================== @@ -386,34 +387,34 @@ Integrating with external event loops: HashTable *pending_events; bool integrated; } reactor_integration_t; - + static void bridge_event_callback(my_external_event_t *ext_event, void *data) { zend_async_event_t *async_event = (zend_async_event_t *)data; - + // Convert external event result to async event result void *result = convert_external_result(ext_event); zend_object *exception = check_external_error(ext_event); - + // Notify async event callbacks async_event->callbacks_notify(async_event, result, exception); } - + bool integrate_with_reactor(my_event_loop_t *loop) { if (!ZEND_ASYNC_REACTOR_IS_ENABLED()) { return false; } - + reactor_integration_t *integration = emalloc(sizeof(reactor_integration_t)); integration->external_loop = loop; - + ALLOC_HASHTABLE(integration->pending_events); zend_hash_init(integration->pending_events, 32, NULL, NULL, 0); - + // Set up bridge between external loop and async events loop->set_callback(loop, bridge_event_callback); - + return true; } @@ -429,18 +430,18 @@ Creating testable async code: .. code:: c #ifdef TESTING_MODE - + typedef struct { zend_async_event_t base; void *expected_result; zend_object *expected_exception; bool should_fail; } mock_event_t; - + static void mock_event_start(zend_async_event_t *event) { mock_event_t *mock = (mock_event_t *)event; - + // Simulate immediate completion for testing if (mock->should_fail) { event->callbacks_notify(event, NULL, mock->expected_exception); @@ -448,28 +449,28 @@ Creating testable async code: event->callbacks_notify(event, mock->expected_result, NULL); } } - + mock_event_t* create_mock_event(void *result, zend_object *exception) { mock_event_t *mock = emalloc(sizeof(mock_event_t)); memset(mock, 0, sizeof(mock_event_t)); - + mock->base.start = mock_event_start; mock->expected_result = result; mock->expected_exception = exception; mock->should_fail = (exception != NULL); - + return mock; } - + #endif -****************** +******************* Advanced Patterns -****************** +******************* State Machine Pattern -====================== +===================== Managing complex async state transitions: @@ -484,19 +485,19 @@ Managing complex async state transitions: STATE_ERROR, STATE_CLOSED } connection_state_t; - + typedef struct { connection_state_t state; zend_async_event_t *current_event; void (*state_handlers[STATE_CLOSED + 1])(void *connection); void *connection_data; } async_state_machine_t; - + static void transition_state(async_state_machine_t *sm, connection_state_t new_state) { if (sm->state != new_state) { sm->state = new_state; - + if (sm->state_handlers[new_state]) { sm->state_handlers[new_state](sm->connection_data); } @@ -516,35 +517,35 @@ Implementing async producer-consumer with channels: zend_coroutine_t *consumer_coro; bool shutdown_requested; } producer_consumer_t; - + static void producer_coroutine(void *arg) { producer_consumer_t *pc = (producer_consumer_t *)arg; - + while (!pc->shutdown_requested) { // Produce data zval data; produce_data(&data); - + // Send to channel (may suspend if channel is full) if (!pc->channel->send(pc->channel, &data)) { break; // Channel closed } } } - + static void consumer_coroutine(void *arg) { producer_consumer_t *pc = (producer_consumer_t *)arg; - + while (!pc->shutdown_requested) { zval data; - + // Receive from channel (may suspend if channel is empty) if (!pc->channel->receive(pc->channel, &data)) { break; // Channel closed } - + // Process data process_data(&data); zval_dtor(&data); @@ -555,13 +556,13 @@ Implementing async producer-consumer with channels: Best Practices Summary ************************ -1. **Always check enablement** before using async APIs -2. **Provide sync fallbacks** for non-async environments -3. **Use internal context** for per-coroutine data -4. **Handle reference counting** properly -5. **Register cleanup handlers** for resource management -6. **Use appropriate error handling** patterns -7. **Consider performance** with pooling and batching -8. **Test thoroughly** with mock implementations -9. **Document async behavior** clearly for users -10. **Follow reactor integration** patterns for external loops \ No newline at end of file +#. **Always check enablement** before using async APIs +#. **Provide sync fallbacks** for non-async environments +#. **Use internal context** for per-coroutine data +#. **Handle reference counting** properly +#. **Register cleanup handlers** for resource management +#. **Use appropriate error handling** patterns +#. **Consider performance** with pooling and batching +#. **Test thoroughly** with mock implementations +#. **Document async behavior** clearly for users +#. **Follow reactor integration** patterns for external loops From a2f97ca3f4d02bd5e8206a2426f6fff47a1cc24f Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:39:33 +0300 Subject: [PATCH 119/137] + Apply Clang format --- Zend/zend_async_API.c | 768 +++++++++++++++--------------- Zend/zend_async_API.h | 1041 +++++++++++++++++++++-------------------- 2 files changed, 913 insertions(+), 896 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index df9e1b44010c9..97902e5c69d88 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -21,7 +21,7 @@ int zend_async_globals_id = 0; size_t zend_async_globals_offset = 0; #else // The default data structure that is used when no other extensions are present. -zend_async_globals_t zend_async_globals_api = {0}; +zend_async_globals_t zend_async_globals_api = { 0 }; #endif #define ASYNC_THROW_ERROR(error) zend_throw_error(NULL, error); @@ -29,58 +29,74 @@ zend_async_globals_t zend_async_globals_api = {0}; /* Forward declarations */ static void zend_async_main_handlers_shutdown(void); -static zend_coroutine_t * spawn(zend_async_scope_t *scope, zend_object *scope_provider, int32_t priority) +static zend_coroutine_t *spawn( + zend_async_scope_t *scope, zend_object *scope_provider, int32_t priority) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; } -static void suspend(bool from_main) {} +static void suspend(bool from_main) +{ +} static void enqueue_coroutine(zend_coroutine_t *coroutine) { ASYNC_THROW_ERROR("Async API is not enabled"); } -static void engine_shutdown_stub(void) {} +static void engine_shutdown_stub(void) +{ +} -static void shutdown_stub(void) {} +static void shutdown_stub(void) +{ +} -static zend_array* get_coroutines_stub(void) { return NULL; } +static zend_array *get_coroutines_stub(void) +{ + return NULL; +} -static zend_future_t* new_future_stub(bool thread_safe, size_t extra_size) +static zend_future_t *new_future_stub(bool thread_safe, size_t extra_size) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; } -static zend_async_channel_t* new_channel_stub(size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size) +static zend_async_channel_t *new_channel_stub( + size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; } -static zend_async_group_t* new_group_stub(size_t extra_size) +static zend_async_group_t *new_group_stub(size_t extra_size) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; } -static zend_object* new_future_obj_stub(zend_future_t *future) +static zend_object *new_future_obj_stub(zend_future_t *future) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; } -static zend_object* new_channel_obj_stub(zend_async_channel_t *channel) +static zend_object *new_channel_obj_stub(zend_async_channel_t *channel) { ASYNC_THROW_ERROR("Async API is not enabled"); return NULL; } -static void add_microtask_stub(zend_async_microtask_t *microtask) {} +static void add_microtask_stub(zend_async_microtask_t *microtask) +{ +} -static zend_array* get_awaiting_info_stub(zend_coroutine_t *coroutine) { return NULL; } +static zend_array *get_awaiting_info_stub(zend_coroutine_t *coroutine) +{ + return NULL; +} static bool spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, int32_t priority) { @@ -88,7 +104,7 @@ static bool spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, i return false; } -static zend_async_context_t * new_context(void) +static zend_async_context_t *new_context(void) { ASYNC_THROW_ERROR("Context API is not enabled"); return NULL; @@ -96,13 +112,11 @@ static zend_async_context_t * new_context(void) /* Internal context stubs removed - using direct implementation */ -static zend_class_entry * get_class_ce(zend_async_class type) +static zend_class_entry *get_class_ce(zend_async_class type) { - if (type == ZEND_ASYNC_EXCEPTION_DEFAULT - || type == ZEND_ASYNC_EXCEPTION_DNS - || type == ZEND_ASYNC_EXCEPTION_POLL - || type == ZEND_ASYNC_EXCEPTION_TIMEOUT - || type == ZEND_ASYNC_EXCEPTION_INPUT_OUTPUT) { + if (type == ZEND_ASYNC_EXCEPTION_DEFAULT || type == ZEND_ASYNC_EXCEPTION_DNS + || type == ZEND_ASYNC_EXCEPTION_POLL || type == ZEND_ASYNC_EXCEPTION_TIMEOUT + || type == ZEND_ASYNC_EXCEPTION_INPUT_OUTPUT) { return zend_ce_exception; } else if (type == ZEND_ASYNC_EXCEPTION_CANCELLATION) { return zend_ce_cancellation_exception; @@ -111,8 +125,8 @@ static zend_class_entry * get_class_ce(zend_async_class type) return NULL; } -static zend_atomic_bool scheduler_lock = {0}; -static char * scheduler_module_name = NULL; +static zend_atomic_bool scheduler_lock = { 0 }; +static char *scheduler_module_name = NULL; zend_async_spawn_t zend_async_spawn_fn = spawn; zend_async_new_coroutine_t zend_async_new_coroutine_fn = NULL; zend_async_new_scope_t zend_async_new_scope_fn = NULL; @@ -135,8 +149,8 @@ zend_async_new_channel_obj_t zend_async_new_channel_obj_fn = new_channel_obj_stu /* GROUP API */ zend_async_new_group_t zend_async_new_group_fn = new_group_stub; -static zend_atomic_bool reactor_lock = {0}; -static char * reactor_module_name = NULL; +static zend_atomic_bool reactor_lock = { 0 }; +static char *reactor_module_name = NULL; zend_async_reactor_startup_t zend_async_reactor_startup_fn = NULL; zend_async_reactor_shutdown_t zend_async_reactor_shutdown_fn = NULL; zend_async_reactor_execute_t zend_async_reactor_execute_fn = NULL; @@ -159,7 +173,7 @@ zend_async_exec_t zend_async_exec_fn = NULL; /* Trigger Event API */ zend_async_new_trigger_event_t zend_async_new_trigger_event_fn = NULL; -static zend_string * thread_pool_module_name = NULL; +static zend_string *thread_pool_module_name = NULL; zend_async_queue_task_t zend_async_queue_task_fn = NULL; /* Iterator API */ @@ -185,7 +199,7 @@ ZEND_API bool zend_async_reactor_is_enabled(void) return reactor_module_name != NULL; } -static void ts_globals_ctor(zend_async_globals_t * globals) +static void ts_globals_ctor(zend_async_globals_t *globals) { globals->state = ZEND_ASYNC_OFF; zend_atomic_bool_store(&globals->heartbeat, false); @@ -199,21 +213,16 @@ static void ts_globals_ctor(zend_async_globals_t * globals) globals->exit_exception = NULL; } -static void ts_globals_dtor(zend_async_globals_t * globals) +static void ts_globals_dtor(zend_async_globals_t *globals) { - } void zend_async_globals_ctor(void) { #ifdef ZTS - ts_allocate_fast_id( - &zend_async_globals_id, - &zend_async_globals_offset, - sizeof(zend_async_globals_t), - (ts_allocate_ctor) ts_globals_ctor, - (ts_allocate_dtor) ts_globals_dtor - ); + ts_allocate_fast_id(&zend_async_globals_id, &zend_async_globals_offset, + sizeof(zend_async_globals_t), (ts_allocate_ctor) ts_globals_ctor, + (ts_allocate_dtor) ts_globals_dtor); ZEND_ASSERT(zend_async_globals_id != 0 && "zend_async_globals allocation failed"); @@ -248,31 +257,19 @@ ZEND_API int zend_async_get_api_version_number(void) return ZEND_ASYNC_API_VERSION_NUMBER; } -ZEND_API bool zend_async_scheduler_register( - char *module, - bool allow_override, - zend_async_new_coroutine_t new_coroutine_fn, - zend_async_new_scope_t new_scope_fn, - zend_async_new_context_t new_context_fn, - zend_async_spawn_t spawn_fn, - zend_async_suspend_t suspend_fn, - zend_async_enqueue_coroutine_t enqueue_coroutine_fn, - zend_async_resume_t resume_fn, - zend_async_cancel_t cancel_fn, - zend_async_spawn_and_throw_t spawn_and_throw_fn, - zend_async_shutdown_t shutdown_fn, - zend_async_get_coroutines_t get_coroutines_fn, - zend_async_add_microtask_t add_microtask_fn, - zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_class_ce_t get_class_ce_fn, - zend_async_new_iterator_t new_iterator_fn, - zend_async_new_future_t new_future_fn, - zend_async_new_channel_t new_channel_fn, - zend_async_new_future_obj_t new_future_obj_fn, - zend_async_new_channel_obj_t new_channel_obj_fn, - zend_async_new_group_t new_group_fn, - zend_async_engine_shutdown_t engine_shutdown_fn -) +ZEND_API bool zend_async_scheduler_register(char *module, bool allow_override, + zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, + zend_async_new_context_t new_context_fn, zend_async_spawn_t spawn_fn, + zend_async_suspend_t suspend_fn, zend_async_enqueue_coroutine_t enqueue_coroutine_fn, + zend_async_resume_t resume_fn, zend_async_cancel_t cancel_fn, + zend_async_spawn_and_throw_t spawn_and_throw_fn, zend_async_shutdown_t shutdown_fn, + zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, + zend_async_get_awaiting_info_t get_awaiting_info_fn, + zend_async_get_class_ce_t get_class_ce_fn, zend_async_new_iterator_t new_iterator_fn, + zend_async_new_future_t new_future_fn, zend_async_new_channel_t new_channel_fn, + zend_async_new_future_obj_t new_future_obj_fn, + zend_async_new_channel_obj_t new_channel_obj_fn, zend_async_new_group_t new_group_fn, + zend_async_engine_shutdown_t engine_shutdown_fn) { if (zend_atomic_bool_exchange(&scheduler_lock, 1)) { return false; @@ -284,10 +281,10 @@ ZEND_API bool zend_async_scheduler_register( if (scheduler_module_name != NULL && false == allow_override) { zend_atomic_bool_store(&scheduler_lock, 0); - zend_error( - E_CORE_ERROR, "The module %s is trying to override Scheduler, which was registered by the module %s.", - module, scheduler_module_name - ); + zend_error(E_CORE_ERROR, + "The module %s is trying to override Scheduler, which was registered by the module " + "%s.", + module, scheduler_module_name); return false; } @@ -296,15 +293,15 @@ ZEND_API bool zend_async_scheduler_register( zend_async_new_coroutine_fn = new_coroutine_fn; zend_async_new_scope_fn = new_scope_fn; zend_async_new_context_fn = new_context_fn; - zend_async_spawn_fn = spawn_fn; - zend_async_suspend_fn = suspend_fn; - zend_async_enqueue_coroutine_fn = enqueue_coroutine_fn; - zend_async_resume_fn = resume_fn; - zend_async_cancel_fn = cancel_fn; - zend_async_spawn_and_throw_fn = spawn_and_throw_fn; - zend_async_shutdown_fn = shutdown_fn; - zend_async_get_coroutines_fn = get_coroutines_fn; - zend_async_add_microtask_fn = add_microtask_fn; + zend_async_spawn_fn = spawn_fn; + zend_async_suspend_fn = suspend_fn; + zend_async_enqueue_coroutine_fn = enqueue_coroutine_fn; + zend_async_resume_fn = resume_fn; + zend_async_cancel_fn = cancel_fn; + zend_async_spawn_and_throw_fn = spawn_and_throw_fn; + zend_async_shutdown_fn = shutdown_fn; + zend_async_get_coroutines_fn = get_coroutines_fn; + zend_async_add_microtask_fn = add_microtask_fn; zend_async_get_awaiting_info_fn = get_awaiting_info_fn; zend_async_get_class_ce_fn = get_class_ce_fn; zend_async_new_iterator_fn = new_iterator_fn; @@ -313,34 +310,28 @@ ZEND_API bool zend_async_scheduler_register( zend_async_new_future_obj_fn = new_future_obj_fn; zend_async_new_channel_obj_fn = new_channel_obj_fn; zend_async_new_group_fn = new_group_fn; - zend_async_engine_shutdown_fn = engine_shutdown_fn; + zend_async_engine_shutdown_fn = engine_shutdown_fn; zend_atomic_bool_store(&scheduler_lock, 0); return true; } -ZEND_API bool zend_async_reactor_register( - char *module, - bool allow_override, - zend_async_reactor_startup_t reactor_startup_fn, - zend_async_reactor_shutdown_t reactor_shutdown_fn, - zend_async_reactor_execute_t reactor_execute_fn, - zend_async_reactor_loop_alive_t reactor_loop_alive_fn, - zend_async_new_socket_event_t new_socket_event_fn, - zend_async_new_poll_event_t new_poll_event_fn, - zend_async_new_timer_event_t new_timer_event_fn, - zend_async_new_signal_event_t new_signal_event_fn, - zend_async_new_process_event_t new_process_event_fn, - zend_async_new_thread_event_t new_thread_event_fn, - zend_async_new_filesystem_event_t new_filesystem_event_fn, - zend_async_getnameinfo_t getnameinfo_fn, - zend_async_getaddrinfo_t getaddrinfo_fn, - zend_async_freeaddrinfo_t freeaddrinfo_fn, - zend_async_new_exec_event_t new_exec_event_fn, - zend_async_exec_t exec_fn, - zend_async_new_trigger_event_t new_trigger_event_fn -) +ZEND_API bool zend_async_reactor_register(char *module, bool allow_override, + zend_async_reactor_startup_t reactor_startup_fn, + zend_async_reactor_shutdown_t reactor_shutdown_fn, + zend_async_reactor_execute_t reactor_execute_fn, + zend_async_reactor_loop_alive_t reactor_loop_alive_fn, + zend_async_new_socket_event_t new_socket_event_fn, + zend_async_new_poll_event_t new_poll_event_fn, + zend_async_new_timer_event_t new_timer_event_fn, + zend_async_new_signal_event_t new_signal_event_fn, + zend_async_new_process_event_t new_process_event_fn, + zend_async_new_thread_event_t new_thread_event_fn, + zend_async_new_filesystem_event_t new_filesystem_event_fn, + zend_async_getnameinfo_t getnameinfo_fn, zend_async_getaddrinfo_t getaddrinfo_fn, + zend_async_freeaddrinfo_t freeaddrinfo_fn, zend_async_new_exec_event_t new_exec_event_fn, + zend_async_exec_t exec_fn, zend_async_new_trigger_event_t new_trigger_event_fn) { if (zend_atomic_bool_exchange(&reactor_lock, 1)) { return false; @@ -351,10 +342,10 @@ ZEND_API bool zend_async_reactor_register( } if (reactor_module_name != NULL && false == allow_override) { - zend_error( - E_CORE_ERROR, "The module %s is trying to override Reactor, which was registered by the module %s.", - module, reactor_module_name - ); + zend_error(E_CORE_ERROR, + "The module %s is trying to override Reactor, which was registered by the module " + "%s.", + module, reactor_module_name); return false; } @@ -365,14 +356,14 @@ ZEND_API bool zend_async_reactor_register( zend_async_reactor_execute_fn = reactor_execute_fn; zend_async_reactor_loop_alive_fn = reactor_loop_alive_fn; - zend_async_new_socket_event_fn = new_socket_event_fn; - zend_async_new_poll_event_fn = new_poll_event_fn; - zend_async_new_timer_event_fn = new_timer_event_fn; - zend_async_new_signal_event_fn = new_signal_event_fn; + zend_async_new_socket_event_fn = new_socket_event_fn; + zend_async_new_poll_event_fn = new_poll_event_fn; + zend_async_new_timer_event_fn = new_timer_event_fn; + zend_async_new_signal_event_fn = new_signal_event_fn; zend_async_new_process_event_fn = new_process_event_fn; - zend_async_new_thread_event_fn = new_thread_event_fn; - zend_async_new_filesystem_event_fn = new_filesystem_event_fn; + zend_async_new_thread_event_fn = new_thread_event_fn; + zend_async_new_filesystem_event_fn = new_filesystem_event_fn; zend_async_getnameinfo_fn = getnameinfo_fn; zend_async_getaddrinfo_fn = getaddrinfo_fn; @@ -388,13 +379,14 @@ ZEND_API bool zend_async_reactor_register( return true; } -ZEND_API void zend_async_thread_pool_register(zend_string *module, bool allow_override, zend_async_queue_task_t queue_task_fn) +ZEND_API void zend_async_thread_pool_register( + zend_string *module, bool allow_override, zend_async_queue_task_t queue_task_fn) { if (thread_pool_module_name != NULL && false == allow_override) { - zend_error( - E_CORE_ERROR, "The module %s is trying to override Thread Pool, which was registered by the module %s.", - ZSTR_VAL(module), ZSTR_VAL(thread_pool_module_name) - ); + zend_error(E_CORE_ERROR, + "The module %s is trying to override Thread Pool, which was registered by the " + "module %s.", + ZSTR_VAL(module), ZSTR_VAL(thread_pool_module_name)); } if (thread_pool_module_name != NULL) { @@ -402,35 +394,29 @@ ZEND_API void zend_async_thread_pool_register(zend_string *module, bool allow_ov } thread_pool_module_name = zend_string_copy(module); - zend_async_queue_task_fn = queue_task_fn; + zend_async_queue_task_fn = queue_task_fn; } -ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char *zend_coroutine_name) +ZEND_API zend_string *zend_coroutine_gen_info( + zend_coroutine_t *coroutine, char *zend_coroutine_name) { if (zend_coroutine_name == NULL) { zend_coroutine_name = ""; } if (ZEND_COROUTINE_SUSPENDED(coroutine)) { - return zend_strpprintf(0, - "Coroutine spawned at %s:%d, suspended at %s:%d (%s)", - coroutine->filename ? ZSTR_VAL(coroutine->filename) : "", - coroutine->lineno, - coroutine->waker->filename ? ZSTR_VAL(coroutine->waker->filename) : "", - coroutine->waker->lineno, - zend_coroutine_name - ); + return zend_strpprintf(0, "Coroutine spawned at %s:%d, suspended at %s:%d (%s)", + coroutine->filename ? ZSTR_VAL(coroutine->filename) : "", coroutine->lineno, + coroutine->waker->filename ? ZSTR_VAL(coroutine->waker->filename) : "", + coroutine->waker->lineno, zend_coroutine_name); } else { - return zend_strpprintf(0, - "Coroutine spawned at %s:%d (%s)", - coroutine->filename ? ZSTR_VAL(coroutine->filename) : "", - coroutine->lineno, - zend_coroutine_name - ); + return zend_strpprintf(0, "Coroutine spawned at %s:%d (%s)", + coroutine->filename ? ZSTR_VAL(coroutine->filename) : "", coroutine->lineno, + zend_coroutine_name); } } -static void event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) +static void event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t *event) { if (callback->ref_count > 1) { // If the callback is still referenced, we cannot dispose it yet @@ -442,9 +428,11 @@ static void event_callback_dispose(zend_async_event_callback_t *callback, zend_a efree(callback); } -ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_event_callback_fn callback, size_t size) +ZEND_API zend_async_event_callback_t *zend_async_event_callback_new( + zend_async_event_callback_fn callback, size_t size) { - zend_async_event_callback_t * event_callback = ecalloc(1, size == 0 ? size : sizeof(zend_async_event_callback_t)); + zend_async_event_callback_t *event_callback + = ecalloc(1, size == 0 ? size : sizeof(zend_async_event_callback_t)); event_callback->ref_count = 1; event_callback->callback = callback; @@ -453,15 +441,14 @@ ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_ return event_callback; } -void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event); +void coroutine_event_callback_dispose( + zend_async_event_callback_t *callback, zend_async_event_t *event); -ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( - zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size -) +ZEND_API zend_coroutine_event_callback_t *zend_async_coroutine_callback_new( + zend_coroutine_t *coroutine, zend_async_event_callback_fn callback, size_t size) { - zend_coroutine_event_callback_t * coroutine_callback = ecalloc( - 1, size != 0 ? size : sizeof(zend_coroutine_event_callback_t) - ); + zend_coroutine_event_callback_t *coroutine_callback + = ecalloc(1, size != 0 ? size : sizeof(zend_coroutine_event_callback_t)); coroutine_callback->base.ref_count = 1; coroutine_callback->base.callback = callback; @@ -475,10 +462,12 @@ ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( /* Waker API */ ////////////////////////////////////////////////////////////////////// -static zend_always_inline zend_async_waker_trigger_t *waker_trigger_create(zend_async_event_t *event, uint32_t initial_capacity) +static zend_always_inline zend_async_waker_trigger_t *waker_trigger_create( + zend_async_event_t *event, uint32_t initial_capacity) { - size_t total_size = sizeof(zend_async_waker_trigger_t) + (initial_capacity - 1) * sizeof(zend_async_event_callback_t *); - zend_async_waker_trigger_t *trigger = (zend_async_waker_trigger_t *)emalloc(total_size); + size_t total_size = sizeof(zend_async_waker_trigger_t) + + (initial_capacity - 1) * sizeof(zend_async_event_callback_t *); + zend_async_waker_trigger_t *trigger = (zend_async_waker_trigger_t *) emalloc(total_size); trigger->length = 0; trigger->capacity = initial_capacity; @@ -487,13 +476,16 @@ static zend_always_inline zend_async_waker_trigger_t *waker_trigger_create(zend_ return trigger; } -static zend_always_inline zend_async_waker_trigger_t *waker_trigger_add_callback(zend_async_waker_trigger_t *trigger, zend_async_event_callback_t *callback) +static zend_always_inline zend_async_waker_trigger_t *waker_trigger_add_callback( + zend_async_waker_trigger_t *trigger, zend_async_event_callback_t *callback) { if (trigger->length >= trigger->capacity) { uint32_t new_capacity = trigger->capacity * 2; - size_t total_size = sizeof(zend_async_waker_trigger_t) + (new_capacity - 1) * sizeof(zend_async_event_callback_t *); + size_t total_size = sizeof(zend_async_waker_trigger_t) + + (new_capacity - 1) * sizeof(zend_async_event_callback_t *); - zend_async_waker_trigger_t *new_trigger = (zend_async_waker_trigger_t *)erealloc(trigger, total_size); + zend_async_waker_trigger_t *new_trigger + = (zend_async_waker_trigger_t *) erealloc(trigger, total_size); new_trigger->capacity = new_capacity; trigger = new_trigger; } @@ -504,7 +496,7 @@ static zend_always_inline zend_async_waker_trigger_t *waker_trigger_add_callback static void waker_events_dtor(zval *item) { - zend_async_waker_trigger_t * trigger = Z_PTR_P(item); + zend_async_waker_trigger_t *trigger = Z_PTR_P(item); zend_async_event_t *event = trigger->event; trigger->event = NULL; @@ -516,8 +508,9 @@ static void waker_events_dtor(zval *item) } } // - // At this point, we explicitly stop the event because it is no longer being listened to by our handlers. - // However, this does not mean the object is destroyed—it may remain in memory if something still holds a reference to it. + // At this point, we explicitly stop the event because it is no longer being listened to by + // our handlers. However, this does not mean the object is destroyed—it may remain in memory + // if something still holds a reference to it. // event->stop(event); ZEND_ASYNC_EVENT_RELEASE(event); @@ -529,7 +522,7 @@ static void waker_events_dtor(zval *item) static void waker_triggered_events_dtor(zval *item) { - zend_async_event_t * event = Z_PTR_P(item); + zend_async_event_t *event = Z_PTR_P(item); ZEND_ASYNC_EVENT_RELEASE(event); } @@ -591,7 +584,7 @@ ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine) coroutine->waker->dtor(coroutine); } - zend_async_waker_t * waker = coroutine->waker; + zend_async_waker_t *waker = coroutine->waker; coroutine->waker = NULL; // default dtor @@ -617,7 +610,8 @@ ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine) efree(waker); } -void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event) +void coroutine_event_callback_dispose( + zend_async_event_callback_t *callback, zend_async_event_t *event) { if (callback->ref_count > 1) { // If the callback is still referenced, we cannot dispose it yet @@ -627,19 +621,20 @@ void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zen // Circular free from destructor return; } else { - ZEND_ASSERT(callback->ref_count > 0 && "Callback ref_count must be greater than 0. Memory corruption detected."); + ZEND_ASSERT(callback->ref_count > 0 + && "Callback ref_count must be greater than 0. Memory corruption detected."); } callback->ref_count = 0; - const zend_coroutine_t * coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; + const zend_coroutine_t *coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; if (EXPECTED(coroutine != NULL)) { - zend_async_waker_t * waker = coroutine->waker; + zend_async_waker_t *waker = coroutine->waker; if (event != NULL && waker != NULL) { // Find the trigger for this event - zval *trigger_zval = zend_hash_index_find(&waker->events, (zend_ulong)event); + zval *trigger_zval = zend_hash_index_find(&waker->events, (zend_ulong) event); if (trigger_zval != NULL) { zend_async_waker_trigger_t *trigger = Z_PTR_P(trigger_zval); @@ -655,10 +650,10 @@ void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zen // If no more callbacks in trigger, remove the entire event if (trigger->length == 0) { - zend_hash_index_del(&waker->events, (zend_ulong)event); + zend_hash_index_del(&waker->events, (zend_ulong) event); if (waker->triggered_events != NULL) { - zend_hash_index_del(waker->triggered_events, (zend_ulong)event); + zend_hash_index_del(waker->triggered_events, (zend_ulong) event); } } } @@ -668,7 +663,8 @@ void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zen efree(callback); } -ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, zend_async_event_t *event) +ZEND_API void zend_async_waker_add_triggered_event( + zend_coroutine_t *coroutine, zend_async_event_t *event) { if (UNEXPECTED(coroutine->waker == NULL)) { return; @@ -679,32 +675,31 @@ ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, zend_hash_init(coroutine->waker->triggered_events, 2, NULL, waker_triggered_events_dtor, 0); } - if (EXPECTED(zend_hash_index_add_ptr(coroutine->waker->triggered_events, (zend_ulong)event, event) != NULL)) { + if (EXPECTED(zend_hash_index_add_ptr( + coroutine->waker->triggered_events, (zend_ulong) event, event) + != NULL)) { ZEND_ASYNC_EVENT_ADD_REF(event); } } -ZEND_API bool zend_async_waker_is_event_exists(zend_coroutine_t *coroutine, zend_async_event_t *event) +ZEND_API bool zend_async_waker_is_event_exists( + zend_coroutine_t *coroutine, zend_async_event_t *event) { if (UNEXPECTED(coroutine->waker == NULL)) { return false; } - return zend_hash_index_find(&coroutine->waker->events, (zend_ulong)event) != NULL; + return zend_hash_index_find(&coroutine->waker->events, (zend_ulong) event) != NULL; } -ZEND_API void zend_async_resume_when( - zend_coroutine_t *coroutine, - zend_async_event_t *event, - const bool trans_event, - zend_async_event_callback_fn callback, - zend_coroutine_event_callback_t *event_callback - ) +ZEND_API void zend_async_resume_when(zend_coroutine_t *coroutine, zend_async_event_t *event, + const bool trans_event, zend_async_event_callback_fn callback, + zend_coroutine_event_callback_t *event_callback) { zend_exception_save(); bool locally_allocated_callback = false; - + if (UNEXPECTED(ZEND_ASYNC_EVENT_IS_CLOSED(event))) { zend_throw_error(NULL, "The event cannot be used after it has been terminated"); zend_exception_restore(); @@ -748,7 +743,7 @@ ZEND_API void zend_async_resume_when( if (locally_allocated_callback) { event_callback->base.dispose(&event_callback->base, event); } - + if (trans_event) { event->dispose(event); } @@ -758,7 +753,7 @@ ZEND_API void zend_async_resume_when( } if (EXPECTED(coroutine->waker != NULL)) { - zval *trigger_zval = zend_hash_index_find(&coroutine->waker->events, (zend_ulong)event); + zval *trigger_zval = zend_hash_index_find(&coroutine->waker->events, (zend_ulong) event); zend_async_waker_trigger_t *trigger; if (UNEXPECTED(trigger_zval != NULL)) { @@ -772,12 +767,14 @@ ZEND_API void zend_async_resume_when( trigger = waker_trigger_create(event, 1); trigger = waker_trigger_add_callback(trigger, &event_callback->base); - if (UNEXPECTED(zend_hash_index_add_ptr(&coroutine->waker->events, (zend_ulong)event, trigger) == NULL)) { + if (UNEXPECTED(zend_hash_index_add_ptr( + &coroutine->waker->events, (zend_ulong) event, trigger) + == NULL)) { // This should not happen with new events, but handle gracefully efree(trigger); - event_callback->coroutine = NULL; - event->del_callback(event, &event_callback->base); + event_callback->coroutine = NULL; + event->del_callback(event, &event_callback->base); event_callback->coroutine = NULL; event->del_callback(event, &event_callback->base); @@ -802,20 +799,22 @@ ZEND_API void zend_async_resume_when( zend_exception_restore(); } -ZEND_API void zend_async_waker_callback_resolve( - zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception -) +ZEND_API void zend_async_waker_callback_resolve(zend_async_event_t *event, + zend_async_event_callback_t *callback, void *result, zend_object *exception) { - zend_coroutine_t * coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; + zend_coroutine_t *coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; if (exception == NULL && coroutine->waker != NULL) { if (coroutine->waker->triggered_events == NULL) { coroutine->waker->triggered_events = (HashTable *) emalloc(sizeof(HashTable)); - zend_hash_init(coroutine->waker->triggered_events, 2, NULL, waker_triggered_events_dtor, 0); + zend_hash_init( + coroutine->waker->triggered_events, 2, NULL, waker_triggered_events_dtor, 0); } - if (EXPECTED(zend_hash_index_add_ptr(coroutine->waker->triggered_events, (zend_ulong)event, event) != NULL)) { + if (EXPECTED(zend_hash_index_add_ptr( + coroutine->waker->triggered_events, (zend_ulong) event, event) + != NULL)) { ZEND_ASYNC_EVENT_ADD_REF(event); } @@ -827,8 +826,8 @@ ZEND_API void zend_async_waker_callback_resolve( if (exception != NULL) { // - // This handler always captures the exception as handled because it passes it to another coroutine. - // As a result, the exception will not propagate further. + // This handler always captures the exception as handled because it passes it to another + // coroutine. As a result, the exception will not propagate further. // ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(event); } @@ -836,39 +835,38 @@ ZEND_API void zend_async_waker_callback_resolve( ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, false); } -ZEND_API void zend_async_waker_callback_cancel( - zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception -) +ZEND_API void zend_async_waker_callback_cancel(zend_async_event_t *event, + zend_async_event_callback_t *callback, void *result, zend_object *exception) { - zend_coroutine_t * coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; + zend_coroutine_t *coroutine = ((zend_coroutine_event_callback_t *) callback)->coroutine; if (UNEXPECTED(exception != NULL)) { ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, false); } else { - ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, zend_async_new_exception( - ZEND_ASYNC_EXCEPTION_CANCELLATION, "Operation has been cancelled" - ), true); + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, + zend_async_new_exception( + ZEND_ASYNC_EXCEPTION_CANCELLATION, "Operation has been cancelled"), + true); } } -ZEND_API void zend_async_waker_callback_timeout( - zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception -) +ZEND_API void zend_async_waker_callback_timeout(zend_async_event_t *event, + zend_async_event_callback_t *callback, void *result, zend_object *exception) { if (UNEXPECTED(exception != NULL)) { - ZEND_ASYNC_RESUME_WITH_ERROR(((zend_coroutine_event_callback_t *) callback)->coroutine, exception, false); + ZEND_ASYNC_RESUME_WITH_ERROR( + ((zend_coroutine_event_callback_t *) callback)->coroutine, exception, false); } else { exception = zend_async_new_exception( - ZEND_ASYNC_EXCEPTION_TIMEOUT, "Operation has been cancelled by timeout" - ); + ZEND_ASYNC_EXCEPTION_TIMEOUT, "Operation has been cancelled by timeout"); - ZEND_ASYNC_RESUME_WITH_ERROR(((zend_coroutine_event_callback_t *) callback)->coroutine, exception, true); + ZEND_ASYNC_RESUME_WITH_ERROR( + ((zend_coroutine_event_callback_t *) callback)->coroutine, exception, true); } } -ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( - zend_coroutine_t * coroutine, const zend_ulong timeout, zend_async_event_t *cancellation -) +ZEND_API zend_async_waker_t *zend_async_waker_new_with_timeout( + zend_coroutine_t *coroutine, const zend_ulong timeout, zend_async_event_t *cancellation) { if (coroutine == NULL) { coroutine = ZEND_ASYNC_CURRENT_COROUTINE; @@ -882,25 +880,20 @@ ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( zend_async_waker_t *waker = zend_async_waker_new(coroutine); if (timeout > 0) { - zend_async_resume_when( - coroutine, - &ZEND_ASYNC_NEW_TIMER_EVENT(timeout, false)->base, - true, - zend_async_waker_callback_resolve, - NULL - ); + zend_async_resume_when(coroutine, &ZEND_ASYNC_NEW_TIMER_EVENT(timeout, false)->base, true, + zend_async_waker_callback_resolve, NULL); } if (cancellation != NULL) { - zend_async_resume_when(coroutine, cancellation, false, zend_async_waker_callback_cancel, NULL); + zend_async_resume_when( + coroutine, cancellation, false, zend_async_waker_callback_cancel, NULL); } return waker; } -ZEND_API bool zend_async_waker_apply_error( - zend_async_waker_t *waker, zend_object *error, bool tranfer_error, bool override, bool for_cancellation -) +ZEND_API bool zend_async_waker_apply_error(zend_async_waker_t *waker, zend_object *error, + bool tranfer_error, bool override, bool for_cancellation) { if (UNEXPECTED(waker == NULL)) { if (tranfer_error) { @@ -947,7 +940,8 @@ ZEND_API bool zend_async_waker_apply_error( /* Exception API */ ////////////////////////////////////////////////////////////////////// -ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_class type, const char *format, ...) +ZEND_API ZEND_COLD zend_object *zend_async_new_exception( + zend_async_class type, const char *format, ...) { if (type == ZEND_ASYNC_CLASS_NO) { type = ZEND_ASYNC_EXCEPTION_DEFAULT; @@ -957,7 +951,7 @@ ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_class type, zval exception, message_val; ZEND_ASSERT(instanceof_function(exception_ce, zend_ce_throwable) - && "Exceptions must implement Throwable"); + && "Exceptions must implement Throwable"); object_init_ex(&exception, exception_ce); @@ -968,7 +962,8 @@ ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_class type, if (message) { ZVAL_STR(&message_val, message); - zend_update_property_ex(exception_ce, Z_OBJ(exception), ZSTR_KNOWN(ZEND_STR_MESSAGE), &message_val); + zend_update_property_ex( + exception_ce, Z_OBJ(exception), ZSTR_KNOWN(ZEND_STR_MESSAGE), &message_val); } zend_string_release(message); @@ -976,7 +971,7 @@ ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_class type, return Z_OBJ(exception); } -ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_class type, const char *format, ...) +ZEND_API ZEND_COLD zend_object *zend_async_throw(zend_async_class type, const char *format, ...) { if (type == ZEND_ASYNC_CLASS_NO) { type = ZEND_ASYNC_EXCEPTION_DEFAULT; @@ -987,39 +982,41 @@ ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_class type, const c zend_string *message = zend_vstrpprintf(0, format, args); va_end(args); - zend_object *obj = zend_throw_exception(ZEND_ASYNC_GET_EXCEPTION_CE(type), ZSTR_VAL(message), 0); + zend_object *obj + = zend_throw_exception(ZEND_ASYNC_GET_EXCEPTION_CE(type), ZSTR_VAL(message), 0); zend_string_release(message); return obj; } -ZEND_API ZEND_COLD zend_object * zend_async_throw_cancellation(const char *format, ...) +ZEND_API ZEND_COLD zend_object *zend_async_throw_cancellation(const char *format, ...) { const zend_object *previous = EG(exception); - if (format == NULL - && previous != NULL - && instanceof_function(previous->ce, ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_TIMEOUT))) { - format = "The operation was canceled by timeout"; - } else { - format = format ? format : "The operation was canceled"; - } + if (format == NULL && previous != NULL + && instanceof_function( + previous->ce, ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_TIMEOUT))) { + format = "The operation was canceled by timeout"; + } else { + format = format ? format : "The operation was canceled"; + } va_list args; va_start(args, format); zend_object *obj = zend_throw_exception_ex( - ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_CANCELLATION), 0, format, args - ); + ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_CANCELLATION), 0, format, args); va_end(args); return obj; } -ZEND_API ZEND_COLD zend_object * zend_async_throw_timeout(const char *format, const zend_long timeout) +ZEND_API ZEND_COLD zend_object *zend_async_throw_timeout( + const char *format, const zend_long timeout) { format = format ? format : "A timeout of %u microseconds occurred"; - return zend_throw_exception_ex(ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_TIMEOUT), 0, format, timeout); + return zend_throw_exception_ex( + ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_TIMEOUT), 0, format, timeout); } ////////////////////////////////////////////////////////////////////// @@ -1034,7 +1031,8 @@ ZEND_API ZEND_COLD zend_object * zend_async_throw_timeout(const char *format, co /// /// 1. The extension registers a unique numeric index by `zend_async_internal_context_key_alloc`, /// described by a static C string. -/// 2. The **TrueAsync API** verifies the uniqueness of the mapping between the index and the C string address. +/// 2. The **TrueAsync API** verifies the uniqueness of the mapping between the index and the C +/// string address. /// 3. The extension uses `zend_async_internal_context_*` functions to get, set, or unset keys. /// 4. Keys are automatically destroyed when the coroutine completes. ////////////////////////////////////////////////////////////////////// @@ -1046,135 +1044,139 @@ static HashTable *zend_async_context_key_names = NULL; static MUTEX_T zend_async_context_mutex = NULL; #endif -void zend_async_init_internal_context_api(void) +void zend_async_init_internal_context_api(void) { #ifdef ZTS - zend_async_context_mutex = tsrm_mutex_alloc(); + zend_async_context_mutex = tsrm_mutex_alloc(); #endif - // Initialize key names table - stores string pointers directly - zend_async_context_key_names = pemalloc(sizeof(HashTable), 1); - zend_hash_init(zend_async_context_key_names, 8, NULL, NULL, 1); // No destructor - we don't own the strings + // Initialize key names table - stores string pointers directly + zend_async_context_key_names = pemalloc(sizeof(HashTable), 1); + zend_hash_init(zend_async_context_key_names, 8, NULL, NULL, + 1); // No destructor - we don't own the strings } -uint32_t zend_async_internal_context_key_alloc(const char *key_name) +uint32_t zend_async_internal_context_key_alloc(const char *key_name) { #ifdef ZTS - tsrm_mutex_lock(zend_async_context_mutex); + tsrm_mutex_lock(zend_async_context_mutex); #endif - - // Check if this string address already has a key - const char **existing_ptr; - ZEND_HASH_FOREACH_NUM_KEY_PTR(zend_async_context_key_names, zend_ulong existing_key, existing_ptr) { - if (*existing_ptr == key_name) { - // Found existing key for this string address + + // Check if this string address already has a key + const char **existing_ptr; + ZEND_HASH_FOREACH_NUM_KEY_PTR( + zend_async_context_key_names, zend_ulong existing_key, existing_ptr) + { + if (*existing_ptr == key_name) { + // Found existing key for this string address #ifdef ZTS - tsrm_mutex_unlock(zend_async_context_mutex); + tsrm_mutex_unlock(zend_async_context_mutex); #endif - return (uint32_t)existing_key; - } - } ZEND_HASH_FOREACH_END(); - - // Use next available index as key - uint32_t key = zend_hash_num_elements(zend_async_context_key_names) + 1; - - // Store string pointer directly - zend_hash_index_add_ptr(zend_async_context_key_names, key, (void*)key_name); - + return (uint32_t) existing_key; + } + } + ZEND_HASH_FOREACH_END(); + + // Use next available index as key + uint32_t key = zend_hash_num_elements(zend_async_context_key_names) + 1; + + // Store string pointer directly + zend_hash_index_add_ptr(zend_async_context_key_names, key, (void *) key_name); + #ifdef ZTS - tsrm_mutex_unlock(zend_async_context_mutex); + tsrm_mutex_unlock(zend_async_context_mutex); #endif - - return key; + + return key; } -const char* zend_async_internal_context_key_name(uint32_t key) +const char *zend_async_internal_context_key_name(uint32_t key) { - if (zend_async_context_key_names == NULL) { - return NULL; - } - + if (zend_async_context_key_names == NULL) { + return NULL; + } + #ifdef ZTS - tsrm_mutex_lock(zend_async_context_mutex); + tsrm_mutex_lock(zend_async_context_mutex); #endif - - const char *name = zend_hash_index_find_ptr(zend_async_context_key_names, key); - + + const char *name = zend_hash_index_find_ptr(zend_async_context_key_names, key); + #ifdef ZTS - tsrm_mutex_unlock(zend_async_context_mutex); + tsrm_mutex_unlock(zend_async_context_mutex); #endif - - return name; + + return name; } -zval* zend_async_internal_context_find(zend_coroutine_t *coroutine, uint32_t key) +zval *zend_async_internal_context_find(zend_coroutine_t *coroutine, uint32_t key) { - if (coroutine == NULL || coroutine->internal_context == NULL) { - return NULL; - } - - return zend_hash_index_find(coroutine->internal_context, key); + if (coroutine == NULL || coroutine->internal_context == NULL) { + return NULL; + } + + return zend_hash_index_find(coroutine->internal_context, key); } -void zend_async_internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value) +void zend_async_internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value) { - if (coroutine == NULL) { - return; - } - - // Initialize internal_context if needed - if (coroutine->internal_context == NULL) { - coroutine->internal_context = zend_new_array(0); - } - - // Set the value - zval copy; - ZVAL_COPY(©, value); - zend_hash_index_update(coroutine->internal_context, key, ©); + if (coroutine == NULL) { + return; + } + + // Initialize internal_context if needed + if (coroutine->internal_context == NULL) { + coroutine->internal_context = zend_new_array(0); + } + + // Set the value + zval copy; + ZVAL_COPY(©, value); + zend_hash_index_update(coroutine->internal_context, key, ©); } -bool zend_async_internal_context_unset(zend_coroutine_t *coroutine, uint32_t key) +bool zend_async_internal_context_unset(zend_coroutine_t *coroutine, uint32_t key) { - if (coroutine == NULL || coroutine->internal_context == NULL) { - return false; - } - - return zend_hash_index_del(coroutine->internal_context, key) == SUCCESS; + if (coroutine == NULL || coroutine->internal_context == NULL) { + return false; + } + + return zend_hash_index_del(coroutine->internal_context, key) == SUCCESS; } void zend_async_coroutine_internal_context_dispose(zend_coroutine_t *coroutine) { - if (coroutine->internal_context != NULL) { - zend_array_release(coroutine->internal_context); - coroutine->internal_context = NULL; - } + if (coroutine->internal_context != NULL) { + zend_array_release(coroutine->internal_context); + coroutine->internal_context = NULL; + } } void zend_async_internal_context_api_shutdown(void) { #ifdef ZTS - if (zend_async_context_mutex != NULL) { - tsrm_mutex_lock(zend_async_context_mutex); - } + if (zend_async_context_mutex != NULL) { + tsrm_mutex_lock(zend_async_context_mutex); + } #endif - - if (zend_async_context_key_names != NULL) { - zend_hash_destroy(zend_async_context_key_names); - pefree(zend_async_context_key_names, 1); - zend_async_context_key_names = NULL; - } - + + if (zend_async_context_key_names != NULL) { + zend_hash_destroy(zend_async_context_key_names); + pefree(zend_async_context_key_names, 1); + zend_async_context_key_names = NULL; + } + #ifdef ZTS - if (zend_async_context_mutex != NULL) { - tsrm_mutex_unlock(zend_async_context_mutex); - tsrm_mutex_free(zend_async_context_mutex); - zend_async_context_mutex = NULL; - } + if (zend_async_context_mutex != NULL) { + tsrm_mutex_unlock(zend_async_context_mutex); + tsrm_mutex_free(zend_async_context_mutex); + zend_async_context_mutex = NULL; + } #endif } void zend_async_coroutine_internal_context_init(zend_coroutine_t *coroutine) { - coroutine->internal_context = NULL; + coroutine->internal_context = NULL; } ////////////////////////////////////////////////////////////////////// /* Internal Context API end */ @@ -1187,28 +1189,28 @@ void zend_async_coroutine_internal_context_init(zend_coroutine_t *coroutine) /* Private helper functions for iterator management - static inline for performance */ /* Register the single iterator - prevents concurrent iterations */ -static zend_always_inline bool -zend_async_callbacks_register_iterator(zend_async_callbacks_vector_t *vector, uint32_t *iterator_index) +static zend_always_inline bool zend_async_callbacks_register_iterator( + zend_async_callbacks_vector_t *vector, uint32_t *iterator_index) { if (vector->current_iterator != NULL) { // Concurrent iteration detected - this is not allowed return false; } - + vector->current_iterator = iterator_index; return true; } /* Unregister the iterator after iteration completes */ -static zend_always_inline void -zend_async_callbacks_unregister_iterator(zend_async_callbacks_vector_t *vector) +static zend_always_inline void zend_async_callbacks_unregister_iterator( + zend_async_callbacks_vector_t *vector) { vector->current_iterator = NULL; } /* Adjust the active iterator when an element is removed */ -static zend_always_inline void -zend_async_callbacks_adjust_iterator(zend_async_callbacks_vector_t *vector, uint32_t removed_index) +static zend_always_inline void zend_async_callbacks_adjust_iterator( + zend_async_callbacks_vector_t *vector, uint32_t removed_index) { if (vector->current_iterator != NULL && *vector->current_iterator > removed_index) { (*vector->current_iterator)--; @@ -1218,8 +1220,8 @@ zend_async_callbacks_adjust_iterator(zend_async_callbacks_vector_t *vector, uint /* Public API implementations */ /* Remove a specific callback; order is NOT preserved, but iterator is safely adjusted */ -ZEND_API void -zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback_t *callback) +ZEND_API void zend_async_callbacks_remove( + zend_async_event_t *event, zend_async_event_callback_t *callback) { zend_async_callbacks_vector_t *vector = &event->callbacks; @@ -1227,7 +1229,7 @@ zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback if (vector->data[i] == callback) { // Adjust the active iterator before performing the removal zend_async_callbacks_adjust_iterator(vector, i); - + // O(1) removal: move last element to current position vector->data[i] = vector->length > 0 ? vector->data[--vector->length] : NULL; callback->dispose(callback, event); @@ -1237,8 +1239,8 @@ zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback } /* Call all callbacks with safe iterator tracking to handle concurrent modifications */ -ZEND_API void -zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception, bool from_handler) +ZEND_API void zend_async_callbacks_notify( + zend_async_event_t *event, void *result, zend_object *exception, bool from_handler) { // Protect from self-deletion by incrementing ref count before calling callbacks ZEND_ASYNC_EVENT_ADD_REF(event); @@ -1259,12 +1261,11 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object zend_async_callbacks_vector_t *vector = &event->callbacks; uint32_t current_index = 0; - + // Register iterator - prevents concurrent iterations if (!zend_async_callbacks_register_iterator(vector, ¤t_index)) { zend_error(E_CORE_WARNING, - "Concurrent callback iteration detected - nested notify() calls are not allowed" - ); + "Concurrent callback iteration detected - nested notify() calls are not allowed"); ZEND_ASYNC_EVENT_RELEASE(event); return; } @@ -1281,7 +1282,7 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object // Execute callback callback->callback(event, callback, result, exception); - + if (UNEXPECTED(EG(exception) != NULL)) { break; } @@ -1289,14 +1290,15 @@ zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object // Unregister iterator zend_async_callbacks_unregister_iterator(vector); - - // Dispose the reference we added at the beginning - this may trigger disposal if ref count reaches 0 + + // Dispose the reference we added at the beginning - this may trigger disposal if ref count + // reaches 0 ZEND_ASYNC_EVENT_RELEASE(event); } /* Call all callbacks and close the event (Like future) */ -ZEND_API void -zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception) +ZEND_API void zend_async_callbacks_notify_and_close( + zend_async_event_t *event, void *result, zend_object *exception) { event->stop(event); ZEND_ASYNC_EVENT_SET_CLOSED(event); @@ -1304,8 +1306,7 @@ zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, z } /* Free the vector's memory including iterator tracking */ -ZEND_API void -zend_async_callbacks_free(zend_async_event_t *event) +ZEND_API void zend_async_callbacks_free(zend_async_event_t *event) { if (event->callbacks.data == NULL) { return; @@ -1313,12 +1314,11 @@ zend_async_callbacks_free(zend_async_event_t *event) zend_async_callbacks_vector_t *vector = &event->callbacks; uint32_t current_index = 0; - + // Register iterator - prevents concurrent iterations if (!zend_async_callbacks_register_iterator(vector, ¤t_index)) { zend_error(E_CORE_WARNING, - "Concurrent callback iteration detected - nested free() calls are not allowed" - ); + "Concurrent callback iteration detected - nested free() calls are not allowed"); return; } @@ -1336,9 +1336,9 @@ zend_async_callbacks_free(zend_async_event_t *event) zend_async_callbacks_unregister_iterator(vector); // Reset all fields - vector->data = NULL; - vector->length = 0; - vector->capacity = 0; + vector->data = NULL; + vector->length = 0; + vector->capacity = 0; vector->current_iterator = NULL; } @@ -1346,7 +1346,8 @@ zend_async_callbacks_free(zend_async_event_t *event) /// Socket Listening API Registration /////////////////////////////////////////////////////////////// /* Socket listening stubs */ -static zend_async_listen_event_t* socket_listen_stub(const char *host, int port, int backlog, size_t extra_size) +static zend_async_listen_event_t *socket_listen_stub( + const char *host, int port, int backlog, size_t extra_size) { ASYNC_THROW_ERROR("Socket listening API is not enabled"); return NULL; @@ -1356,14 +1357,11 @@ static zend_async_listen_event_t* socket_listen_stub(const char *host, int port, ZEND_API zend_async_socket_listen_t zend_async_socket_listen_fn = socket_listen_stub; /* Registration lock for socket listening */ -static zend_atomic_bool socket_listening_lock = {0}; +static zend_atomic_bool socket_listening_lock = { 0 }; static char *socket_listening_module_name = NULL; ZEND_API bool zend_async_socket_listening_register( - char *module, - bool allow_override, - zend_async_socket_listen_t socket_listen_fn -) + char *module, bool allow_override, zend_async_socket_listen_t socket_listen_fn) { if (zend_atomic_bool_exchange(&socket_listening_lock, 1)) { return false; @@ -1374,10 +1372,10 @@ ZEND_API bool zend_async_socket_listening_register( } if (socket_listening_module_name != NULL && false == allow_override) { - zend_error( - E_CORE_ERROR, "The module %s is trying to override Socket Listening API, which was registered by the module %s.", - module, socket_listening_module_name - ); + zend_error(E_CORE_ERROR, + "The module %s is trying to override Socket Listening API, which was registered by " + "the module %s.", + module, socket_listening_module_name); return false; } @@ -1396,7 +1394,7 @@ ZEND_API void zend_coroutine_switch_handlers_init(zend_coroutine_t *coroutine) if (coroutine->switch_handlers) { return; /* Already initialized */ } - + coroutine->switch_handlers = emalloc(sizeof(zend_coroutine_switch_handlers_vector_t)); coroutine->switch_handlers->length = 0; coroutine->switch_handlers->capacity = 0; @@ -1409,7 +1407,7 @@ ZEND_API void zend_coroutine_switch_handlers_destroy(zend_coroutine_t *coroutine if (!coroutine->switch_handlers) { return; } - + if (coroutine->switch_handlers->data) { efree(coroutine->switch_handlers->data); } @@ -1418,96 +1416,90 @@ ZEND_API void zend_coroutine_switch_handlers_destroy(zend_coroutine_t *coroutine } ZEND_API uint32_t zend_coroutine_add_switch_handler( - zend_coroutine_t *coroutine, - zend_coroutine_switch_handler_fn handler) + zend_coroutine_t *coroutine, zend_coroutine_switch_handler_fn handler) { if (!handler) { zend_error(E_WARNING, "Cannot add NULL switch handler"); return 0; } - + if (!coroutine->switch_handlers) { zend_coroutine_switch_handlers_init(coroutine); } - + zend_coroutine_switch_handlers_vector_t *vector = coroutine->switch_handlers; - + if (vector->in_execution) { zend_error(E_WARNING, "Cannot add switch handler during handler execution"); return 0; } - + /* Check for duplicate handler */ for (uint32_t i = 0; i < vector->length; i++) { if (vector->data[i].handler == handler) { return i; } } - + /* Expand array if needed */ if (vector->length == vector->capacity) { vector->capacity = vector->capacity ? vector->capacity * 2 : 4; - vector->data = safe_erealloc(vector->data, - vector->capacity, - sizeof(zend_coroutine_switch_handler_t), - 0); + vector->data = safe_erealloc( + vector->data, vector->capacity, sizeof(zend_coroutine_switch_handler_t), 0); } - + /* Add handler */ uint32_t index = vector->length; vector->data[index].handler = handler; vector->length++; - + return index; } ZEND_API bool zend_coroutine_remove_switch_handler( - zend_coroutine_t *coroutine, - uint32_t handler_index) + zend_coroutine_t *coroutine, uint32_t handler_index) { if (!coroutine->switch_handlers) { return false; } - + zend_coroutine_switch_handlers_vector_t *vector = coroutine->switch_handlers; - + if (vector->in_execution) { zend_error(E_WARNING, "Cannot remove switch handler during handler execution"); return false; } - + if (handler_index >= vector->length) { return false; } - + /* Shift elements to remove the handler */ for (uint32_t i = handler_index; i < vector->length - 1; i++) { vector->data[i] = vector->data[i + 1]; } - + vector->length--; return true; } ZEND_API void zend_coroutine_call_switch_handlers( - zend_coroutine_t *coroutine, - bool is_enter, - bool is_finishing) + zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) { if (!coroutine->switch_handlers || coroutine->switch_handlers->length == 0) { return; } - + zend_coroutine_switch_handlers_vector_t *vector = coroutine->switch_handlers; - + /* Set execution protection flag */ vector->in_execution = true; - + /* Call all handlers and remove those that return false */ uint32_t write_index = 0; for (uint32_t read_index = 0; read_index < vector->length; read_index++) { bool keep_handler = vector->data[read_index].handler(coroutine, is_enter, is_finishing); - + if (keep_handler) { /* Keep this handler - move it to write position if needed */ if (write_index != read_index) { @@ -1517,13 +1509,13 @@ ZEND_API void zend_coroutine_call_switch_handlers( } /* If keep_handler is false, we skip copying this handler (effectively removing it) */ } - + /* Update length to reflect removed handlers */ vector->length = write_index; - + /* Clear execution protection flag */ vector->in_execution = false; - + /* Free vector memory if no handlers remain */ if (vector->length == 0 && vector->data != NULL) { coroutine->switch_handlers = NULL; @@ -1538,10 +1530,10 @@ ZEND_API void zend_coroutine_call_switch_handlers( /// Global Main Coroutine Switch Handlers Implementation /////////////////////////////////////////////////////////////// -static zend_coroutine_switch_handlers_vector_t global_main_coroutine_start_handlers = {0, 0, NULL, false}; +static zend_coroutine_switch_handlers_vector_t global_main_coroutine_start_handlers + = { 0, 0, NULL, false }; -ZEND_API void zend_async_add_main_coroutine_start_handler( - zend_coroutine_switch_handler_fn handler) +ZEND_API void zend_async_add_main_coroutine_start_handler(zend_coroutine_switch_handler_fn handler) { zend_coroutine_switch_handlers_vector_t *vector = &global_main_coroutine_start_handlers; @@ -1555,7 +1547,8 @@ ZEND_API void zend_async_add_main_coroutine_start_handler( /* Expand vector if needed */ if (vector->length >= vector->capacity) { uint32_t new_capacity = vector->capacity ? vector->capacity * 2 : 4; - vector->data = safe_perealloc(vector->data, new_capacity, sizeof(zend_coroutine_switch_handler_t), 0, 1); + vector->data = safe_perealloc( + vector->data, new_capacity, sizeof(zend_coroutine_switch_handler_t), 0, 1); vector->capacity = new_capacity; } @@ -1579,10 +1572,7 @@ ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *ma /* Copy all global handlers to main coroutine first */ for (uint32_t i = 0; i < global_vector->length; i++) { - zend_coroutine_add_switch_handler( - main_coroutine, - global_vector->data[i].handler - ); + zend_coroutine_add_switch_handler(main_coroutine, global_vector->data[i].handler); } /* Now call the standard switch handlers function which will handle removal logic */ @@ -1593,7 +1583,7 @@ ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *ma static void zend_async_main_handlers_shutdown(void) { zend_coroutine_switch_handlers_vector_t *vector = &global_main_coroutine_start_handlers; - + if (vector->data != NULL) { pefree(vector->data, 1); vector->data = NULL; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 4b93a0c4af13d..04f4b643807f9 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -25,9 +25,8 @@ #define ZEND_ASYNC_API_VERSION_PATCH 0 #define ZEND_ASYNC_API_VERSION_NUMBER \ - ((ZEND_ASYNC_API_VERSION_MAJOR << 16) | \ - (ZEND_ASYNC_API_VERSION_MINOR << 8) | \ - (ZEND_ASYNC_API_VERSION_PATCH)) + ((ZEND_ASYNC_API_VERSION_MAJOR << 16) | (ZEND_ASYNC_API_VERSION_MINOR << 8) \ + | (ZEND_ASYNC_API_VERSION_PATCH)) #ifndef PHP_WIN32 #include @@ -42,18 +41,18 @@ typedef enum { } async_poll_event; /* Signal Constants */ -#define ZEND_ASYNC_SIGHUP 1 -#define ZEND_ASYNC_SIGINT 2 -#define ZEND_ASYNC_SIGQUIT 3 -#define ZEND_ASYNC_SIGILL 4 +#define ZEND_ASYNC_SIGHUP 1 +#define ZEND_ASYNC_SIGINT 2 +#define ZEND_ASYNC_SIGQUIT 3 +#define ZEND_ASYNC_SIGILL 4 #define ZEND_ASYNC_SIGABRT_COMPAT 6 -#define ZEND_ASYNC_SIGFPE 8 -#define ZEND_ASYNC_SIGKILL 9 -#define ZEND_ASYNC_SIGSEGV 11 -#define ZEND_ASYNC_SIGTERM 15 -#define ZEND_ASYNC_SIGBREAK 21 -#define ZEND_ASYNC_SIGABRT 22 -#define ZEND_ASYNC_SIGWINCH 28 +#define ZEND_ASYNC_SIGFPE 8 +#define ZEND_ASYNC_SIGKILL 9 +#define ZEND_ASYNC_SIGSEGV 11 +#define ZEND_ASYNC_SIGTERM 15 +#define ZEND_ASYNC_SIGBREAK 21 +#define ZEND_ASYNC_SIGABRT 22 +#define ZEND_ASYNC_SIGWINCH 28 // // Definitions compatibles with proc_open() @@ -93,8 +92,7 @@ typedef enum { ZEND_COROUTINE_HI_PRIORITY = 255 } zend_coroutine_priority; -typedef enum -{ +typedef enum { ZEND_ASYNC_CLASS_NO = 0, ZEND_ASYNC_CLASS_AWAITABLE = 1, ZEND_ASYNC_CLASS_COROUTINE = 2, @@ -148,29 +146,27 @@ typedef struct _zend_coroutine_switch_handler_s zend_coroutine_switch_handler_t; typedef struct _zend_coroutine_switch_handlers_vector_s zend_coroutine_switch_handlers_vector_t; typedef bool (*zend_coroutine_switch_handler_fn)( - zend_coroutine_t *coroutine, - bool is_enter, /* true = entering coroutine, false = leaving */ - bool is_finishing /* true = coroutine is finishing */ - /* returns: true = keep handler, false = remove handler after execution */ + zend_coroutine_t *coroutine, bool is_enter, /* true = entering coroutine, false = leaving */ + bool is_finishing /* true = coroutine is finishing */ + /* returns: true = keep handler, false = remove handler after execution */ ); typedef struct _zend_async_event_s zend_async_event_t; typedef struct _zend_async_event_callback_s zend_async_event_callback_t; typedef struct _zend_async_waker_trigger_s zend_async_waker_trigger_t; typedef struct _zend_coroutine_event_callback_s zend_coroutine_event_callback_t; -typedef void (*zend_async_event_callback_fn) -( - zend_async_event_t *event, - zend_async_event_callback_t *callback, - void * result, - zend_object *exception -); -typedef void (*zend_async_event_callback_dispose_fn)(zend_async_event_callback_t *callback, zend_async_event_t * event); -typedef void (*zend_async_event_add_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); -typedef void (*zend_async_event_del_callback_t)(zend_async_event_t *event, zend_async_event_callback_t *callback); -typedef void (*zend_async_event_callbacks_notify_t)(zend_async_event_t *event, void *result, zend_object *exception); -typedef void (*zend_async_event_start_t) (zend_async_event_t *event); -typedef void (*zend_async_event_stop_t) (zend_async_event_t *event); +typedef void (*zend_async_event_callback_fn)(zend_async_event_t *event, + zend_async_event_callback_t *callback, void *result, zend_object *exception); +typedef void (*zend_async_event_callback_dispose_fn)( + zend_async_event_callback_t *callback, zend_async_event_t *event); +typedef void (*zend_async_event_add_callback_t)( + zend_async_event_t *event, zend_async_event_callback_t *callback); +typedef void (*zend_async_event_del_callback_t)( + zend_async_event_t *event, zend_async_event_callback_t *callback); +typedef void (*zend_async_event_callbacks_notify_t)( + zend_async_event_t *event, void *result, zend_object *exception); +typedef void (*zend_async_event_start_t)(zend_async_event_t *event); +typedef void (*zend_async_event_stop_t)(zend_async_event_t *event); /** * The replay method can be called in several modes: @@ -179,11 +175,10 @@ typedef void (*zend_async_event_stop_t) (zend_async_event_t *event); * * The method will return true if the result was applied. */ -typedef bool (*zend_async_event_replay_t) ( - zend_async_event_t *event, zend_async_event_callback_t *callback, zval *result, zend_object **exception -); -typedef void (*zend_async_event_dispose_t) (zend_async_event_t *event); -typedef zend_string* (*zend_async_event_info_t) (zend_async_event_t *event); +typedef bool (*zend_async_event_replay_t)(zend_async_event_t *event, + zend_async_event_callback_t *callback, zval *result, zend_object **exception); +typedef void (*zend_async_event_dispose_t)(zend_async_event_t *event); +typedef zend_string *(*zend_async_event_info_t)(zend_async_event_t *event); typedef struct _zend_async_poll_event_s zend_async_poll_event_t; typedef struct _zend_async_timer_event_s zend_async_timer_event_t; @@ -205,91 +200,75 @@ typedef struct _zend_async_task_s zend_async_task_t; /* Internal context typedefs removed - using direct functions */ -typedef zend_coroutine_t * (*zend_async_new_coroutine_t)(zend_async_scope_t *scope); -typedef zend_async_scope_t * (*zend_async_new_scope_t)(zend_async_scope_t * parent_scope, bool with_zend_object); -typedef zend_coroutine_t * (*zend_async_spawn_t)(zend_async_scope_t *scope, zend_object *scope_provider, int32_t priority); +typedef zend_coroutine_t *(*zend_async_new_coroutine_t)(zend_async_scope_t *scope); +typedef zend_async_scope_t *(*zend_async_new_scope_t)( + zend_async_scope_t *parent_scope, bool with_zend_object); +typedef zend_coroutine_t *(*zend_async_spawn_t)( + zend_async_scope_t *scope, zend_object *scope_provider, int32_t priority); typedef void (*zend_async_suspend_t)(bool from_main); typedef void (*zend_async_enqueue_coroutine_t)(zend_coroutine_t *coroutine); -typedef void (*zend_async_resume_t)(zend_coroutine_t *coroutine, zend_object * error, const bool transfer_error); -typedef void (*zend_async_cancel_t)(zend_coroutine_t *coroutine, zend_object * error, bool transfer_error, const bool is_safely); -typedef bool (*zend_async_spawn_and_throw_t)(zend_object *exception, zend_async_scope_t *scope, int32_t priority); +typedef void (*zend_async_resume_t)( + zend_coroutine_t *coroutine, zend_object *error, const bool transfer_error); +typedef void (*zend_async_cancel_t)( + zend_coroutine_t *coroutine, zend_object *error, bool transfer_error, const bool is_safely); +typedef bool (*zend_async_spawn_and_throw_t)( + zend_object *exception, zend_async_scope_t *scope, int32_t priority); typedef void (*zend_async_shutdown_t)(void); typedef void (*zend_async_engine_shutdown_t)(void); -typedef zend_array* (*zend_async_get_coroutines_t)(void); +typedef zend_array *(*zend_async_get_coroutines_t)(void); typedef void (*zend_async_add_microtask_t)(zend_async_microtask_t *microtask); -typedef zend_array* (*zend_async_get_awaiting_info_t)(zend_coroutine_t * coroutine); -typedef zend_class_entry* (*zend_async_get_class_ce_t)(zend_async_class type); -typedef zend_future_t* (*zend_async_new_future_t)(bool thread_safe, size_t extra_size); -typedef zend_async_channel_t* (*zend_async_new_channel_t)(size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size); +typedef zend_array *(*zend_async_get_awaiting_info_t)(zend_coroutine_t *coroutine); +typedef zend_class_entry *(*zend_async_get_class_ce_t)(zend_async_class type); +typedef zend_future_t *(*zend_async_new_future_t)(bool thread_safe, size_t extra_size); +typedef zend_async_channel_t *(*zend_async_new_channel_t)( + size_t buffer_size, bool resizable, bool thread_safe, size_t extra_size); -typedef zend_async_group_t* (*zend_async_new_group_t)(size_t extra_size); +typedef zend_async_group_t *(*zend_async_new_group_t)(size_t extra_size); -typedef zend_object* (*zend_async_new_future_obj_t)(zend_future_t *future); -typedef zend_object* (*zend_async_new_channel_obj_t)(zend_async_channel_t *channel); +typedef zend_object *(*zend_async_new_future_obj_t)(zend_future_t *future); +typedef zend_object *(*zend_async_new_channel_obj_t)(zend_async_channel_t *channel); typedef void (*zend_async_reactor_startup_t)(void); typedef void (*zend_async_reactor_shutdown_t)(void); typedef bool (*zend_async_reactor_execute_t)(bool no_wait); typedef bool (*zend_async_reactor_loop_alive_t)(void); -typedef zend_async_poll_event_t* (*zend_async_new_socket_event_t)( - zend_socket_t socket, async_poll_event events, size_t extra_size -); -typedef zend_async_poll_event_t* (*zend_async_new_poll_event_t)( - zend_file_descriptor_t fh, zend_socket_t socket, async_poll_event events, size_t extra_size -); -typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)( - const zend_ulong timeout, const zend_ulong nanoseconds, const bool is_periodic, size_t extra_size -); -typedef zend_async_signal_event_t* (*zend_async_new_signal_event_t)(int signum, size_t extra_size); -typedef zend_async_process_event_t* (*zend_async_new_process_event_t)(zend_process_t process_handle, size_t extra_size); +typedef zend_async_poll_event_t *(*zend_async_new_socket_event_t)( + zend_socket_t socket, async_poll_event events, size_t extra_size); +typedef zend_async_poll_event_t *(*zend_async_new_poll_event_t)(zend_file_descriptor_t fh, + zend_socket_t socket, async_poll_event events, size_t extra_size); +typedef zend_async_timer_event_t *(*zend_async_new_timer_event_t)(const zend_ulong timeout, + const zend_ulong nanoseconds, const bool is_periodic, size_t extra_size); +typedef zend_async_signal_event_t *(*zend_async_new_signal_event_t)(int signum, size_t extra_size); +typedef zend_async_process_event_t *(*zend_async_new_process_event_t)( + zend_process_t process_handle, size_t extra_size); typedef void (*zend_async_thread_entry_t)(void *arg, size_t extra_size); -typedef zend_async_thread_event_t* (*zend_async_new_thread_event_t)( - zend_async_thread_entry_t entry, void *arg, size_t extra_size -); +typedef zend_async_thread_event_t *(*zend_async_new_thread_event_t)( + zend_async_thread_entry_t entry, void *arg, size_t extra_size); typedef void (*zend_async_trigger_event_trigger_fn)(zend_async_trigger_event_t *event); -typedef zend_async_trigger_event_t* (*zend_async_new_trigger_event_t)(size_t extra_size); -typedef zend_async_filesystem_event_t* (*zend_async_new_filesystem_event_t)( - zend_string * path, const unsigned int flags, size_t extra_size -); - -typedef zend_async_dns_nameinfo_t* (*zend_async_getnameinfo_t)(const struct sockaddr* addr, int flags, size_t extra_size); -typedef zend_async_dns_addrinfo_t* (*zend_async_getaddrinfo_t)( - const char *node, const char *service, const struct addrinfo *hints, size_t extra_size -); +typedef zend_async_trigger_event_t *(*zend_async_new_trigger_event_t)(size_t extra_size); +typedef zend_async_filesystem_event_t *(*zend_async_new_filesystem_event_t)( + zend_string *path, const unsigned int flags, size_t extra_size); + +typedef zend_async_dns_nameinfo_t *(*zend_async_getnameinfo_t)( + const struct sockaddr *addr, int flags, size_t extra_size); +typedef zend_async_dns_addrinfo_t *(*zend_async_getaddrinfo_t)( + const char *node, const char *service, const struct addrinfo *hints, size_t extra_size); typedef void (*zend_async_freeaddrinfo_t)(struct addrinfo *ai); -typedef zend_async_exec_event_t* (*zend_async_new_exec_event_t) ( - zend_async_exec_mode exec_mode, - const char *cmd, - zval *return_buffer, - zval *return_value, - zval *std_error, - const char *cwd, - const char *env, - size_t extra_size -); +typedef zend_async_exec_event_t *(*zend_async_new_exec_event_t)(zend_async_exec_mode exec_mode, + const char *cmd, zval *return_buffer, zval *return_value, zval *std_error, const char *cwd, + const char *env, size_t extra_size); -typedef zend_async_listen_event_t* (*zend_async_socket_listen_t)( - const char *host, int port, int backlog, size_t extra_size -); +typedef zend_async_listen_event_t *(*zend_async_socket_listen_t)( + const char *host, int port, int backlog, size_t extra_size); typedef int (*zend_async_listen_get_local_address_t)( - zend_async_listen_event_t *listen_event, - char *host, size_t host_len, - int *port -); + zend_async_listen_event_t *listen_event, char *host, size_t host_len, int *port); -typedef int (* zend_async_exec_t) ( - zend_async_exec_mode exec_mode, - const char *cmd, - zval *return_buffer, - zval *return_value, - zval *std_error, - const char *cwd, - const char *env, - const zend_ulong timeout -); +typedef int (*zend_async_exec_t)(zend_async_exec_mode exec_mode, const char *cmd, + zval *return_buffer, zval *return_value, zval *std_error, const char *cwd, const char *env, + const zend_ulong timeout); typedef void (*zend_async_queue_task_t)(zend_async_task_t *task); @@ -307,14 +286,14 @@ struct _zend_fcall_s { /////////////////////////////////////////////////////////////////// struct _zend_coroutine_switch_handler_s { - zend_coroutine_switch_handler_fn handler; /* Handler function pointer */ + zend_coroutine_switch_handler_fn handler; /* Handler function pointer */ }; struct _zend_coroutine_switch_handlers_vector_s { - uint32_t length; /* Number of handlers */ - uint32_t capacity; /* Allocated capacity */ - zend_coroutine_switch_handler_t *data; /* Array of handlers */ - bool in_execution; /* Protection flag during execution */ + uint32_t length; /* Number of handlers */ + uint32_t capacity; /* Allocated capacity */ + zend_coroutine_switch_handler_t *data; /* Array of handlers */ + bool in_execution; /* Protection flag during execution */ }; struct _zend_async_microtask_s { @@ -350,9 +329,9 @@ struct _zend_async_microtask_s { typedef void (*zend_async_iterator_method_t)(zend_async_iterator_t *iterator); -#define ZEND_ASYNC_ITERATOR_FIELDS \ - zend_async_microtask_t microtask; \ - zend_async_scope_t *scope; \ +#define ZEND_ASYNC_ITERATOR_FIELDS \ + zend_async_microtask_t microtask; \ + zend_async_scope_t *scope; \ /* NULLABLE. Custom data for the iterator, can be used to store additional information. */ \ void *extended_data; \ /* NULLABLE. An additional destructor that will be called. */ \ @@ -360,30 +339,25 @@ typedef void (*zend_async_iterator_method_t)(zend_async_iterator_t *iterator); /* A method that starts the iterator in the current coroutine. */ \ zend_async_iterator_method_t run; \ /* A method that starts the iterator in a separate coroutine with the specified priority. */ \ - void (*run_in_coroutine)(zend_async_iterator_t *iterator, int32_t priority); \ + void (*run_in_coroutine)(zend_async_iterator_t * iterator, int32_t priority); \ /* The maximum number of concurrent tasks that can be executed at the same time */ \ unsigned int concurrency; \ /* Priority for coroutines created by this iterator */ \ int32_t priority; \ /* NULLABLE. Exception that stopped the iterator */ \ - zend_object * exception; + zend_object *exception; struct _zend_async_iterator_s { ZEND_ASYNC_ITERATOR_FIELDS }; -typedef zend_result (*zend_async_iterator_handler_t)(zend_async_iterator_t *iterator, zval *current, zval *key); - -typedef zend_async_iterator_t* (*zend_async_new_iterator_t)( - zval *array, - zend_object_iterator *zend_iterator, - zend_fcall_t *fcall, - zend_async_iterator_handler_t handler, - zend_async_scope_t *scope, - unsigned int concurrency, - int32_t priority, - size_t iterator_size -); +typedef zend_result (*zend_async_iterator_handler_t)( + zend_async_iterator_t *iterator, zval *current, zval *key); + +typedef zend_async_iterator_t *(*zend_async_new_iterator_t)(zval *array, + zend_object_iterator *zend_iterator, zend_fcall_t *fcall, + zend_async_iterator_handler_t handler, zend_async_scope_t *scope, unsigned int concurrency, + int32_t priority, size_t iterator_size); /////////////////////////////////////////////////////////////////// /// Event Structures @@ -408,14 +382,14 @@ struct _zend_async_event_callback_s { // you **MUST** use either this macro or ZEND_ASYNC_EVENT_CALLBACK_RELEASE. // #define ZEND_ASYNC_EVENT_CALLBACK_DEC_REF(callback) \ - if(callback != NULL && callback->ref_count > 1) { \ + if (callback != NULL && callback->ref_count > 1) { \ callback->ref_count--; \ } #define ZEND_ASYNC_EVENT_CALLBACK_RELEASE(callback) \ if ((callback) != NULL && (callback)->ref_count > 1) { \ (callback)->ref_count--; \ - } else if((callback)->dispose != NULL) { \ + } else if ((callback)->dispose != NULL) { \ (callback)->dispose((callback), NULL); \ } else { \ coroutine_event_callback_dispose((callback), NULL); \ @@ -427,27 +401,27 @@ struct _zend_coroutine_event_callback_s { }; struct _zend_async_waker_trigger_s { - uint32_t length; /* current number of callbacks */ - uint32_t capacity; /* allocated slots in the array */ - zend_async_event_t *event; - zend_async_event_callback_t *data[1]; /* flexible array member */ + uint32_t length; /* current number of callbacks */ + uint32_t capacity; /* allocated slots in the array */ + zend_async_event_t *event; + zend_async_event_callback_t *data[1]; /* flexible array member */ }; /* Dynamic array of async event callbacks with single iterator protection */ typedef struct _zend_async_callbacks_vector_s { - uint32_t length; /* current number of callbacks */ - uint32_t capacity; /* allocated slots in the array */ - zend_async_event_callback_t **data; /* dynamically allocated callback array */ - + uint32_t length; /* current number of callbacks */ + uint32_t capacity; /* allocated slots in the array */ + zend_async_event_callback_t **data; /* dynamically allocated callback array */ + /* Single iterator tracking - NULL means no active iteration */ - uint32_t *current_iterator; /* pointer to active iterator index */ + uint32_t *current_iterator; /* pointer to active iterator index */ } zend_async_callbacks_vector_t; /** * Basic structure for representing events. - * An event can be either an internal C object or a Zend object implementing the Awaitable interface. - * In that case, the ZEND_ASYNC_EVENT_F_ZEND_OBJ flag is set to TRUE, - * and the zend_object_offset field points to the offset of the zend_object structure. + * An event can be either an internal C object or a Zend object implementing the Awaitable + * interface. In that case, the ZEND_ASYNC_EVENT_F_ZEND_OBJ flag is set to TRUE, and the + * zend_object_offset field points to the offset of the zend_object structure. * * To manage the reference counter, use the macros: * ZEND_ASYNC_EVENT_ADD_REF, ZEND_ASYNC_EVENT_DEL_REF, ZEND_ASYNC_EVENT_RELEASE. @@ -458,8 +432,7 @@ struct _zend_async_event_s { uint32_t flags; /* Offset to the beginning of additional data associated with the event (used for extensions) */ uint32_t extra_offset; - union - { + union { /* The refcount of the event. */ uint32_t ref_count; /* The offset of Zend object structure. */ @@ -476,8 +449,9 @@ struct _zend_async_event_s { zend_async_event_stop_t stop; /* * Replay method. Nullable. - * This method is implemented only by those events that can provide a result again, even after they have completed. - * For example, this method is relevant for coroutines and futures, which can provide the result again and again. + * This method is implemented only by those events that can provide a result again, even after + * they have completed. For example, this method is relevant for coroutines and futures, which + * can provide the result again and again. */ zend_async_event_replay_t replay; zend_async_event_dispose_t dispose; @@ -491,9 +465,10 @@ struct _zend_async_event_s { }; /** - * Event reference. A special data structure that allows representing an object with the Awaitable interface, - * but which does not store the event directly—instead, it holds only a reference to it. - * This is necessary for events that are destroyed asynchronously and therefore cannot be used as Zend objects. + * Event reference. A special data structure that allows representing an object with the Awaitable + * interface, but which does not store the event directly—instead, it holds only a reference to it. + * This is necessary for events that are destroyed asynchronously and therefore cannot be used as + * Zend objects. * * For example, events like Timer, Poll, and Signal cannot be Zend objects * because their destruction cycle does not align. @@ -502,129 +477,144 @@ struct _zend_async_event_s { * * zend_object_offset is the offset of the Zend object structure. * * event is a pointer to the zend_async_event_t structure. */ -#define ZEND_ASYNC_EVENT_REF_PROLOG \ - uint32_t flags; \ +#define ZEND_ASYNC_EVENT_REF_PROLOG \ + uint32_t flags; \ uint32_t zend_object_offset; -#define ZEND_ASYNC_EVENT_REF_FIELDS \ - uint32_t flags; \ - uint32_t zend_object_offset; \ +#define ZEND_ASYNC_EVENT_REF_FIELDS \ + uint32_t flags; \ + uint32_t zend_object_offset; \ zend_async_event_t *event; typedef struct { ZEND_ASYNC_EVENT_REF_FIELDS } zend_async_event_ref_t; -#define ZEND_ASYNC_EVENT_F_CLOSED (1u << 0) /* event was closed */ -#define ZEND_ASYNC_EVENT_F_RESULT_USED (1u << 1) /* result will be used in exception handler */ -#define ZEND_ASYNC_EVENT_F_EXC_CAUGHT (1u << 2) /* error was caught in exception handler */ +#define ZEND_ASYNC_EVENT_F_CLOSED (1u << 0) /* event was closed */ +#define ZEND_ASYNC_EVENT_F_RESULT_USED (1u << 1) /* result will be used in exception handler */ +#define ZEND_ASYNC_EVENT_F_EXC_CAUGHT (1u << 2) /* error was caught in exception handler */ /* Indicates that the event produces a ZVAL pointer during the callback. */ -#define ZEND_ASYNC_EVENT_F_ZVAL_RESULT (1u << 3) -#define ZEND_ASYNC_EVENT_F_ZEND_OBJ (1u << 4) /* event is a zend object */ -#define ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY (1u << 5) /* event will not free memory in dispose handler */ -#define ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED (1u << 6) /* exception has been caught and processed */ +#define ZEND_ASYNC_EVENT_F_ZVAL_RESULT (1u << 3) +#define ZEND_ASYNC_EVENT_F_ZEND_OBJ (1u << 4) /* event is a zend object */ +#define ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY \ + (1u << 5) /* event will not free memory in dispose handler */ +#define ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED (1u << 6) /* exception has been caught and processed \ + */ -#define ZEND_ASYNC_EVENT_F_REFERENCE (1u << 7) /* event is a reference structure */ +#define ZEND_ASYNC_EVENT_F_REFERENCE (1u << 7) /* event is a reference structure */ // Flag indicating that the event has a zend_object reference by extra_offset. -#define ZEND_ASYNC_EVENT_F_OBJ_REF (1u << 8) /* has zend_object ref */ +#define ZEND_ASYNC_EVENT_F_OBJ_REF (1u << 8) /* has zend_object ref */ -#define ZEND_ASYNC_EVENT_REFERENCE_PREFIX ((uint32_t)0x80) /* prefix for reference structures */ +#define ZEND_ASYNC_EVENT_REFERENCE_PREFIX ((uint32_t) 0x80) /* prefix for reference structures */ // Create a reference to an event with the given offset and event pointer. -#define ZEND_ASYNC_EVENT_REF_SET(ptr, offset, ev) do { \ - (ptr)->flags = ZEND_ASYNC_EVENT_REFERENCE_PREFIX; \ - (ptr)->zend_object_offset = (offset); \ - (ptr)->event = (ev); \ +#define ZEND_ASYNC_EVENT_REF_SET(ptr, offset, ev) \ + do { \ + (ptr)->flags = ZEND_ASYNC_EVENT_REFERENCE_PREFIX; \ + (ptr)->zend_object_offset = (offset); \ + (ptr)->event = (ev); \ } while (0) -#define ZEND_ASYNC_EVENT_IS_CLOSED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) -#define ZEND_ASYNC_EVENT_WILL_RESULT_USED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_RESULT_USED) != 0) -#define ZEND_ASYNC_EVENT_WILL_EXC_CAUGHT(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_EXC_CAUGHT) != 0) -#define ZEND_ASYNC_EVENT_WILL_ZVAL_RESULT(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_ZVAL_RESULT) != 0) -#define ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_ZEND_OBJ) != 0) -#define ZEND_ASYNC_EVENT_IS_NO_FREE_MEMORY(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY) != 0) -#define ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) != 0) +#define ZEND_ASYNC_EVENT_IS_CLOSED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) +#define ZEND_ASYNC_EVENT_WILL_RESULT_USED(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_RESULT_USED) != 0) +#define ZEND_ASYNC_EVENT_WILL_EXC_CAUGHT(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_EXC_CAUGHT) != 0) +#define ZEND_ASYNC_EVENT_WILL_ZVAL_RESULT(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_ZVAL_RESULT) != 0) +#define ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) (((ev)->flags & ZEND_ASYNC_EVENT_F_ZEND_OBJ) != 0) +#define ZEND_ASYNC_EVENT_IS_NO_FREE_MEMORY(ev) \ + (((ev)->flags & ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY) != 0) +#define ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED(ev) \ + (((ev)->flags & ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) != 0) -#define ZEND_ASYNC_EVENT_SET_CLOSED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_CLOSED) -#define ZEND_ASYNC_EVENT_CLR_CLOSED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_CLOSED) +#define ZEND_ASYNC_EVENT_SET_CLOSED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_CLOSED) +#define ZEND_ASYNC_EVENT_CLR_CLOSED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_CLOSED) -#define ZEND_ASYNC_EVENT_SET_RESULT_USED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_RESULT_USED) -#define ZEND_ASYNC_EVENT_CLR_RESULT_USED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_RESULT_USED) +#define ZEND_ASYNC_EVENT_SET_RESULT_USED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_RESULT_USED) +#define ZEND_ASYNC_EVENT_CLR_RESULT_USED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_RESULT_USED) -#define ZEND_ASYNC_EVENT_SET_EXC_CAUGHT(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_EXC_CAUGHT) -#define ZEND_ASYNC_EVENT_CLR_EXC_CAUGHT(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_EXC_CAUGHT) +#define ZEND_ASYNC_EVENT_SET_EXC_CAUGHT(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_EXC_CAUGHT) +#define ZEND_ASYNC_EVENT_CLR_EXC_CAUGHT(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_EXC_CAUGHT) -#define ZEND_ASYNC_EVENT_SET_ZVAL_RESULT(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_ZVAL_RESULT) -#define ZEND_ASYNC_EVENT_CLR_ZVAL_RESULT(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_ZVAL_RESULT) +#define ZEND_ASYNC_EVENT_SET_ZVAL_RESULT(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_ZVAL_RESULT) +#define ZEND_ASYNC_EVENT_CLR_ZVAL_RESULT(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_ZVAL_RESULT) -#define ZEND_ASYNC_EVENT_SET_ZEND_OBJ(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_ZEND_OBJ) -#define ZEND_ASYNC_EVENT_SET_ZEND_OBJ_OFFSET(ev, offset) ((ev)->zend_object_offset = (unsigned int) (offset)) +#define ZEND_ASYNC_EVENT_SET_ZEND_OBJ(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_ZEND_OBJ) +#define ZEND_ASYNC_EVENT_SET_ZEND_OBJ_OFFSET(ev, offset) \ + ((ev)->zend_object_offset = (unsigned int) (offset)) -#define ZEND_ASYNC_EVENT_SET_NO_FREE_MEMORY(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY) +#define ZEND_ASYNC_EVENT_SET_NO_FREE_MEMORY(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY) -#define ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) -#define ZEND_ASYNC_EVENT_CLR_EXCEPTION_HANDLED(ev) ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) +#define ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(ev) \ + ((ev)->flags |= ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) +#define ZEND_ASYNC_EVENT_CLR_EXCEPTION_HANDLED(ev) \ + ((ev)->flags &= ~ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED) -#define ZEND_ASYNC_EVENT_WITH_OBJECT_REF(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_OBJ_REF) +#define ZEND_ASYNC_EVENT_WITH_OBJECT_REF(ev) ((ev)->flags |= ZEND_ASYNC_EVENT_F_OBJ_REF) // Convert awaitable Zend object to zend_async_event_t pointer -#define ZEND_ASYNC_EVENT_IS_REFERENCE(ptr) (*((const uint32_t *)(ptr)) == ZEND_ASYNC_EVENT_REFERENCE_PREFIX) -#define ZEND_ASYNC_OBJECT_TO_EVENT(obj) \ - ( \ - ZEND_ASYNC_EVENT_IS_REFERENCE((void *)((char *)(obj) - (obj)->handlers->offset)) \ - ? ((zend_async_event_ref_t *)((char *)(obj) - (obj)->handlers->offset))->event \ - : (zend_async_event_t *)((char *)(obj) - (obj)->handlers->offset) \ - ) +#define ZEND_ASYNC_EVENT_IS_REFERENCE(ptr) \ + (*((const uint32_t *) (ptr)) == ZEND_ASYNC_EVENT_REFERENCE_PREFIX) +#define ZEND_ASYNC_OBJECT_TO_EVENT(obj) \ + (ZEND_ASYNC_EVENT_IS_REFERENCE((void *) ((char *) (obj) - (obj)->handlers->offset)) \ + ? ((zend_async_event_ref_t *) ((char *) (obj) - (obj)->handlers->offset)) \ + ->event \ + : (zend_async_event_t *) ((char *) (obj) - (obj)->handlers->offset)) // Convert zend_async_event_t to zend_object pointer -#define ZEND_ASYNC_EVENT_TO_OBJECT(ev) \ - (((ev)->flags & ZEND_ASYNC_EVENT_F_OBJ_REF) \ - ? *(zend_object **)((char *)(ev) + (ev)->extra_offset) \ - : (zend_object *)((char *)(ev) + (ev)->zend_object_offset) ) +#define ZEND_ASYNC_EVENT_TO_OBJECT(ev) \ + (((ev)->flags & ZEND_ASYNC_EVENT_F_OBJ_REF) \ + ? *(zend_object **) ((char *) (ev) + (ev)->extra_offset) \ + : (zend_object *) ((char *) (ev) + (ev)->zend_object_offset)) // Get refcount of the event object -#define ZEND_ASYNC_EVENT_REF(ev) (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? \ - GC_REFCOUNT(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) : \ - (ev)->ref_count) +#define ZEND_ASYNC_EVENT_REF(ev) \ + (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? GC_REFCOUNT(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) \ + : (ev)->ref_count) // Proper increment of the event object's reference count. -#define ZEND_ASYNC_EVENT_ADD_REF(ev) (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? \ - GC_ADDREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) : \ - ++(ev)->ref_count) +#define ZEND_ASYNC_EVENT_ADD_REF(ev) \ + (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? GC_ADDREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) \ + : ++(ev)->ref_count) // Proper decrement of the event object's reference count. -#define ZEND_ASYNC_EVENT_DEL_REF(ev) (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? \ - GC_DELREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) : \ - --(ev)->ref_count) +#define ZEND_ASYNC_EVENT_DEL_REF(ev) \ + (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev) ? GC_DELREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) \ + : --(ev)->ref_count) /* Properly release the event object */ -#define ZEND_ASYNC_EVENT_RELEASE(ev) do { \ - if (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev)) { \ - if(GC_REFCOUNT(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) == 1) { \ - OBJ_RELEASE(ZEND_ASYNC_EVENT_TO_OBJECT(ev)); \ - } else { \ - GC_DELREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)); \ - } \ - } else { \ - if ((ev)->ref_count == 1) { \ - (ev)->ref_count = 0; \ - (ev)->dispose(ev); \ +#define ZEND_ASYNC_EVENT_RELEASE(ev) \ + do { \ + if (ZEND_ASYNC_EVENT_IS_ZEND_OBJ(ev)) { \ + if (GC_REFCOUNT(ZEND_ASYNC_EVENT_TO_OBJECT(ev)) == 1) { \ + OBJ_RELEASE(ZEND_ASYNC_EVENT_TO_OBJECT(ev)); \ + } else { \ + GC_DELREF(ZEND_ASYNC_EVENT_TO_OBJECT(ev)); \ + } \ } else { \ - (ev)->ref_count--; \ + if ((ev)->ref_count == 1) { \ + (ev)->ref_count = 0; \ + (ev)->dispose(ev); \ + } else { \ + (ev)->ref_count--; \ + } \ } \ - } \ -} while (0) + } while (0) -#define ZEND_ASYNC_EVENT_REPLAY(ev, callback) (ev->replay != NULL ? ev->replay(ev, callback, NULL, NULL) : false) -#define ZEND_ASYNC_EVENT_EXTRACT_RESULT(ev, result) (ev->replay != NULL ? ev->replay(ev, NULL, result, NULL) : false) -#define ZEND_ASYNC_EVENT_EXTRACT_RESULT_OR_ERROR(ev, result, exception) (ev->replay != NULL ? ev->replay(ev, NULL, result, exception) : false) +#define ZEND_ASYNC_EVENT_REPLAY(ev, callback) \ + (ev->replay != NULL ? ev->replay(ev, callback, NULL, NULL) : false) +#define ZEND_ASYNC_EVENT_EXTRACT_RESULT(ev, result) \ + (ev->replay != NULL ? ev->replay(ev, NULL, result, NULL) : false) +#define ZEND_ASYNC_EVENT_EXTRACT_RESULT_OR_ERROR(ev, result, exception) \ + (ev->replay != NULL ? ev->replay(ev, NULL, result, exception) : false) /* Public callback vector functions - implementations in zend_async_API.c */ -ZEND_API void zend_async_callbacks_notify(zend_async_event_t *event, void *result, zend_object *exception, bool from_handler); -ZEND_API void zend_async_callbacks_remove(zend_async_event_t *event, zend_async_event_callback_t *callback); +ZEND_API void zend_async_callbacks_notify( + zend_async_event_t *event, void *result, zend_object *exception, bool from_handler); +ZEND_API void zend_async_callbacks_remove( + zend_async_event_t *event, zend_async_event_callback_t *callback); ZEND_API void zend_async_callbacks_free(zend_async_event_t *event); -ZEND_API void zend_async_callbacks_notify_and_close(zend_async_event_t *event, void *result, zend_object *exception); +ZEND_API void zend_async_callbacks_notify_and_close( + zend_async_event_t *event, void *result, zend_object *exception); #define ZEND_ASYNC_CALLBACKS_NOTIFY(event, result, exception) \ zend_async_callbacks_notify((event), (result), (exception), false) @@ -635,13 +625,13 @@ ZEND_API void zend_async_callbacks_notify_and_close(zend_async_event_t *event, v #define ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER(event, result, exception) \ zend_async_callbacks_notify((event), (result), (exception), true) - /* Append a callback; grows the buffer when needed */ -static zend_always_inline void -zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t *callback) +static zend_always_inline void zend_async_callbacks_push( + zend_async_event_t *event, zend_async_event_callback_t *callback) { if (event->callbacks.data == NULL) { - event->callbacks.data = (zend_async_event_callback_t **)safe_emalloc(4, sizeof(zend_async_event_callback_t *), 0); + event->callbacks.data = (zend_async_event_callback_t **) safe_emalloc( + 4, sizeof(zend_async_event_callback_t *), 0); event->callbacks.capacity = 4; } @@ -649,10 +639,8 @@ zend_async_callbacks_push(zend_async_event_t *event, zend_async_event_callback_t if (vector->length == vector->capacity) { vector->capacity = vector->capacity ? vector->capacity * 2 : 4; - vector->data = (zend_async_event_callback_t **)safe_erealloc(vector->data, - vector->capacity, - sizeof(zend_async_event_callback_t *), - 0); + vector->data = (zend_async_event_callback_t **) safe_erealloc( + vector->data, vector->capacity, sizeof(zend_async_event_callback_t *), 0); } vector->data[vector->length++] = callback; @@ -712,7 +700,8 @@ struct _zend_async_dns_addrinfo_s { zend_async_event_t base; const char *node; const char *service; - /* The DNS resolution result must be explicitly and mandatorily freed using the ZEND_ASYNC_FREEADDRINFO method! */ + /* The DNS resolution result must be explicitly and mandatorily freed using the + * ZEND_ASYNC_FREEADDRINFO method! */ struct addrinfo *result; }; @@ -720,14 +709,14 @@ struct _zend_async_exec_event_s { zend_async_event_t base; zend_async_exec_mode exec_mode; bool terminated; - char * cmd; - zval * return_value; - zval * result_buffer; + char *cmd; + zval *return_value; + zval *result_buffer; size_t output_len; - char * output_buffer; + char *output_buffer; zend_long exit_code; int term_signal; - zval * std_error; + zval *std_error; }; struct _zend_async_listen_event_s { @@ -753,14 +742,16 @@ struct _zend_async_trigger_event_s { /// Scope Structures /////////////////////////////////////////////////////////////////// -typedef void (*zend_async_before_coroutine_enqueue_t)(zend_coroutine_t *coroutine, zend_async_scope_t *scope, zval *result); -typedef void (*zend_async_after_coroutine_enqueue_t)(zend_coroutine_t *coroutine, zend_async_scope_t *scope); +typedef void (*zend_async_before_coroutine_enqueue_t)( + zend_coroutine_t *coroutine, zend_async_scope_t *scope, zval *result); +typedef void (*zend_async_after_coroutine_enqueue_t)( + zend_coroutine_t *coroutine, zend_async_scope_t *scope); /* Dynamic array of async event callbacks */ typedef struct _zend_async_scopes_vector_s { - uint32_t length; /* current number of items */ - uint32_t capacity; /* allocated slots in the array */ - zend_async_scope_t **data; /* dynamically allocated array */ + uint32_t length; /* current number of items */ + uint32_t capacity; /* allocated slots in the array */ + zend_async_scope_t **data; /* dynamically allocated array */ } zend_async_scopes_vector_t; /** @@ -795,81 +786,90 @@ struct _zend_async_scope_s { /** * The method handles an exception delivered to the Scope. * Its result may either be the cancellation of the Scope or the suppression of the exception. - * If the is_cancellation parameter is FALSE, it indicates an attempt to handle an exception from a coroutine. - * Otherwise, it's an attempt by the user to stop the execution of the Scope. + * If the is_cancellation parameter is FALSE, it indicates an attempt to handle an exception + * from a coroutine. Otherwise, it's an attempt by the user to stop the execution of the Scope. * - * The method should return true if the exception was handled and the Scope can continue execution. + * The method should return true if the exception was handled and the Scope can continue + * execution. * - * This method is the central point of responsibility where the behavior in case of an error is determined. + * This method is the central point of responsibility where the behavior in case of an error is + * determined. */ - bool (*catch_or_cancel)( - zend_async_scope_t *scope, - zend_coroutine_t *coroutine, - zend_async_scope_t *from_scope, - zend_object *exception, - bool transfer_error, - const bool is_safely, - const bool is_cancellation - ); + bool (*catch_or_cancel)(zend_async_scope_t *scope, zend_coroutine_t *coroutine, + zend_async_scope_t *from_scope, zend_object *exception, bool transfer_error, + const bool is_safely, const bool is_cancellation); }; #define ZEND_ASYNC_SCOPE_CLOSE(scope, is_safely) \ ((scope)->catch_or_cancel((scope), NULL, NULL, NULL, false, (is_safely), true)) #define ZEND_ASYNC_SCOPE_CANCEL(scope, exception, transfer_error, is_safely) \ - ((scope)->catch_or_cancel((scope), NULL, NULL, (exception), (transfer_error), (is_safely), true)) + ((scope)->catch_or_cancel( \ + (scope), NULL, NULL, (exception), (transfer_error), (is_safely), true)) #define ZEND_ASYNC_SCOPE_CATCH(scope, coroutine, from_scope, exception, transfer_error, is_safely) \ - ((scope)->catch_or_cancel((scope), (coroutine), (from_scope), (exception), (transfer_error), (is_safely), false)) - -#define ZEND_ASYNC_SCOPE_F_CLOSED ZEND_ASYNC_EVENT_F_CLOSED /* scope was closed */ -#define ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY /* scope will not free memory in dispose handler */ -#define ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY (1u << 14) /* scope will be disposed safely */ -#define ZEND_ASYNC_SCOPE_F_CANCELLED (1u << 15) /* scope was cancelled */ -#define ZEND_ASYNC_SCOPE_F_DISPOSING (1u << 16) /* scope disposing */ - -#define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CLOSED) != 0) -#define ZEND_ASYNC_SCOPE_IS_NO_FREE_MEMORY(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) != 0) -#define ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) != 0) -#define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CANCELLED) != 0) -#define ZEND_ASYNC_SCOPE_IS_DISPOSING(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSING) != 0) - -#define ZEND_ASYNC_SCOPE_SET_CLOSED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CLOSED) -#define ZEND_ASYNC_SCOPE_CLR_CLOSED(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_CLOSED) - -#define ZEND_ASYNC_SCOPE_SET_NO_FREE_MEMORY(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) -#define ZEND_ASYNC_SCOPE_CLR_NO_FREE_MEMORY(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) - -#define ZEND_ASYNC_SCOPE_SET_DISPOSE_SAFELY(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) -#define ZEND_ASYNC_SCOPE_CLR_DISPOSE_SAFELY(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) - -#define ZEND_ASYNC_SCOPE_SET_CANCELLED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CANCELLED) - -#define ZEND_ASYNC_SCOPE_SET_DISPOSING(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_DISPOSING) -#define ZEND_ASYNC_SCOPE_CLR_DISPOSING(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_DISPOSING) - -static zend_always_inline void -zend_async_scope_add_child(zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) + ((scope)->catch_or_cancel((scope), (coroutine), (from_scope), (exception), (transfer_error), \ + (is_safely), false)) + +#define ZEND_ASYNC_SCOPE_F_CLOSED ZEND_ASYNC_EVENT_F_CLOSED /* scope was closed */ +#define ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY \ + ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY /* scope will not free memory in dispose handler */ +#define ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY (1u << 14) /* scope will be disposed safely */ +#define ZEND_ASYNC_SCOPE_F_CANCELLED (1u << 15) /* scope was cancelled */ +#define ZEND_ASYNC_SCOPE_F_DISPOSING (1u << 16) /* scope disposing */ + +#define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CLOSED) != 0) +#define ZEND_ASYNC_SCOPE_IS_NO_FREE_MEMORY(scope) \ + (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) != 0) +#define ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(scope) \ + (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) != 0) +#define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) \ + (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_CANCELLED) != 0) +#define ZEND_ASYNC_SCOPE_IS_DISPOSING(scope) \ + (((scope)->event.flags & ZEND_ASYNC_SCOPE_F_DISPOSING) != 0) + +#define ZEND_ASYNC_SCOPE_SET_CLOSED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CLOSED) +#define ZEND_ASYNC_SCOPE_CLR_CLOSED(scope) ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_CLOSED) + +#define ZEND_ASYNC_SCOPE_SET_NO_FREE_MEMORY(scope) \ + ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) +#define ZEND_ASYNC_SCOPE_CLR_NO_FREE_MEMORY(scope) \ + ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_NO_FREE_MEMORY) + +#define ZEND_ASYNC_SCOPE_SET_DISPOSE_SAFELY(scope) \ + ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) +#define ZEND_ASYNC_SCOPE_CLR_DISPOSE_SAFELY(scope) \ + ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_DISPOSE_SAFELY) + +#define ZEND_ASYNC_SCOPE_SET_CANCELLED(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_CANCELLED) + +#define ZEND_ASYNC_SCOPE_SET_DISPOSING(scope) ((scope)->event.flags |= ZEND_ASYNC_SCOPE_F_DISPOSING) +#define ZEND_ASYNC_SCOPE_CLR_DISPOSING(scope) \ + ((scope)->event.flags &= ~ZEND_ASYNC_SCOPE_F_DISPOSING) + +static zend_always_inline void zend_async_scope_add_child( + zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) { zend_async_scopes_vector_t *vector = &parent_scope->scopes; child_scope->parent_scope = parent_scope; if (vector->data == NULL) { - vector->data = (zend_async_scope_t **)safe_emalloc(4, sizeof(zend_async_scope_t *), 0); + vector->data = (zend_async_scope_t **) safe_emalloc(4, sizeof(zend_async_scope_t *), 0); vector->capacity = 4; } if (vector->length == vector->capacity) { vector->capacity *= 2; - vector->data = (zend_async_scope_t **)safe_erealloc(vector->data, vector->capacity, sizeof(zend_async_scope_t *), 0); + vector->data = (zend_async_scope_t **) safe_erealloc( + vector->data, vector->capacity, sizeof(zend_async_scope_t *), 0); } vector->data[vector->length++] = child_scope; } -static zend_always_inline void -zend_async_scope_remove_child(zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) +static zend_always_inline void zend_async_scope_remove_child( + zend_async_scope_t *parent_scope, zend_async_scope_t *child_scope) { zend_async_scopes_vector_t *vector = &parent_scope->scopes; for (uint32_t i = 0; i < vector->length; ++i) { @@ -887,8 +887,7 @@ zend_async_scope_remove_child(zend_async_scope_t *parent_scope, zend_async_scope } } -static zend_always_inline void -zend_async_scope_free_children(zend_async_scope_t *parent_scope) +static zend_always_inline void zend_async_scope_free_children(zend_async_scope_t *parent_scope) { zend_async_scopes_vector_t *vector = &parent_scope->scopes; @@ -918,14 +917,18 @@ typedef enum { /** * Condition that is TRUE if the coroutine is in the queue */ -#define ZEND_ASYNC_WAKER_IN_QUEUE(waker) (waker != NULL && ((waker)->status == ZEND_ASYNC_WAKER_QUEUED || waker->status == ZEND_ASYNC_WAKER_IGNORED)) +#define ZEND_ASYNC_WAKER_IN_QUEUE(waker) \ + (waker != NULL \ + && ((waker)->status == ZEND_ASYNC_WAKER_QUEUED \ + || waker->status == ZEND_ASYNC_WAKER_IGNORED)) struct _zend_async_waker_s { /* The waker status. */ ZEND_ASYNC_WAKER_STATUS status; /* The array of zend_async_trigger_callback_t. */ HashTable events; - /* A list of events objects (zend_async_event_t) that occurred during the last iteration of the event loop. */ + /* A list of events objects (zend_async_event_t) that occurred during the last iteration of the + * event loop. */ HashTable *triggered_events; /* Result of the waker. */ zval result; @@ -995,7 +998,8 @@ struct _zend_coroutine_s { * The macro evaluates to TRUE if the coroutine is in a waiting state — * either waiting for events or waiting in the execution queue. */ -#define ZEND_COROUTINE_SUSPENDED(coroutine) ((coroutine)->waker != NULL && ZEND_ASYNC_WAKER_WAITING((coroutine)->waker)) +#define ZEND_COROUTINE_SUSPENDED(coroutine) \ + ((coroutine)->waker != NULL && ZEND_ASYNC_WAKER_WAITING((coroutine)->waker)) /* Coroutine flags */ #define ZEND_COROUTINE_F_STARTED (1u << 10) /* coroutine is started */ @@ -1004,24 +1008,37 @@ struct _zend_coroutine_s { #define ZEND_COROUTINE_F_PROTECTED (1u << 13) /* coroutine is protected */ #define ZEND_COROUTINE_F_MAIN (1u << 14) /* coroutine is a main coroutine */ -#define ZEND_COROUTINE_IS_ZOMBIE(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_ZOMBIE) != 0) +#define ZEND_COROUTINE_IS_ZOMBIE(coroutine) \ + (((coroutine)->event.flags & ZEND_COROUTINE_F_ZOMBIE) != 0) #define ZEND_COROUTINE_SET_ZOMBIE(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_ZOMBIE) -#define ZEND_COROUTINE_IS_STARTED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_STARTED) != 0) -#define ZEND_COROUTINE_IS_CANCELLED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_CANCELLED) != 0) -#define ZEND_COROUTINE_IS_FINISHED(coroutine) (((coroutine)->event.flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) -#define ZEND_COROUTINE_IS_PROTECTED(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_PROTECTED) != 0) -#define ZEND_COROUTINE_IS_EXCEPTION_HANDLED(coroutine) ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED(&(coroutine)->event) +#define ZEND_COROUTINE_IS_STARTED(coroutine) \ + (((coroutine)->event.flags & ZEND_COROUTINE_F_STARTED) != 0) +#define ZEND_COROUTINE_IS_CANCELLED(coroutine) \ + (((coroutine)->event.flags & ZEND_COROUTINE_F_CANCELLED) != 0) +#define ZEND_COROUTINE_IS_FINISHED(coroutine) \ + (((coroutine)->event.flags & ZEND_ASYNC_EVENT_F_CLOSED) != 0) +#define ZEND_COROUTINE_IS_PROTECTED(coroutine) \ + (((coroutine)->event.flags & ZEND_COROUTINE_F_PROTECTED) != 0) +#define ZEND_COROUTINE_IS_EXCEPTION_HANDLED(coroutine) \ + ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED(&(coroutine)->event) #define ZEND_COROUTINE_IS_MAIN(coroutine) (((coroutine)->event.flags & ZEND_COROUTINE_F_MAIN) != 0) #define ZEND_COROUTINE_SET_STARTED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_STARTED) -#define ZEND_COROUTINE_SET_CANCELLED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_CANCELLED) -#define ZEND_COROUTINE_SET_FINISHED(coroutine) ((coroutine)->event.flags |= ZEND_ASYNC_EVENT_F_CLOSED) -#define ZEND_COROUTINE_SET_PROTECTED(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_PROTECTED) +#define ZEND_COROUTINE_SET_CANCELLED(coroutine) \ + ((coroutine)->event.flags |= ZEND_COROUTINE_F_CANCELLED) +#define ZEND_COROUTINE_SET_FINISHED(coroutine) \ + ((coroutine)->event.flags |= ZEND_ASYNC_EVENT_F_CLOSED) +#define ZEND_COROUTINE_SET_PROTECTED(coroutine) \ + ((coroutine)->event.flags |= ZEND_COROUTINE_F_PROTECTED) #define ZEND_COROUTINE_SET_MAIN(coroutine) ((coroutine)->event.flags |= ZEND_COROUTINE_F_MAIN) -#define ZEND_COROUTINE_CLR_PROTECTED(coroutine) ((coroutine)->event.flags &= ~ZEND_COROUTINE_F_PROTECTED) -#define ZEND_COROUTINE_SET_EXCEPTION_HANDLED(coroutine) ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(&(coroutine)->event) -#define ZEND_COROUTINE_CLR_EXCEPTION_HANDLED(coroutine) ZEND_ASYNC_EVENT_CLR_EXCEPTION_HANDLED(&(coroutine)->event) - -static zend_always_inline zend_string *zend_coroutine_callable_name(const zend_coroutine_t *coroutine) +#define ZEND_COROUTINE_CLR_PROTECTED(coroutine) \ + ((coroutine)->event.flags &= ~ZEND_COROUTINE_F_PROTECTED) +#define ZEND_COROUTINE_SET_EXCEPTION_HANDLED(coroutine) \ + ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(&(coroutine)->event) +#define ZEND_COROUTINE_CLR_EXCEPTION_HANDLED(coroutine) \ + ZEND_ASYNC_EVENT_CLR_EXCEPTION_HANDLED(&(coroutine)->event) + +static zend_always_inline zend_string *zend_coroutine_callable_name( + const zend_coroutine_t *coroutine) { if (ZEND_COROUTINE_IS_MAIN(coroutine)) { return zend_string_init("main", sizeof("main") - 1, 0); @@ -1040,28 +1057,29 @@ static zend_always_inline zend_string *zend_coroutine_callable_name(const zend_c * Z_PARAM_VARIADIC_WITH_NAMED(args, args_count, named_args); */ #define ZEND_ASYNC_FCALL_DEFINE(fcall, fci, fcc, args, args_count, named_args) \ - zend_fcall_t * fcall = ecalloc(1, sizeof(zend_fcall_t)); \ - fcall->fci = fci; \ - fcall->fci_cache = fcc; \ - if (args_count) { \ - fcall->fci.param_count = args_count; \ - fcall->fci.params = safe_emalloc(args_count, sizeof(zval), 0); \ - for (uint32_t i = 0; i < args_count; i++) { \ - ZVAL_COPY(&fcall->fci.params[i], &args[i]); \ - } \ - } \ - if (named_args) { \ - fcall->fci.named_params = named_args; \ - GC_ADDREF(named_args); \ - } \ + zend_fcall_t *fcall = ecalloc(1, sizeof(zend_fcall_t)); \ + fcall->fci = fci; \ + fcall->fci_cache = fcc; \ + if (args_count) { \ + fcall->fci.param_count = args_count; \ + fcall->fci.params = safe_emalloc(args_count, sizeof(zval), 0); \ + for (uint32_t i = 0; i < args_count; i++) { \ + ZVAL_COPY(&fcall->fci.params[i], &args[i]); \ + } \ + } \ + if (named_args) { \ + fcall->fci.named_params = named_args; \ + GC_ADDREF(named_args); \ + } \ Z_TRY_ADDREF(fcall->fci.function_name); /////////////////////////////////////////////////////////////// /// Async Context Structures /////////////////////////////////////////////////////////////// -typedef zend_async_context_t * (*zend_async_new_context_t)(void); -typedef bool (*zend_async_context_find_t)(zend_async_context_t *context, zval *key, zval *result, bool include_parent); +typedef zend_async_context_t *(*zend_async_new_context_t)(void); +typedef bool (*zend_async_context_find_t)( + zend_async_context_t *context, zval *key, zval *result, bool include_parent); typedef void (*zend_async_context_set_t)(zend_async_context_t *context, zval *key, zval *value); typedef bool (*zend_async_context_unset_t)(zend_async_context_t *context, zval *key); typedef void (*zend_async_context_dispose_t)(zend_async_context_t *context); @@ -1086,9 +1104,9 @@ struct _zend_async_context_s { * It inherits from zend_async_event_t to participate in the event system. */ struct _zend_future_s { - zend_async_event_t event; /* Event inheritance (first member) */ - zval result; /* Result value */ - zend_object *exception; /* Exception object (NULL = no error) */ + zend_async_event_t event; /* Event inheritance (first member) */ + zval result; /* Result value */ + zend_object *exception; /* Exception object (NULL = no error) */ /* Created file and line number */ uint32_t lineno; uint32_t completed_lineno; @@ -1105,22 +1123,23 @@ struct _zend_future_s { #define ZEND_FUTURE_SET_THREAD_SAFE(future) ((future)->event.flags |= ZEND_FUTURE_F_THREAD_SAFE) #define ZEND_FUTURE_SET_IGNORED(future) ((future)->event.flags |= ZEND_FUTURE_F_IGNORED) - -#define ZEND_FUTURE_COMPLETE(future, result) do { \ - if(ZEND_ASYNC_EVENT_IS_CLOSED(&(future)->event)) { \ - break; \ - } \ - ZVAL_COPY(&(future)->result, (result)); \ - (future)->event.stop(&(future)->event); \ +#define ZEND_FUTURE_COMPLETE(future, result) \ + do { \ + if (ZEND_ASYNC_EVENT_IS_CLOSED(&(future)->event)) { \ + break; \ + } \ + ZVAL_COPY(&(future)->result, (result)); \ + (future)->event.stop(&(future)->event); \ } while (0) -#define ZEND_FUTURE_REJECT(future, error) do { \ - if(ZEND_ASYNC_EVENT_IS_CLOSED(&(future)->event)) { \ - break; \ - } \ - (future)->exception = error; \ - GC_ADDREF(error); \ - (future)->event.stop(&(future)->event); \ +#define ZEND_FUTURE_REJECT(future, error) \ + do { \ + if (ZEND_ASYNC_EVENT_IS_CLOSED(&(future)->event)) { \ + break; \ + } \ + (future)->exception = error; \ + GC_ADDREF(error); \ + (future)->event.stop(&(future)->event); \ } while (0) /////////////////////////////////////////////////////////////// @@ -1132,15 +1151,14 @@ struct _zend_future_s { * It inherits from zend_async_event_t to participate in the event system. */ struct _zend_async_channel_s { - zend_async_event_t event; /* Event inheritance (first member) */ + zend_async_event_t event; /* Event inheritance (first member) */ /* Channel-specific method pointers */ - zend_channel_send_t send; /* Send method */ - zend_channel_receive_t receive; /* Receive method */ + zend_channel_send_t send; /* Send method */ + zend_channel_receive_t receive; /* Receive method */ }; #define ZEND_ASYNC_CHANNEL_F_THREAD_SAFE (1u << 10) - /////////////////////////////////////////////////////////////// /// Global Macros /////////////////////////////////////////////////////////////// @@ -1210,31 +1228,34 @@ END_EXTERN_C() #define ZEND_ASYNC_MAIN_SCOPE ZEND_ASYNC_G(main_scope) #define ZEND_ASYNC_SCHEDULER ZEND_ASYNC_G(scheduler) -#define ZEND_ASYNC_INCREASE_EVENT_COUNT if (ZEND_ASYNC_G(active_event_count) < UINT_MAX) { \ +#define ZEND_ASYNC_INCREASE_EVENT_COUNT \ + if (ZEND_ASYNC_G(active_event_count) < UINT_MAX) { \ ZEND_ASYNC_G(active_event_count)++; \ } else { \ ZEND_ASSERT("The event count is already max."); \ } -#define ZEND_ASYNC_DECREASE_EVENT_COUNT if (ZEND_ASYNC_G(active_event_count) > 0) { \ +#define ZEND_ASYNC_DECREASE_EVENT_COUNT \ + if (ZEND_ASYNC_G(active_event_count) > 0) { \ ZEND_ASYNC_G(active_event_count)--; \ } else { \ ZEND_ASSERT("The event count is already zero."); \ } -#define ZEND_ASYNC_INCREASE_COROUTINE_COUNT if (ZEND_ASYNC_G(active_coroutine_count) < UINT_MAX) { \ +#define ZEND_ASYNC_INCREASE_COROUTINE_COUNT \ + if (ZEND_ASYNC_G(active_coroutine_count) < UINT_MAX) { \ ZEND_ASYNC_G(active_coroutine_count)++; \ } else { \ ZEND_ASSERT("The coroutine count is already max."); \ } -#define ZEND_ASYNC_DECREASE_COROUTINE_COUNT if (ZEND_ASYNC_G(active_coroutine_count) > 0) { \ +#define ZEND_ASYNC_DECREASE_COROUTINE_COUNT \ + if (ZEND_ASYNC_G(active_coroutine_count) > 0) { \ ZEND_ASYNC_G(active_coroutine_count)--; \ } else { \ ZEND_ASSERT("The coroutine count is already zero."); \ } - BEGIN_EXTERN_C() ZEND_API bool zend_async_is_enabled(void); @@ -1245,13 +1266,15 @@ void zend_async_api_shutdown(void); void zend_async_globals_ctor(void); void zend_async_globals_dtor(void); -ZEND_API const char * zend_async_get_api_version(void); +ZEND_API const char *zend_async_get_api_version(void); ZEND_API int zend_async_get_api_version_number(void); -ZEND_API ZEND_COLD zend_object * zend_async_new_exception(zend_async_class type, const char *format, ...); -ZEND_API ZEND_COLD zend_object * zend_async_throw(zend_async_class type, const char *format, ...); -ZEND_API ZEND_COLD zend_object * zend_async_throw_cancellation(const char *format, ...); -ZEND_API ZEND_COLD zend_object * zend_async_throw_timeout(const char *format, const zend_long timeout); +ZEND_API ZEND_COLD zend_object *zend_async_new_exception( + zend_async_class type, const char *format, ...); +ZEND_API ZEND_COLD zend_object *zend_async_throw(zend_async_class type, const char *format, ...); +ZEND_API ZEND_COLD zend_object *zend_async_throw_cancellation(const char *format, ...); +ZEND_API ZEND_COLD zend_object *zend_async_throw_timeout( + const char *format, const zend_long timeout); /* Scheduler API */ @@ -1285,9 +1308,10 @@ ZEND_API extern zend_async_new_context_t zend_async_new_context_fn; /* Internal Context API - Direct Functions */ ZEND_API uint32_t zend_async_internal_context_key_alloc(const char *key_name); -ZEND_API const char* zend_async_internal_context_key_name(uint32_t key); -ZEND_API zval * zend_async_internal_context_find(zend_coroutine_t *coroutine, uint32_t key); -ZEND_API void zend_async_internal_context_set(zend_coroutine_t *coroutine, uint32_t key, zval *value); +ZEND_API const char *zend_async_internal_context_key_name(uint32_t key); +ZEND_API zval *zend_async_internal_context_find(zend_coroutine_t *coroutine, uint32_t key); +ZEND_API void zend_async_internal_context_set( + zend_coroutine_t *coroutine, uint32_t key, zval *value); ZEND_API bool zend_async_internal_context_unset(zend_coroutine_t *coroutine, uint32_t key); /* Internal Context initialization and cleanup */ @@ -1332,147 +1356,117 @@ ZEND_API extern zend_async_queue_task_t zend_async_queue_task_fn; /* Trigger Event API */ ZEND_API extern zend_async_new_trigger_event_t zend_async_new_trigger_event_fn; -ZEND_API bool zend_async_scheduler_register( - char *module, - bool allow_override, - zend_async_new_coroutine_t new_coroutine_fn, - zend_async_new_scope_t new_scope_fn, - zend_async_new_context_t new_context_fn, - zend_async_spawn_t spawn_fn, - zend_async_suspend_t suspend_fn, - zend_async_enqueue_coroutine_t enqueue_coroutine_fn, - zend_async_resume_t resume_fn, - zend_async_cancel_t cancel_fn, - zend_async_spawn_and_throw_t spawn_and_throw_fn, - zend_async_shutdown_t shutdown_fn, - zend_async_get_coroutines_t get_coroutines_fn, - zend_async_add_microtask_t add_microtask_fn, - zend_async_get_awaiting_info_t get_awaiting_info_fn, - zend_async_get_class_ce_t get_class_ce_fn, - zend_async_new_iterator_t new_iterator_fn, - zend_async_new_future_t new_future_fn, - zend_async_new_channel_t new_channel_fn, - zend_async_new_future_obj_t new_future_obj_fn, - zend_async_new_channel_obj_t new_channel_obj_fn, - zend_async_new_group_t new_group_fn, - zend_async_engine_shutdown_t engine_shutdown_fn -); - -ZEND_API bool zend_async_reactor_register( - char *module, - bool allow_override, - zend_async_reactor_startup_t reactor_startup_fn, - zend_async_reactor_shutdown_t reactor_shutdown_fn, - zend_async_reactor_execute_t reactor_execute_fn, - zend_async_reactor_loop_alive_t reactor_loop_alive_fn, - zend_async_new_socket_event_t new_socket_event_fn, - zend_async_new_poll_event_t new_poll_event_fn, - zend_async_new_timer_event_t new_timer_event_fn, - zend_async_new_signal_event_t new_signal_event_fn, - zend_async_new_process_event_t new_process_event_fn, - zend_async_new_thread_event_t new_thread_event_fn, - zend_async_new_filesystem_event_t new_filesystem_event_fn, - zend_async_getnameinfo_t getnameinfo_fn, - zend_async_getaddrinfo_t getaddrinfo_fn, - zend_async_freeaddrinfo_t freeaddrinfo_fn, - zend_async_new_exec_event_t new_exec_event_fn, - zend_async_exec_t exec_fn, - zend_async_new_trigger_event_t new_trigger_event_fn -); +ZEND_API bool zend_async_scheduler_register(char *module, bool allow_override, + zend_async_new_coroutine_t new_coroutine_fn, zend_async_new_scope_t new_scope_fn, + zend_async_new_context_t new_context_fn, zend_async_spawn_t spawn_fn, + zend_async_suspend_t suspend_fn, zend_async_enqueue_coroutine_t enqueue_coroutine_fn, + zend_async_resume_t resume_fn, zend_async_cancel_t cancel_fn, + zend_async_spawn_and_throw_t spawn_and_throw_fn, zend_async_shutdown_t shutdown_fn, + zend_async_get_coroutines_t get_coroutines_fn, zend_async_add_microtask_t add_microtask_fn, + zend_async_get_awaiting_info_t get_awaiting_info_fn, + zend_async_get_class_ce_t get_class_ce_fn, zend_async_new_iterator_t new_iterator_fn, + zend_async_new_future_t new_future_fn, zend_async_new_channel_t new_channel_fn, + zend_async_new_future_obj_t new_future_obj_fn, + zend_async_new_channel_obj_t new_channel_obj_fn, zend_async_new_group_t new_group_fn, + zend_async_engine_shutdown_t engine_shutdown_fn); + +ZEND_API bool zend_async_reactor_register(char *module, bool allow_override, + zend_async_reactor_startup_t reactor_startup_fn, + zend_async_reactor_shutdown_t reactor_shutdown_fn, + zend_async_reactor_execute_t reactor_execute_fn, + zend_async_reactor_loop_alive_t reactor_loop_alive_fn, + zend_async_new_socket_event_t new_socket_event_fn, + zend_async_new_poll_event_t new_poll_event_fn, + zend_async_new_timer_event_t new_timer_event_fn, + zend_async_new_signal_event_t new_signal_event_fn, + zend_async_new_process_event_t new_process_event_fn, + zend_async_new_thread_event_t new_thread_event_fn, + zend_async_new_filesystem_event_t new_filesystem_event_fn, + zend_async_getnameinfo_t getnameinfo_fn, zend_async_getaddrinfo_t getaddrinfo_fn, + zend_async_freeaddrinfo_t freeaddrinfo_fn, zend_async_new_exec_event_t new_exec_event_fn, + zend_async_exec_t exec_fn, zend_async_new_trigger_event_t new_trigger_event_fn); ZEND_API void zend_async_thread_pool_register( - zend_string *module, bool allow_override, zend_async_queue_task_t queue_task_fn -); + zend_string *module, bool allow_override, zend_async_queue_task_t queue_task_fn); ZEND_API bool zend_async_socket_listening_register( - char *module, - bool allow_override, - zend_async_socket_listen_t socket_listen_fn -); + char *module, bool allow_override, zend_async_socket_listen_t socket_listen_fn); -ZEND_API zend_string* zend_coroutine_gen_info(zend_coroutine_t *coroutine, char *zend_coroutine_name); +ZEND_API zend_string *zend_coroutine_gen_info( + zend_coroutine_t *coroutine, char *zend_coroutine_name); -ZEND_API zend_async_event_callback_t * zend_async_event_callback_new(zend_async_event_callback_fn callback, size_t size); +ZEND_API zend_async_event_callback_t *zend_async_event_callback_new( + zend_async_event_callback_fn callback, size_t size); #define ZEND_ASYNC_EVENT_CALLBACK(callback) zend_async_event_callback_new(callback, 0) #define ZEND_ASYNC_EVENT_CALLBACK_EX(callback, size) zend_async_event_callback_new(callback, size) -ZEND_API zend_coroutine_event_callback_t * zend_async_coroutine_callback_new( - zend_coroutine_t * coroutine, zend_async_event_callback_fn callback, size_t size -); -ZEND_API void coroutine_event_callback_dispose(zend_async_event_callback_t *callback, zend_async_event_t * event); +ZEND_API zend_coroutine_event_callback_t *zend_async_coroutine_callback_new( + zend_coroutine_t *coroutine, zend_async_event_callback_fn callback, size_t size); +ZEND_API void coroutine_event_callback_dispose( + zend_async_event_callback_t *callback, zend_async_event_t *event); /* Waker API */ ZEND_API zend_async_waker_t *zend_async_waker_new(zend_coroutine_t *coroutine); -ZEND_API zend_async_waker_t * zend_async_waker_new_with_timeout( - zend_coroutine_t * coroutine, const zend_ulong timeout, zend_async_event_t *cancellation -); -ZEND_API bool zend_async_waker_apply_error( - zend_async_waker_t *waker, zend_object *error, bool transfer_error, bool override, bool for_cancellation -); +ZEND_API zend_async_waker_t *zend_async_waker_new_with_timeout( + zend_coroutine_t *coroutine, const zend_ulong timeout, zend_async_event_t *cancellation); +ZEND_API bool zend_async_waker_apply_error(zend_async_waker_t *waker, zend_object *error, + bool transfer_error, bool override, bool for_cancellation); ZEND_API void zend_async_waker_destroy(zend_coroutine_t *coroutine); -ZEND_API void zend_async_waker_add_triggered_event(zend_coroutine_t *coroutine, zend_async_event_t *event); -ZEND_API bool zend_async_waker_is_event_exists(zend_coroutine_t *coroutine, zend_async_event_t *event); - -#define ZEND_ASYNC_WAKER_APPLY_ERROR(waker, error, transfer) zend_async_waker_apply_error((waker), (error), (transfer), true, false) -#define ZEND_ASYNC_WAKER_APPEND_ERROR(waker, error, transfer) zend_async_waker_apply_error((waker), (error), (transfer), false, false) -#define ZEND_ASYNC_WAKER_APPLY_CANCELLATION(waker, error, transfer) zend_async_waker_apply_error((waker), (error), (transfer), true, true) - -ZEND_API void zend_async_resume_when( - zend_coroutine_t *coroutine, - zend_async_event_t *event, - const bool trans_event, - zend_async_event_callback_fn callback, - zend_coroutine_event_callback_t *event_callback - ); - -ZEND_API void zend_async_waker_callback_resolve( - zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception -); +ZEND_API void zend_async_waker_add_triggered_event( + zend_coroutine_t *coroutine, zend_async_event_t *event); +ZEND_API bool zend_async_waker_is_event_exists( + zend_coroutine_t *coroutine, zend_async_event_t *event); -ZEND_API void zend_async_waker_callback_cancel( - zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception -); +#define ZEND_ASYNC_WAKER_APPLY_ERROR(waker, error, transfer) \ + zend_async_waker_apply_error((waker), (error), (transfer), true, false) +#define ZEND_ASYNC_WAKER_APPEND_ERROR(waker, error, transfer) \ + zend_async_waker_apply_error((waker), (error), (transfer), false, false) +#define ZEND_ASYNC_WAKER_APPLY_CANCELLATION(waker, error, transfer) \ + zend_async_waker_apply_error((waker), (error), (transfer), true, true) -ZEND_API void zend_async_waker_callback_timeout( - zend_async_event_t *event, zend_async_event_callback_t *callback, void * result, zend_object *exception -); +ZEND_API void zend_async_resume_when(zend_coroutine_t *coroutine, zend_async_event_t *event, + const bool trans_event, zend_async_event_callback_fn callback, + zend_coroutine_event_callback_t *event_callback); + +ZEND_API void zend_async_waker_callback_resolve(zend_async_event_t *event, + zend_async_event_callback_t *callback, void *result, zend_object *exception); + +ZEND_API void zend_async_waker_callback_cancel(zend_async_event_t *event, + zend_async_event_callback_t *callback, void *result, zend_object *exception); + +ZEND_API void zend_async_waker_callback_timeout(zend_async_event_t *event, + zend_async_event_callback_t *callback, void *result, zend_object *exception); /* Coroutine Switch Handlers API */ ZEND_API uint32_t zend_coroutine_add_switch_handler( - zend_coroutine_t *coroutine, - zend_coroutine_switch_handler_fn handler -); + zend_coroutine_t *coroutine, zend_coroutine_switch_handler_fn handler); ZEND_API bool zend_coroutine_remove_switch_handler( - zend_coroutine_t *coroutine, - uint32_t handler_index -); + zend_coroutine_t *coroutine, uint32_t handler_index); ZEND_API void zend_coroutine_call_switch_handlers( - zend_coroutine_t *coroutine, - bool is_enter, - bool is_finishing -); + zend_coroutine_t *coroutine, bool is_enter, bool is_finishing); ZEND_API void zend_coroutine_switch_handlers_init(zend_coroutine_t *coroutine); ZEND_API void zend_coroutine_switch_handlers_destroy(zend_coroutine_t *coroutine); /* Global Main Coroutine Switch Handlers API */ -ZEND_API void zend_async_add_main_coroutine_start_handler( - zend_coroutine_switch_handler_fn handler -); +ZEND_API void zend_async_add_main_coroutine_start_handler(zend_coroutine_switch_handler_fn handler); ZEND_API void zend_async_call_main_coroutine_start_handlers(zend_coroutine_t *main_coroutine); /* Future API Functions */ #define ZEND_ASYNC_NEW_FUTURE(thread_safe) zend_async_new_future_fn(thread_safe, 0) -#define ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) zend_async_new_future_fn(thread_safe, extra_size) +#define ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) \ + zend_async_new_future_fn(thread_safe, extra_size) #define ZEND_ASYNC_NEW_FUTURE_OBJ(future) zend_async_new_future_obj_fn(future) /* Channel API Functions */ -#define ZEND_ASYNC_NEW_CHANNEL(buffer_size, resizable, thread_safe) zend_async_new_channel_fn(buffer_size, resizable, thread_safe, 0) -#define ZEND_ASYNC_NEW_CHANNEL_EX(buffer_size, resizable, thread_safe, extra_size) zend_async_new_channel_fn(buffer_size, resizable, thread_safe, extra_size) +#define ZEND_ASYNC_NEW_CHANNEL(buffer_size, resizable, thread_safe) \ + zend_async_new_channel_fn(buffer_size, resizable, thread_safe, 0) +#define ZEND_ASYNC_NEW_CHANNEL_EX(buffer_size, resizable, thread_safe, extra_size) \ + zend_async_new_channel_fn(buffer_size, resizable, thread_safe, extra_size) #define ZEND_ASYNC_NEW_CHANNEL_OBJ(channel) zend_async_new_channel_obj_fn(channel) /* GROUP API Functions */ @@ -1494,22 +1488,26 @@ END_EXTERN_C() #define ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN() zend_async_suspend_fn(true) #define ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine) zend_async_enqueue_coroutine_fn(coroutine) #define ZEND_ASYNC_RESUME(coroutine) zend_async_resume_fn(coroutine, NULL, false) -#define ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error) zend_async_resume_fn(coroutine, error, transfer_error) -#define ZEND_ASYNC_CANCEL(coroutine, error, transfer_error) zend_async_cancel_fn(coroutine, error, transfer_error, false) -#define ZEND_ASYNC_CANCEL_EX(coroutine, error, transfer_error, is_safely) zend_async_cancel_fn(coroutine, error, transfer_error, is_safely) +#define ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error) \ + zend_async_resume_fn(coroutine, error, transfer_error) +#define ZEND_ASYNC_CANCEL(coroutine, error, transfer_error) \ + zend_async_cancel_fn(coroutine, error, transfer_error, false) +#define ZEND_ASYNC_CANCEL_EX(coroutine, error, transfer_error, is_safely) \ + zend_async_cancel_fn(coroutine, error, transfer_error, is_safely) /** * Spawns a new coroutine and throws the specified exception within it. - * + * * This creates a dedicated coroutine for exception handling, ensuring proper * scope-based error propagation when exceptions occur in microtasks or other * contexts where direct throwing would bypass scope exception handling. - * + * * @param exception The exception object to throw in the new coroutine * @param scope Target scope for the coroutine (NULL for current scope) * @param priority Priority level for the exception-throwing coroutine */ -#define ZEND_ASYNC_SPAWN_AND_THROW(exception, scope, priority) zend_async_spawn_and_throw_fn(exception, scope, priority) +#define ZEND_ASYNC_SPAWN_AND_THROW(exception, scope, priority) \ + zend_async_spawn_and_throw_fn(exception, scope, priority) /** * The API method initiates graceful shutdown mode. */ @@ -1528,33 +1526,53 @@ END_EXTERN_C() #define ZEND_ASYNC_REACTOR_EXECUTE(no_wait) zend_async_reactor_execute_fn(no_wait) #define ZEND_ASYNC_REACTOR_LOOP_ALIVE() zend_async_reactor_loop_alive_fn() -#define ZEND_ASYNC_NEW_SOCKET_EVENT(socket, events) zend_async_new_socket_event_fn(socket, events, 0) -#define ZEND_ASYNC_NEW_SOCKET_EVENT_EX(socket, events, extra_size) zend_async_new_socket_event_fn(socket, events, extra_size) -#define ZEND_ASYNC_NEW_POLL_EVENT(fh, socket, events) zend_async_new_poll_event_fn(fh, socket, events, 0) -#define ZEND_ASYNC_NEW_POLL_EVENT_EX(fh, socket, events, extra_size) zend_async_new_poll_event_fn(fh, socket, events, extra_size) -#define ZEND_ASYNC_NEW_TIMER_EVENT(timeout, is_periodic) zend_async_new_timer_event_fn(timeout, 0, is_periodic, 0) -#define ZEND_ASYNC_NEW_TIMER_EVENT_EX(timeout, is_periodic, extra_size) zend_async_new_timer_event_fn(timeout, 0, is_periodic, extra_size) -#define ZEND_ASYNC_NEW_TIMER_EVENT_NS(timeout, nanoseconds, is_periodic) zend_async_new_timer_event_fn(timeout, nanoseconds, is_periodic, 0) -#define ZEND_ASYNC_NEW_TIMER_EVENT_NS_EX(timeout, nanoseconds, is_periodic, extra_size) zend_async_new_timer_event_fn(timeout, nanoseconds, is_periodic, extra_size) +#define ZEND_ASYNC_NEW_SOCKET_EVENT(socket, events) \ + zend_async_new_socket_event_fn(socket, events, 0) +#define ZEND_ASYNC_NEW_SOCKET_EVENT_EX(socket, events, extra_size) \ + zend_async_new_socket_event_fn(socket, events, extra_size) +#define ZEND_ASYNC_NEW_POLL_EVENT(fh, socket, events) \ + zend_async_new_poll_event_fn(fh, socket, events, 0) +#define ZEND_ASYNC_NEW_POLL_EVENT_EX(fh, socket, events, extra_size) \ + zend_async_new_poll_event_fn(fh, socket, events, extra_size) +#define ZEND_ASYNC_NEW_TIMER_EVENT(timeout, is_periodic) \ + zend_async_new_timer_event_fn(timeout, 0, is_periodic, 0) +#define ZEND_ASYNC_NEW_TIMER_EVENT_EX(timeout, is_periodic, extra_size) \ + zend_async_new_timer_event_fn(timeout, 0, is_periodic, extra_size) +#define ZEND_ASYNC_NEW_TIMER_EVENT_NS(timeout, nanoseconds, is_periodic) \ + zend_async_new_timer_event_fn(timeout, nanoseconds, is_periodic, 0) +#define ZEND_ASYNC_NEW_TIMER_EVENT_NS_EX(timeout, nanoseconds, is_periodic, extra_size) \ + zend_async_new_timer_event_fn(timeout, nanoseconds, is_periodic, extra_size) #define ZEND_ASYNC_NEW_SIGNAL_EVENT(signum) zend_async_new_signal_event_fn(signum, 0) -#define ZEND_ASYNC_NEW_SIGNAL_EVENT_EX(signum, extra_size) zend_async_new_signal_event_fn(signum, extra_size) -#define ZEND_ASYNC_NEW_PROCESS_EVENT(process_handle) zend_async_new_process_event_fn(process_handle, 0) -#define ZEND_ASYNC_NEW_PROCESS_EVENT_EX(process_handle, extra_size) zend_async_new_process_event_fn(process_handle, extra_size) +#define ZEND_ASYNC_NEW_SIGNAL_EVENT_EX(signum, extra_size) \ + zend_async_new_signal_event_fn(signum, extra_size) +#define ZEND_ASYNC_NEW_PROCESS_EVENT(process_handle) \ + zend_async_new_process_event_fn(process_handle, 0) +#define ZEND_ASYNC_NEW_PROCESS_EVENT_EX(process_handle, extra_size) \ + zend_async_new_process_event_fn(process_handle, extra_size) #define ZEND_ASYNC_NEW_THREAD_EVENT(entry, arg) zend_async_new_thread_event_fn(entry, arg, 0) #define ZEND_ASYNC_NEW_THREAD_EVENT_EX(entry, arg) zend_async_new_thread_event_fn(entry, arg, 0) -#define ZEND_ASYNC_NEW_FILESYSTEM_EVENT(path, flags) zend_async_new_filesystem_event_fn(path, flags, 0) -#define ZEND_ASYNC_NEW_FILESYSTEM_EVENT_EX(path, flags, extra_size) zend_async_new_filesystem_event_fn(path, flags, extra_size) +#define ZEND_ASYNC_NEW_FILESYSTEM_EVENT(path, flags) \ + zend_async_new_filesystem_event_fn(path, flags, 0) +#define ZEND_ASYNC_NEW_FILESYSTEM_EVENT_EX(path, flags, extra_size) \ + zend_async_new_filesystem_event_fn(path, flags, extra_size) #define ZEND_ASYNC_GETNAMEINFO(addr, flags) zend_async_getnameinfo_fn(addr, flags, 0) -#define ZEND_ASYNC_GETNAMEINFO_EX(addr, flags, extra_size) zend_async_getnameinfo_fn(addr, flags, extra_size) -#define ZEND_ASYNC_GETADDRINFO(node, service, hints) zend_async_getaddrinfo_fn(node, service, hints, 0) -#define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, extra_size) zend_async_getaddrinfo_fn(node, service, hints, extra_size) +#define ZEND_ASYNC_GETNAMEINFO_EX(addr, flags, extra_size) \ + zend_async_getnameinfo_fn(addr, flags, extra_size) +#define ZEND_ASYNC_GETADDRINFO(node, service, hints) \ + zend_async_getaddrinfo_fn(node, service, hints, 0) +#define ZEND_ASYNC_GETADDRINFO_EX(node, service, hints, extra_size) \ + zend_async_getaddrinfo_fn(node, service, hints, extra_size) #define ZEND_ASYNC_FREEADDRINFO(ai) zend_async_freeaddrinfo_fn(ai) -#define ZEND_ASYNC_NEW_EXEC_EVENT(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env) \ - zend_async_new_exec_event_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, 0) -#define ZEND_ASYNC_NEW_EXEC_EVENT_EX(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, extra_size) \ - zend_async_new_exec_event_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, extra_size) +#define ZEND_ASYNC_NEW_EXEC_EVENT( \ + exec_mode, cmd, return_buffer, return_value, std_error, cwd, env) \ + zend_async_new_exec_event_fn( \ + exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, 0) +#define ZEND_ASYNC_NEW_EXEC_EVENT_EX( \ + exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, extra_size) \ + zend_async_new_exec_event_fn( \ + exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, extra_size) #define ZEND_ASYNC_EXEC(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, timeout) \ zend_async_exec_fn(exec_mode, cmd, return_buffer, return_value, std_error, cwd, env, timeout) @@ -1571,39 +1589,48 @@ END_EXTERN_C() zend_async_socket_listen_fn(host, port, backlog, extra_size) /* Iterator API Macros */ -#define ZEND_ASYNC_NEW_ITERATOR_SCOPE(array, zend_iterator, fcall, handler, scope, concurrency, priority) \ - zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, scope, concurrency, priority, 0) +#define ZEND_ASYNC_NEW_ITERATOR_SCOPE( \ + array, zend_iterator, fcall, handler, scope, concurrency, priority) \ + zend_async_new_iterator_fn( \ + array, zend_iterator, fcall, handler, scope, concurrency, priority, 0) #define ZEND_ASYNC_NEW_ITERATOR(array, zend_iterator, fcall, handler, concurrency, priority) \ zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, NULL, concurrency, priority, 0) -#define ZEND_ASYNC_NEW_ITERATOR_EX(array, zend_iterator, fcall, handler, concurrency, priority, size) \ - zend_async_new_iterator_fn(array, zend_iterator, fcall, handler, NULL, concurrency, priority, size) +#define ZEND_ASYNC_NEW_ITERATOR_EX( \ + array, zend_iterator, fcall, handler, concurrency, priority, size) \ + zend_async_new_iterator_fn( \ + array, zend_iterator, fcall, handler, NULL, concurrency, priority, size) /* Context API Macros */ #define ZEND_ASYNC_NEW_CONTEXT(parent) zend_async_new_context_fn(parent) -#define ZEND_ASYNC_CURRENT_CONTEXT (ZEND_ASYNC_G(coroutine) != NULL ? ZEND_ASYNC_G(coroutine)->scope->context : NULL) +#define ZEND_ASYNC_CURRENT_CONTEXT \ + (ZEND_ASYNC_G(coroutine) != NULL ? ZEND_ASYNC_G(coroutine)->scope->context : NULL) #define ZEND_ASYNC_GET_COROUTINE_CONTEXT() \ - ((ZEND_ASYNC_G(coroutine)) ? \ - (ZEND_ASYNC_G(coroutine)->context ? \ - ZEND_ASYNC_G(coroutine)->context : \ - (ZEND_ASYNC_G(coroutine)->context = ZEND_ASYNC_NEW_CONTEXT(NULL))) \ - : NULL) + ((ZEND_ASYNC_G(coroutine)) \ + ? (ZEND_ASYNC_G(coroutine)->context ? ZEND_ASYNC_G(coroutine)->context \ + : (ZEND_ASYNC_G(coroutine)->context \ + = ZEND_ASYNC_NEW_CONTEXT(NULL))) \ + : NULL) /* Internal Context API Macros */ -#define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC(key_name) zend_async_internal_context_key_alloc(key_name) +#define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC(key_name) \ + zend_async_internal_context_key_alloc(key_name) #define ZEND_ASYNC_INTERNAL_CONTEXT_KEY_NAME(key) zend_async_internal_context_key_name(key) #define ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coro, key) zend_async_internal_context_find(coro, key) -#define ZEND_ASYNC_INTERNAL_CONTEXT_SET(coro, key, value) zend_async_internal_context_set(coro, key, value) +#define ZEND_ASYNC_INTERNAL_CONTEXT_SET(coro, key, value) \ + zend_async_internal_context_set(coro, key, value) #define ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coro, key) zend_async_internal_context_unset(coro, key) /* Coroutine Switch Handlers API Macros */ -#define ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler) zend_coroutine_add_switch_handler(coroutine, handler) +#define ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler) \ + zend_coroutine_add_switch_handler(coroutine, handler) #define ZEND_COROUTINE_ENTER(coroutine) zend_coroutine_call_switch_handlers(coroutine, true, false); #define ZEND_COROUTINE_LEAVE(coroutine) zend_coroutine_call_switch_handlers(coroutine, false, false) #define ZEND_COROUTINE_FINISH(coroutine) zend_coroutine_call_switch_handlers(coroutine, false, true) /* Global Main Coroutine Switch Handlers API Macros */ -#define ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler) zend_async_add_main_coroutine_start_handler(handler) +#define ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler) \ + zend_async_add_main_coroutine_start_handler(handler) #define ZEND_ASYNC_ADD_SWITCH_HANDLER(handler) \ if (ZEND_ASYNC_CURRENT_COROUTINE) { \ zend_coroutine_add_switch_handler(ZEND_ASYNC_CURRENT_COROUTINE, handler); \ @@ -1611,4 +1638,4 @@ END_EXTERN_C() zend_async_add_main_coroutine_start_handler(handler); \ } -#endif //ZEND_ASYNC_API_H \ No newline at end of file +#endif // ZEND_ASYNC_API_H \ No newline at end of file From eefe88107f3319ef046d56261d51defec236fe71 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:45:33 +0300 Subject: [PATCH 120/137] * fix This should be removed --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index 046a927a20603..1e92e88fb77fa 100644 --- a/.gitignore +++ b/.gitignore @@ -316,8 +316,3 @@ tmp-php.ini !/ext/lexbor/patches/*.patch !/ext/pcre/pcre2lib/config.h !/win32/build/Makefile - -# ------------------------------------------------------------------------------ -# True Asynchronous extensions -# ------------------------------------------------------------------------------ -/ext/async \ No newline at end of file From 3fc842a4e946b8ba301cb06da7ef3ece274ea25d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:47:00 +0300 Subject: [PATCH 121/137] * remove doc --- docs/source/index.rst | 6 - docs/source/miscellaneous/writing-tests.rst | 22 +- docs/source/true_async_api/api-reference.rst | 424 ------------- docs/source/true_async_api/architecture.rst | 27 - docs/source/true_async_api/coroutines.rst | 469 --------------- docs/source/true_async_api/events.rst | 301 ---------- docs/source/true_async_api/examples.rst | 452 -------------- .../true_async_api/implementation-guide.rst | 396 ------------ docs/source/true_async_api/index.rst | 108 ---- docs/source/true_async_api/patterns.rst | 568 ------------------ 10 files changed, 11 insertions(+), 2762 deletions(-) delete mode 100644 docs/source/true_async_api/api-reference.rst delete mode 100644 docs/source/true_async_api/architecture.rst delete mode 100644 docs/source/true_async_api/coroutines.rst delete mode 100644 docs/source/true_async_api/events.rst delete mode 100644 docs/source/true_async_api/examples.rst delete mode 100644 docs/source/true_async_api/implementation-guide.rst delete mode 100644 docs/source/true_async_api/index.rst delete mode 100644 docs/source/true_async_api/patterns.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index de0cd477c469d..21e2526f47f64 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,12 +15,6 @@ core/data-structures/index -.. toctree:: - :caption: TrueAsync API - :hidden: - - true_async_api/index - .. toctree:: :caption: Miscellaneous :hidden: diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.rst index 46f03f94703ad..fd09d80f1275e 100644 --- a/docs/source/miscellaneous/writing-tests.rst +++ b/docs/source/miscellaneous/writing-tests.rst @@ -95,13 +95,13 @@ might use a variation tests to test boundary conditions. How big is a test case? ======================= -Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The -reason for this is that if we break something in PHP and it breaks your test case we need to be able -to find out quite quickly what we broke, going through 1000s of line of test case output is not -easy. Having said that it's sometimes just not practical to stay within the 10 line guideline, in -this case you can help a lot by commenting the output. You may find plenty of much longer tests in -PHP - the small tests message is something that we learnt over time, in fact we are slowly going -through and splitting tests up when we need to. +Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The reason +for this is that if we break something in PHP and it breaks your test case we need to be able to +find out quite quickly what we broke, going through 1000s of line of test case output is not easy. +Having said that it's sometimes just not practical to stay within the 10 line guideline, in this +case you can help a lot by commenting the output. You may find plenty of much longer tests in PHP - +the small tests message is something that we learnt over time, in fact we are slowly going through +and splitting tests up when we need to. Comments ======== @@ -183,10 +183,10 @@ Testing your test cases Most people who write tests for PHP don't have access to a huge number of operating systems but the tests are run on every system that runs PHP. It's good to test your test on as many platforms as you -can — Linux and Windows are the most important, it's increasingly important to make sure that -tests run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — -don't worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of -failures on other platforms. If you don't have karma to commit have a look at the next section. +can — Linux and Windows are the most important, it's increasingly important to make sure that tests +run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — don't +worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of failures on +other platforms. If you don't have karma to commit have a look at the next section. When you are testing your test case it's really important to make sure that you clean up any temporary resources (eg files) that you used in the test. There is a special ``--CLEAN--`` section diff --git a/docs/source/true_async_api/api-reference.rst b/docs/source/true_async_api/api-reference.rst deleted file mode 100644 index 5deecf10dbd7c..0000000000000 --- a/docs/source/true_async_api/api-reference.rst +++ /dev/null @@ -1,424 +0,0 @@ -############### - API Reference -############### - -This section provides comprehensive documentation for the TrueAsync API functions, macros, and data -structures based on the actual implementation in ``Zend/zend_async_API.h``. - -************* - API Version -************* - -.. code:: c - - #define ZEND_ASYNC_API "TrueAsync API v0.4.0" - #define ZEND_ASYNC_API_VERSION_MAJOR 0 - #define ZEND_ASYNC_API_VERSION_MINOR 4 - #define ZEND_ASYNC_API_VERSION_PATCH 0 - -***************** - Core API Macros -***************** - -Enablement Check -================ - -.. code:: c - - ZEND_ASYNC_IS_ENABLED() - -Returns ``true`` if async functionality is available, ``false`` otherwise. Always check this before -using any async APIs. - -.. code:: c - - ZEND_ASYNC_REACTOR_IS_ENABLED() - -Returns ``true`` if reactor (event loop) is available and functional. - -********************** - Coroutine Operations -********************** - -Coroutine Creation -================== - -.. code:: c - - ZEND_ASYNC_SPAWN() - ZEND_ASYNC_SPAWN_WITH(scope) - ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider) - ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority) - ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority) - -Creates and spawns new coroutines with various configuration options: - -- ``ZEND_ASYNC_SPAWN()`` - Create coroutine with default settings -- ``ZEND_ASYNC_SPAWN_WITH(scope)`` - Create coroutine within specific scope -- ``ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider)`` - Use scope provider object -- ``ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority)`` - Set coroutine priority -- ``ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority)`` - Both scope and priority - -**Priority levels:** - -.. code:: c - - typedef enum { - ZEND_COROUTINE_NORMAL = 0, - ZEND_COROUTINE_HI_PRIORITY = 255 - } zend_coroutine_priority; - -Coroutine Management -==================== - -.. code:: c - - ZEND_ASYNC_NEW_COROUTINE(scope) - -Creates a new coroutine object without starting it. - -.. code:: c - - ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine) - -Adds coroutine to execution queue for scheduling. - -.. code:: c - - ZEND_ASYNC_SUSPEND() - ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN() - -Suspends current coroutine execution: - -- ``SUSPEND()`` - Normal suspension -- ``RUN_SCHEDULER_AFTER_MAIN()`` - Suspend from main context - -Coroutine Control -================= - -.. code:: c - - ZEND_ASYNC_RESUME(coroutine) - ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error) - -Resume coroutine execution: - -- ``RESUME(coroutine)`` - Normal resume -- ``RESUME_WITH_ERROR(coroutine, error, transfer_error)`` - Resume with exception - -.. code:: c - - ZEND_ASYNC_CANCEL(coroutine, error, transfer_error) - ZEND_ASYNC_CANCEL_EX(coroutine, error, transfer_error, is_safely) - -Cancel coroutine execution: - -- ``CANCEL()`` - Cancel with default safety -- ``CANCEL_EX()`` - Cancel with explicit safety flag - -*********** - Scope API -*********** - -Scope Creation -============== - -.. code:: c - - ZEND_ASYNC_NEW_SCOPE(parent) - ZEND_ASYNC_NEW_SCOPE_WITH_OBJECT(parent) - -Create new async scopes for coroutine isolation: - -- ``NEW_SCOPE(parent)`` - Create scope with parent reference -- ``NEW_SCOPE_WITH_OBJECT(parent)`` - Create scope backed by zend_object - -Scope Flags -=========== - -.. code:: c - - #define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) - #define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) - #define ZEND_ASYNC_SCOPE_IS_DISPOSING(scope) - -Check scope status: - -- ``IS_CLOSED`` - Scope has been closed -- ``IS_CANCELLED`` - Scope was cancelled -- ``IS_DISPOSING`` - Scope is being disposed - -*********** - Event API -*********** - -Event Lifecycle -=============== - -All async events implement these core function pointers: - -.. code:: c - - typedef void (*zend_async_event_start_t)(zend_async_event_t *event); - typedef void (*zend_async_event_stop_t)(zend_async_event_t *event); - typedef void (*zend_async_event_dispose_t)(zend_async_event_t *event); - -Event State Checking -==================== - -.. code:: c - - ZEND_ASYNC_EVENT_IS_CLOSED(event) - -Check if event has been closed. - -Event Reference Counting -======================== - -.. code:: c - - ZEND_ASYNC_EVENT_REF(event) - ZEND_ASYNC_EVENT_ADD_REF(event) - ZEND_ASYNC_EVENT_DEL_REF(event) - -Manage event reference counts for memory safety. - -Event Flags -=========== - -.. code:: c - - #define ZEND_ASYNC_EVENT_F_CLOSED (1u << 0) /* event was closed */ - #define ZEND_ASYNC_EVENT_F_RESULT_USED (1u << 1) /* result will be used */ - #define ZEND_ASYNC_EVENT_F_EXC_CAUGHT (1u << 2) /* error was caught */ - #define ZEND_ASYNC_EVENT_F_ZVAL_RESULT (1u << 3) /* result is zval */ - -.. code:: c - - ZEND_ASYNC_EVENT_SET_CLOSED(event) - ZEND_ASYNC_EVENT_CLR_CLOSED(event) - -Manipulate event flags. - -************* - Reactor API -************* - -Reactor Control -=============== - -.. code:: c - - ZEND_ASYNC_REACTOR_STARTUP() - -Start the async reactor (event loop). - -Poll Events -=========== - -.. code:: c - - typedef enum { - ASYNC_READABLE = 1, - ASYNC_WRITABLE = 2, - ASYNC_DISCONNECT = 4, - ASYNC_PRIORITIZED = 8 - } async_poll_event; - -Event flags for socket polling operations. - -****************** - Signal Constants -****************** - -.. code:: c - - #define ZEND_ASYNC_SIGHUP 1 - #define ZEND_ASYNC_SIGINT 2 - #define ZEND_ASYNC_SIGQUIT 3 - #define ZEND_ASYNC_SIGILL 4 - #define ZEND_ASYNC_SIGTERM 15 - #define ZEND_ASYNC_SIGBREAK 21 - #define ZEND_ASYNC_SIGABRT 22 - #define ZEND_ASYNC_SIGWINCH 28 - -Cross-platform signal constants for async signal handling. - -******************* - Process Execution -******************* - -Execution Modes -=============== - -.. code:: c - - typedef enum { - ZEND_ASYNC_EXEC_MODE_EXEC, /* exec() - last line only */ - ZEND_ASYNC_EXEC_MODE_SYSTEM, /* system() - print + return last */ - ZEND_ASYNC_EXEC_MODE_EXEC_ARRAY, /* exec() with array output */ - ZEND_ASYNC_EXEC_MODE_PASSTHRU, /* passthru() - binary output */ - ZEND_ASYNC_EXEC_MODE_SHELL_EXEC /* shell_exec() - buffer output */ - } zend_async_exec_mode; - -************************ - Future and Channel API -************************ - -Futures -======= - -.. code:: c - - ZEND_ASYNC_NEW_FUTURE(thread_safe) - ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) - -Create future objects for async result handling. - -Channels -======== - -.. code:: c - - ZEND_ASYNC_NEW_CHANNEL(buffer_size, resizable, thread_safe) - ZEND_ASYNC_NEW_CHANNEL_EX(buffer_size, resizable, thread_safe, extra_size) - -Create channels for coroutine communication. - -***************************** - API Context Switch Handlers -***************************** - -Registration -============ - -.. code:: c - - uint32_t zend_coroutine_add_switch_handler( - zend_coroutine_t *coroutine, - zend_coroutine_switch_handler_fn handler - ); - - bool zend_coroutine_remove_switch_handler( - zend_coroutine_t *coroutine, - uint32_t handler_index - ); - -Add/remove context switch handlers for individual coroutines. - -Global Handlers -=============== - -.. code:: c - - void zend_async_add_main_coroutine_start_handler( - zend_coroutine_switch_handler_fn handler - ); - -Add global handlers that are called when main coroutines start. - -Handler Function Type -===================== - -.. code:: c - - typedef bool (*zend_coroutine_switch_handler_fn)( - zend_coroutine_t *coroutine, - bool is_enter, /* true = entering, false = leaving */ - bool is_finishing /* true = coroutine finishing */ - ); - -Returns ``true`` to keep handler, ``false`` to remove it after execution. - -******************* - Utility Functions -******************* - -System Management -================= - -.. code:: c - - ZEND_ASYNC_SHUTDOWN() - ZEND_ASYNC_ENGINE_SHUTDOWN() - -Initiate graceful shutdown of async subsystem. - -.. code:: c - - ZEND_ASYNC_GET_COROUTINES() - ZEND_ASYNC_GET_AWAITING_INFO(coroutine) - -Get information about active coroutines and their await status. - -Microtasks -========== - -.. code:: c - - ZEND_ASYNC_ADD_MICROTASK(microtask) - -Add microtask to execution queue. - -Exception Handling -================== - -.. code:: c - - ZEND_ASYNC_SPAWN_AND_THROW(exception, scope, priority) - -Spawn coroutine specifically for throwing an exception within a scope. - -********************** - API Error Management -********************** - -API Exception Classes -===================== - -.. code:: c - - typedef enum { - ZEND_ASYNC_CLASS_NO = 0, - ZEND_ASYNC_CLASS_AWAITABLE = 1, - ZEND_ASYNC_CLASS_COROUTINE = 2 - } zend_async_class; - - ZEND_ASYNC_GET_CE(type) - ZEND_ASYNC_GET_EXCEPTION_CE(type) - -Get class entries for async-related classes and exceptions. - -************ - Data Types -************ - -Platform Types -============== - -.. code:: c - - #ifdef PHP_WIN32 - typedef HANDLE zend_file_descriptor_t; - typedef DWORD zend_process_id_t; - typedef HANDLE zend_process_t; - typedef SOCKET zend_socket_t; - #else - typedef int zend_file_descriptor_t; - typedef pid_t zend_process_id_t; - typedef pid_t zend_process_t; - typedef int zend_socket_t; - #endif - -Cross-platform type definitions for file descriptors, processes, and sockets. - -******************** - API Best Practices -******************** - -#. **Always check enablement** with ``ZEND_ASYNC_IS_ENABLED()`` before using APIs -#. **Use appropriate scopes** for coroutine isolation -#. **Handle reference counting** properly with event objects -#. **Register cleanup handlers** using context switch handlers -#. **Check reactor availability** before using event-based operations -#. **Use priority levels** appropriately for coroutine scheduling diff --git a/docs/source/true_async_api/architecture.rst b/docs/source/true_async_api/architecture.rst deleted file mode 100644 index 8a61ab8ae6b2f..0000000000000 --- a/docs/source/true_async_api/architecture.rst +++ /dev/null @@ -1,27 +0,0 @@ -############## - Architecture -############## - -The TrueAsync API follows a clean separation-of-concerns architecture that splits API definition -from implementation. This design enables modularity, testability, and support for multiple backend -implementations. - -The **True Async API** is divided into three main functional blocks: - -- **Scheduler** Manages coroutines and the main event loop. -- **Reactor** Handles everything related to input/output (I/O), including sockets, timers, and file - descriptors. -- **Thread Pool** Allows execution of blocking or heavy tasks in a thread pool. - ---- - -## Implementation Flexibility - -The True Async API provides clearly separated interfaces for each block, allowing them to be -implemented independently—even across different PHP extensions. For example: - -- The Scheduler can be implemented as part of the PHP core. -- The Reactor can be implemented in a separate extension using `libuv`. -- The Thread Pool can be based on native threads or a library like `pthreads`. - -This approach gives developers maximum flexibility and control over asynchronous execution. diff --git a/docs/source/true_async_api/coroutines.rst b/docs/source/true_async_api/coroutines.rst deleted file mode 100644 index 8e94cc71b9250..0000000000000 --- a/docs/source/true_async_api/coroutines.rst +++ /dev/null @@ -1,469 +0,0 @@ -############ - Coroutines -############ - -Coroutines are the core building blocks of the TrueAsync API. They represent lightweight execution -contexts that can be suspended and resumed, enabling cooperative multitasking without traditional -thread overhead. Every coroutine is implemented as an event (``zend_coroutine_t`` extends -``zend_async_event_t``) and participates in the standard event lifecycle. - -****************** - Coroutine Basics -****************** - -A coroutine represents a function or code block that can be paused during execution and resumed -later. Unlike traditional threads, coroutines are cooperatively scheduled - they yield control -voluntarily rather than being preemptively interrupted. - -Key characteristics: - -- **Lightweight** - Minimal memory overhead compared to threads -- **Cooperative** - Explicit yield points, no race conditions -- **Event-based** - Coroutines are events that can await other events -- **Scoped** - Each coroutine runs within a scope for context isolation - -********************* - Creating Coroutines -********************* - -The TrueAsync API provides several ways to create coroutines: - -.. code:: c - - // Create a new coroutine within default scope - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); - - // Create with specific scope - zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(parent_scope); - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH(scope); - - // Create with scope provider object - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider); - -Coroutines can be either: - -**Userland coroutines** - Execute PHP code through ``zend_fcall_t`` callback mechanism - -**Internal coroutines** - Execute C code through ``zend_coroutine_entry_t`` function pointer - -********************* - Coroutine Lifecycle -********************* - -Coroutines follow a well-defined lifecycle: - -#. **Creation** - Coroutine is spawned but not yet started -#. **Enqueue** - Added to execution queue via ``ZEND_ASYNC_ENQUEUE_COROUTINE()`` -#. **Execution** - Runs until suspension point or completion -#. **Suspension** - Yields control while waiting for events -#. **Resume** - Continues execution when events complete -#. **Completion** - Finishes with result or exception - -.. code:: c - - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); - - // Set up coroutine execution - coro->fcall = &my_function_call; - coro->scope = current_scope; - - // Start execution - ZEND_ASYNC_ENQUEUE_COROUTINE(coro); - - // Later: resume with result - ZEND_ASYNC_RESUME(coro); - - // Or resume with error - ZEND_ASYNC_RESUME_WITH_ERROR(coro, exception, transfer_error); - -******************* - Coroutine Context -******************* - -Each coroutine maintains its own context information: - -**Scope Context** (``zend_async_scope_t *scope``) - Links to parent scope for hierarchical context management - -**Async Context** (``zend_async_context_t *context``) - User-defined context variables accessible from PHP code - -**Internal Context** (``HashTable *internal_context``) - Extension-private data storage with numeric keys - -**Switch Handlers** (``zend_coroutine_switch_handlers_vector_t *switch_handlers``) - Functions called when entering/leaving the coroutine - -************************* - Context Switch Handlers -************************* - -Context switch handlers are one of the most powerful features of the coroutine system. They allow -extensions to hook into coroutine context switches and perform necessary cleanup, initialization, or -state management. - -Handler Function Signature -========================== - -.. code:: c - - typedef bool (*zend_coroutine_switch_handler_fn)( - zend_coroutine_t *coroutine, - bool is_enter, /* true = entering coroutine, false = leaving */ - bool is_finishing /* true = coroutine is finishing */ - /* returns: true = keep handler, false = remove handler after execution */ - ); - -The handler receives three parameters: - -- **coroutine** - The coroutine being switched to/from -- **is_enter** - ``true`` when entering coroutine, ``false`` when leaving -- **is_finishing** - ``true`` when coroutine is finishing execution - -The return value determines handler lifetime: - -- **true** - Keep handler for future context switches -- **false** - Remove handler immediately after execution - -This allows handlers to perform one-time initialization and then remove themselves automatically. - -Registering Handlers -==================== - -There are two types of switch handlers: - -**Per-Coroutine Handlers** - -.. code:: c - - // Add handler to specific coroutine - uint32_t handler_id = ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, my_handler); - - // Remove specific handler - zend_coroutine_remove_switch_handler(coroutine, handler_id); - -**Global Main Coroutine Handlers** - -.. code:: c - - // Register global handler for all main coroutines - ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(my_main_handler); - -Global handlers are automatically copied to each main coroutine when it starts and can remove -themselves by returning ``false``. - -Practical Example: Output Buffering -=================================== - -The ``main/output.c`` module provides an excellent example of context switch handlers in action. The -output system needs to isolate output buffers between the main execution context and coroutines to -prevent buffer conflicts. - -.. code:: c - - static bool php_output_main_coroutine_start_handler( - zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) - { - if (false == is_enter || OG(handlers).elements == 0) { - return false; /* Remove handler - no work to do */ - } - - /* Create coroutine-specific output context */ - php_output_context_t *ctx = ecalloc(1, sizeof(php_output_context_t)); - php_output_init_async_context(ctx); - - /* Copy output handlers from global to coroutine context */ - php_output_handler **src_handlers = (php_output_handler **)zend_stack_base(&OG(handlers)); - int handler_count = zend_stack_count(&OG(handlers)); - - for (int i = 0; i < handler_count; i++) { - php_output_handler *handler = src_handlers[i]; - zend_stack_push(&ctx->handlers, &handler); - } - - /* Store context in coroutine internal_context */ - zval ctx_zval; - ZVAL_PTR(&ctx_zval, ctx); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, php_output_context_key, &ctx_zval); - - /* Clean global handlers to avoid conflicts */ - zend_stack_destroy(&OG(handlers)); - zend_stack_init(&OG(handlers), sizeof(php_output_handler *)); - OG(active) = NULL; - - /* Add cleanup callback for coroutine completion */ - zend_coroutine_event_callback_t *cleanup_callback = - zend_async_coroutine_callback_new(coroutine, php_output_coroutine_cleanup_callback, 0); - coroutine->event.add_callback(&coroutine->event, &cleanup_callback->base); - - return false; /* Remove handler - initialization is complete */ - } - -This handler: - -#. **Detects main coroutine entry** - Only acts when ``is_enter`` is true -#. **Creates isolated context** - Copies output handlers to coroutine-specific storage -#. **Stores in internal_context** - Uses numeric key for fast access -#. **Cleans global state** - Prevents conflicts between main and coroutine contexts -#. **Registers cleanup** - Ensures proper resource cleanup on coroutine completion -#. **Removes itself** - Returns ``false`` since initialization is one-time only - -When to Use Context Switch Handlers -=================================== - -Context switch handlers are ideal for: - -**Resource Isolation** - Separating global state between main execution and coroutines (like output buffers, error - handlers, etc.) - -**Context Migration** - Moving data from one execution context to another - -**State Initialization** - Setting up coroutine-specific resources or configurations - -**Cleanup Management** - Registering cleanup callbacks for coroutine completion - -**Performance Optimization** - Pre-computing or caching data when entering frequently-used coroutines - -********************** - Internal Context API -********************** - -The internal context system provides a type-safe, efficient way for PHP extensions to store private -data associated with coroutines. Unlike userland context variables, internal context uses numeric -keys for faster access and is completely isolated from PHP code. - -Why Internal Context? -===================== - -Before internal context, extensions had limited options for storing coroutine-specific data: - -- **Global variables** - Not coroutine-safe, cause conflicts -- **Object properties** - Not available for internal coroutines -- **Manual management** - Complex, error-prone cleanup - -Internal context solves these problems by providing: - -- **Automatic cleanup** - Data is freed when coroutine completes -- **Type safety** - Values stored as ``zval`` with proper reference counting -- **Uniqueness** - Each extension gets private numeric keys -- **Performance** - Hash table lookup by integer key - -Key Allocation -============== - -Extensions allocate unique keys during module initialization: - -.. code:: c - - static uint32_t my_extension_context_key = 0; - - PHP_MINIT_FUNCTION(my_extension) - { - // Allocate unique key for this extension - my_extension_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension_data"); - return SUCCESS; - } - -The ``ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC`` macro: - -- Takes a static string identifier (for debugging) -- Returns a unique numeric key -- Validates that the same string address always gets the same key -- Is thread-safe in ZTS builds - -Storing and Retrieving Data -=========================== - -.. code:: c - - void my_coroutine_handler(zend_coroutine_t *coroutine) - { - // Store data in coroutine context - zval my_data; - ZVAL_LONG(&my_data, 42); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_extension_context_key, &my_data); - - // Later: retrieve data - zval *stored_data = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_extension_context_key); - if (stored_data && Z_TYPE_P(stored_data) == IS_LONG) { - long value = Z_LVAL_P(stored_data); - // Use value... - } - - // Optional: remove data explicitly - ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coroutine, my_extension_context_key); - } - -The API automatically handles: - -- **Reference counting** - ``zval`` reference counts are managed properly -- **Memory management** - All context data is freed when coroutine ends -- **Thread safety** - Operations are safe in ZTS builds - -Advanced Usage Patterns -======================= - -**Caching expensive computations:** - -.. code:: c - - zval *cached_result = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, cache_key); - if (!cached_result) { - // Expensive operation - zval computed_value; - ZVAL_STRING(&computed_value, expensive_computation()); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, cache_key, &computed_value); - cached_result = &computed_value; - } - -**Tracking state across suspensions:** - -.. code:: c - - // Before suspending - zval state; - ZVAL_LONG(&state, OPERATION_IN_PROGRESS); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, state_key, &state); - - // After resuming - zval *state_val = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, state_key); - if (state_val && Z_LVAL_P(state_val) == OPERATION_IN_PROGRESS) { - // Continue operation... - } - -**Storing complex objects:** - -.. code:: c - - zval object_container; - ZVAL_OBJ(&object_container, my_object); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, object_key, &object_container); - // Object reference count is automatically incremented - -Best Practices -============== - -#. **Allocate keys in MINIT** - Ensures keys are available when needed -#. **Use descriptive key names** - Helps with debugging and maintenance -#. **Check return values** - ``FIND`` returns ``NULL`` if key doesn't exist -#. **Validate data types** - Always check ``Z_TYPE_P`` before accessing data -#. **Clean up appropriately** - Use ``UNSET`` for early cleanup if needed - -*************** - Coroutine API -*************** - -Core Functions -============== - -.. code:: c - - // Coroutine creation and management - zend_coroutine_t *ZEND_ASYNC_SPAWN(); - zend_coroutine_t *ZEND_ASYNC_NEW_COROUTINE(scope); - void ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine); - - // Execution control - void ZEND_ASYNC_SUSPEND(); - void ZEND_ASYNC_RESUME(coroutine); - void ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error); - void ZEND_ASYNC_CANCEL(coroutine, error, transfer_error); - - // Context switch handlers - uint32_t ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler); - bool zend_coroutine_remove_switch_handler(coroutine, handler_index); - void ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler); - -State Checking Macros -===================== - -.. code:: c - - // Coroutine state - ZEND_COROUTINE_IS_STARTED(coroutine) - ZEND_COROUTINE_IS_FINISHED(coroutine) - ZEND_COROUTINE_IS_CANCELLED(coroutine) - ZEND_COROUTINE_SUSPENDED(coroutine) - ZEND_COROUTINE_IS_MAIN(coroutine) - - // Current context - ZEND_ASYNC_CURRENT_COROUTINE - ZEND_ASYNC_CURRENT_SCOPE - -Memory Management -================= - -Coroutines follow standard event reference counting: - -.. code:: c - - ZEND_ASYNC_EVENT_ADD_REF(&coroutine->event); // Increment reference - ZEND_ASYNC_EVENT_RELEASE(&coroutine->event); // Decrement and cleanup if needed - -The coroutine automatically cleans up: - -- Internal context data -- Switch handlers -- Event callbacks -- Scope references - -**************** - Best Practices -**************** - -#. **Always check if async is enabled** before using coroutine APIs -#. **Use appropriate scopes** to maintain context isolation -#. **Handle exceptions properly** in coroutine callbacks -#. **Register cleanup handlers** for long-running operations -#. **Use internal context** instead of global variables for coroutine data -#. **Remove one-time handlers** by returning ``false`` from switch handlers -#. **Validate coroutine state** before performing operations - -Example: Complete Coroutine Usage -================================= - -.. code:: c - - static uint32_t my_context_key = 0; - - PHP_MINIT_FUNCTION(my_extension) - { - my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); - ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(my_main_handler); - return SUCCESS; - } - - static bool my_main_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) - { - if (!is_enter) return true; - - // Initialize coroutine-specific data - zval init_data; - ZVAL_STRING(&init_data, "initialized"); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &init_data); - - return false; // Remove handler after initialization - } - - void my_async_operation(void) - { - if (!ZEND_ASYNC_IS_ENABLED()) { - return; // Fallback to synchronous operation - } - - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); - coro->internal_entry = my_coroutine_function; - - ZEND_ASYNC_ENQUEUE_COROUTINE(coro); - ZEND_ASYNC_SUSPEND(); // Wait for completion - } - -This comprehensive coroutine system enables powerful asynchronous programming patterns while -maintaining clean separation of concerns and proper resource management. diff --git a/docs/source/true_async_api/events.rst b/docs/source/true_async_api/events.rst deleted file mode 100644 index 1af86924f76c2..0000000000000 --- a/docs/source/true_async_api/events.rst +++ /dev/null @@ -1,301 +0,0 @@ -######## - Events -######## - -The TrueAsync API revolves around the concept of **events**. An event represents any asynchronous -action that can produce a result or error in the future. All specific event types share a common -base structure ``zend_async_event_t`` which provides memory management, state flags and a standard -lifecycle. - -******************* - Creating an Event -******************* - -To create an event you typically call one of the ``ZEND_ASYNC_NEW_*`` macros which delegate to the -underlying implementation. Each macro returns a structure that contains ``zend_async_event_t`` as -its first member. - -.. code:: c - - zend_async_timer_event_t *timer = ZEND_ASYNC_NEW_TIMER_EVENT(1000, false); - timer->base.start(&timer->base); - -The ``base`` field exposes the common functions ``start``, ``stop`` and ``dispose`` as well as -reference counting helpers. - -************* - Event Flags -************* - -Each event has a ``flags`` field that controls its state and behaviour. The most important flags -are: - -``ZEND_ASYNC_EVENT_F_CLOSED`` - The event has been closed and may no longer be started or stopped. - -``ZEND_ASYNC_EVENT_F_RESULT_USED`` - Indicates that the result will be consumed by an awaiting context. When this flag is set the - awaiting code keeps the result alive until it is processed in the exception handler. - -``ZEND_ASYNC_EVENT_F_EXC_CAUGHT`` - Marks that an error produced by the event was caught in a callback and will not be rethrown - automatically. - -``ZEND_ASYNC_EVENT_F_ZVAL_RESULT`` - Signals that the callback result is a ``zval`` pointer. The TrueAsync core will properly - increment the reference count before inserting the value into userland arrays. - -``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` - Specifies that the structure also acts as a Zend object implementing ``Awaitable``. - -``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` - The dispose handler must not free the memory of the event itself. This is used when the event is - embedded into another structure. - -``ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED`` - Set by the callback once it has fully processed an exception. If this flag is not set the - exception will be rethrown after callbacks finish. - -``ZEND_ASYNC_EVENT_F_REFERENCE`` - Indicates that this structure is only an event reference which stores a pointer to the real - event. - -``ZEND_ASYNC_EVENT_F_OBJ_REF`` - Marks that the event keeps a pointer to the Zend object in its ``extra_offset`` region so - reference counting works through the object. - -Convenience macros such as ``ZEND_ASYNC_EVENT_SET_CLOSED`` and -``ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED`` are provided to manipulate these flags. - -***************** - Event Callbacks -***************** - -The core maintains a dynamic vector of callbacks for each event. Implementations provide the -``add_callback`` and ``del_callback`` methods which internally use ``zend_async_callbacks_push`` and -``zend_async_callbacks_remove``. The ``zend_async_callbacks_notify`` helper iterates over all -registered callbacks and passes the result or exception. If a ``notify_handler`` is set on the event -it is invoked first and can adjust the result or exception as needed. The handler is expected to -call ``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` to forward the values to all listeners. The -``php-async`` extension provides convenience macros ``ZEND_ASYNC_CALLBACKS_NOTIFY`` for regular use, -``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` when called from inside a ``notify_handler``, and -``ZEND_ASYNC_CALLBACKS_NOTIFY_AND_CLOSE`` to close the event before notifying listeners. - -The following example shows a libuv poll event that dispatches its callbacks once the underlying -handle becomes readable: - -.. code:: c - - static void on_poll_event(uv_poll_t *handle, int status, int events) { - async_poll_event_t *poll = handle->data; - zend_object *exception = NULL; - - if (status < 0) { - exception = async_new_exception( - async_ce_input_output_exception, - "Input output error: %s", - uv_strerror(status) - ); - } - - poll->event.triggered_events = events; - - ZEND_ASYNC_CALLBACKS_NOTIFY(&poll->event.base, NULL, exception); - - if (exception != NULL) { - zend_object_release(exception); - } - } - -****************** - Event Coroutines -****************** - -Coroutines themselves are implemented as events using the ``zend_coroutine_t`` structure. When a -coroutine yields, its waker waits on multiple events and resumes the coroutine once any of them -triggers. - -.. code:: c - - // Spawn a coroutine that waits for a timer - zend_coroutine_t *co = ZEND_ASYNC_SPAWN(NULL); - zend_async_resume_when(co, &timer->base, false, zend_async_waker_callback_resolve, NULL); - zend_async_enqueue_coroutine(co); - -When the coroutine finishes execution the event triggers again to deliver the result or exception. -The coroutine implementation marks the callback result as a ``zval`` value using -``ZEND_ASYNC_EVENT_SET_ZVAL_RESULT``. Callback handlers may also set -``ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED`` to indicate that the thrown exception has been processed -and should not be rethrown by the runtime. - -****************** - Extending Events -****************** - -Custom event types embed ``zend_async_event_t`` at the beginning of their structure and may allocate -additional memory beyond the end of the struct. The ``extra_size`` argument in -``ZEND_ASYNC_NEW_*_EX`` controls how much extra space is reserved, and ``extra_offset`` records -where that region begins. - -.. code:: c - - // Allocate extra space for user data - zend_async_poll_event_t *poll = ZEND_ASYNC_NEW_POLL_EVENT_EX(fd, false, sizeof(my_data_t)); - my_data_t *data = (my_data_t *)((char*)poll + poll->base.extra_offset); - -The libuv backend defines event wrappers that embed libuv handles. A timer event, for example, -extends ``zend_async_timer_event_t`` as follows: - -.. code:: c - - typedef struct { - zend_async_timer_event_t event; - uv_timer_t uv_handle; - } async_timer_event_t; - - // Initialize callbacks for the event - event->event.base.add_callback = libuv_add_callback; - event->event.base.del_callback = libuv_remove_callback; - event->event.base.start = libuv_timer_start; - event->event.base.stop = libuv_timer_stop; - event->event.base.dispose = libuv_timer_dispose; - -Every extended event defines its own ``start``, ``stop`` and ``dispose`` functions. The dispose -handler must release all resources associated with the event and is called when the reference count -reaches ``1``. It is common to stop the event first and then close the underlying libuv handle so -that memory gets freed in the ``uv_close`` callback. - -.. code:: c - - static void libuv_timer_dispose(zend_async_event_t *event) - { - if (ZEND_ASYNC_EVENT_REF(event) > 1) { - ZEND_ASYNC_EVENT_DEL_REF(event); - return; - } - - if (event->loop_ref_count > 0) { - event->loop_ref_count = 1; - event->stop(event); - } - - zend_async_callbacks_free(event); - - async_timer_event_t *timer = (async_timer_event_t *)event; - uv_close((uv_handle_t *)&timer->uv_handle, libuv_close_handle_cb); - } - -If ``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` is set the dispose handler must not free the event memory -itself because the structure is embedded in another object (e.g. ``async_coroutine_t``). The libuv -close callback will only free the libuv handle in this case. - -************************ - Custom Event Callbacks -************************ - -Callbacks can also be extended to store additional state. The await logic in ``php-async`` defines a -callback that inherits from ``zend_coroutine_event_callback_t`` and keeps a reference to the -awaiting context: - -.. code:: c - - typedef struct { - zend_coroutine_event_callback_t callback; - async_await_context_t *await_context; - zval key; - zend_async_event_callback_dispose_fn prev_dispose; - } async_await_callback_t; - - async_await_callback_t *cb = ecalloc(1, sizeof(async_await_callback_t)); - cb->callback.base.callback = async_waiting_callback; - cb->await_context = ctx; - zend_async_resume_when(co, awaitable, false, NULL, &cb->callback); - -************************ - Events as Zend Objects -************************ - -If ``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` is set, the event also acts as a Zend object implementing -``Awaitable``. The ``zend_object_offset`` field stores the location of the ``zend_object`` within -the structure. Reference counting macros automatically use either the internal counter or -``GC_REFCOUNT`` depending on this flag. - -This allows events to be exposed to userland seamlessly while keeping the internal lifecycle -consistent. - -For events that are destroyed asynchronously (e.g. libuv timers) the actual event structure cannot -be a Zend object. Instead a lightweight reference structure is used. ``ZEND_ASYNC_EVENT_REF_PROLOG`` -reserves the required fields in the Zend object and ``ZEND_ASYNC_EVENT_REF_SET`` stores the pointer -to the real event together with the ``zend_object`` offset. The event must then be flagged with -``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` so that reference counting delegates to the object. - -When accessing the event from userland objects use ``ZEND_ASYNC_OBJECT_TO_EVENT`` and -``ZEND_ASYNC_EVENT_TO_OBJECT`` which handle both direct and reference-based layouts transparently. - -The ``php-async`` extension provides ``Async\\Timeout`` objects that embed a timer event. Recent -updates introduce a helper API for linking an event with a Zend object. A helper macro -``ZEND_ASYNC_EVENT_REF_PROLOG`` reserves fields at the beginning of the object to hold an event -reference. ``ZEND_ASYNC_EVENT_REF_SET`` stores the pointer to the newly created event and the offset -of the ``zend_object`` inside the structure. ``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` then marks the -event so reference counting will use the Zend object rather than the internal counter. - -The object factory now uses these helpers when creating the timer: - -.. code:: - - static zend_object *async_timeout_create(const zend_ulong ms, const bool is_periodic) - { - async_timeout_object_t *object = - ecalloc(1, sizeof(async_timeout_object_t) + - zend_object_properties_size(async_ce_timeout)); - - zend_async_event_t *event = (zend_async_event_t *) ZEND_ASYNC_NEW_TIMER_EVENT_EX( - ms, is_periodic, sizeof(async_timeout_ext_t) - ); - - if (UNEXPECTED(event == NULL)) { - efree(object); - return NULL; - } - - ZEND_ASYNC_EVENT_REF_SET(object, XtOffsetOf(async_timeout_object_t, std), (zend_async_timer_event_t *) event); - ZEND_ASYNC_EVENT_WITH_OBJECT_REF(event); - - async_timeout_ext_t *timeout = ASYNC_TIMEOUT_FROM_EVENT(event); - timeout->std = &object->std; - timeout->prev_dispose = event->dispose; - event->notify_handler = timeout_before_notify_handler; - event->dispose = async_timeout_event_dispose; - - zend_object_std_init(&object->std, async_ce_timeout); - object_properties_init(&object->std, async_ce_timeout); - object->std.handlers = &async_timeout_handlers; - - return &object->std; - } - - static void timeout_before_notify_handler(zend_async_event_t *event, - void *result, - zend_object *exception) - { - if (UNEXPECTED(exception != NULL)) { - ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER(event, result, exception); - return; - } - - zend_object *timeout_exception = async_new_exception( - async_ce_timeout_exception, - "Timeout occurred after %lu milliseconds", - ((zend_async_timer_event_t *) event)->timeout - ); - - ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER(event, result, timeout_exception); - zend_object_release(timeout_exception); - } - -.. note:: - - Events must not be exposed as Zend objects if their memory is released asynchronously. Zend - assumes that object destruction happens entirely during the ``zend_object_release`` call and - cannot wait for callbacks such as ``uv_close`` to free the underlying event. The - ``Async\\Timeout`` class will be redesigned to avoid this pattern. diff --git a/docs/source/true_async_api/examples.rst b/docs/source/true_async_api/examples.rst deleted file mode 100644 index aae751a5725bd..0000000000000 --- a/docs/source/true_async_api/examples.rst +++ /dev/null @@ -1,452 +0,0 @@ -########## - Examples -########## - -This section provides practical C code examples for using the TrueAsync API in PHP extensions. - -**************** - Basic Examples -**************** - -Checking API Availability -========================= - -Always check if async is enabled before using the API: - -.. code:: c - - #include "Zend/zend_async_API.h" - - void my_async_function(void) - { - if (!ZEND_ASYNC_IS_ENABLED()) { - // Fallback to synchronous operation - my_sync_function(); - return; - } - - // Use async API - my_actual_async_function(); - } - -Simple Coroutine Creation -========================= - -Creating and spawning a basic coroutine: - -.. code:: c - - static void my_coroutine_entry(void *arg) - { - php_printf("Hello from coroutine!\n"); - } - - void spawn_example(void) - { - if (!ZEND_ASYNC_IS_ENABLED()) return; - - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); - if (!coro) { - php_error(E_WARNING, "Failed to create coroutine"); - return; - } - - coro->internal_entry = my_coroutine_entry; - coro->internal_arg = NULL; - - ZEND_ASYNC_ENQUEUE_COROUTINE(coro); - } - -Using Scopes -============ - -Creating coroutines with custom scopes: - -.. code:: c - - void scope_example(void) - { - if (!ZEND_ASYNC_IS_ENABLED()) return; - - // Create new scope - zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(NULL); - if (!scope) return; - - // Spawn coroutine within scope - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH(scope); - if (coro) { - coro->internal_entry = my_coroutine_entry; - ZEND_ASYNC_ENQUEUE_COROUTINE(coro); - } - } - -********************************* - Context Switch Handler Examples -********************************* - -Per-Coroutine Handler -===================== - -Adding context switch handlers to specific coroutines: - -.. code:: c - - static bool my_switch_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) - { - if (is_finishing) { - php_printf("Coroutine finishing\n"); - return false; // Remove handler - } - - if (is_enter) { - php_printf("Entering coroutine\n"); - } else { - php_printf("Leaving coroutine\n"); - } - - return true; // Keep handler - } - - void add_handler_example(void) - { - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); - if (!coro) return; - - uint32_t handler_id = zend_coroutine_add_switch_handler(coro, my_switch_handler); - - coro->internal_entry = my_coroutine_entry; - ZEND_ASYNC_ENQUEUE_COROUTINE(coro); - - // Later: remove handler if needed - // zend_coroutine_remove_switch_handler(coro, handler_id); - } - -Global Main Coroutine Handler -============================= - -Handler that applies to all main coroutines: - -.. code:: c - - static uint32_t my_context_key = 0; - - static bool main_coroutine_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) - { - if (!is_enter) return true; - - // Initialize extension data for this coroutine - my_extension_data_t *data = ecalloc(1, sizeof(my_extension_data_t)); - data->initialized = true; - - // Store in coroutine internal context - zval data_zval; - ZVAL_PTR(&data_zval, data); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &data_zval); - - return false; // Remove handler after initialization - } - - PHP_MINIT_FUNCTION(my_extension) - { - my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); - zend_async_add_main_coroutine_start_handler(main_coroutine_handler); - return SUCCESS; - } - -****************** - Event Operations -****************** - -Basic Event Usage -================= - -Working with async events (using timer as example): - -.. code:: c - - static void timer_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, - void *result, zend_object *exception) - { - if (exception) { - php_printf("Timer error occurred\n"); - } else { - php_printf("Timer fired!\n"); - } - - // Clean up - ZEND_ASYNC_EVENT_DEL_REF(event); - } - - void timer_example(void) - { - if (!ZEND_ASYNC_REACTOR_IS_ENABLED()) return; - - // This would need actual reactor implementation - // zend_async_timer_event_t *timer = zend_async_new_timer_event_fn(1000, 0, false, 0); - // if (!timer) return; - - // zend_async_event_callback_t *callback = ...; - // timer->base.add_callback(&timer->base, callback); - // timer->base.start(&timer->base); - } - -*************************** - Internal Context Examples -*************************** - -Storing Extension Data -====================== - -Using internal context to store per-coroutine data: - -.. code:: c - - static uint32_t cache_context_key = 0; - - typedef struct { - HashTable *cached_results; - time_t last_access; - } cache_data_t; - - PHP_MINIT_FUNCTION(cache_extension) - { - cache_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("cache_data"); - return SUCCESS; - } - - cache_data_t* get_cache_data(zend_coroutine_t *coroutine) - { - zval *cached = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, cache_context_key); - if (cached && Z_TYPE_P(cached) == IS_PTR) { - return (cache_data_t *)Z_PTR_P(cached); - } - - // Create new cache data - cache_data_t *data = ecalloc(1, sizeof(cache_data_t)); - ALLOC_HASHTABLE(data->cached_results); - zend_hash_init(data->cached_results, 16, NULL, ZVAL_PTR_DTOR, 0); - data->last_access = time(NULL); - - // Store in context - zval data_zval; - ZVAL_PTR(&data_zval, data); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, cache_context_key, &data_zval); - - return data; - } - -************************* - Error Handling Examples -************************* - -Exception Creation -================== - -Creating and handling exceptions in async context: - -.. code:: c - - void async_operation_with_error(void) - { - if (!ZEND_ASYNC_IS_ENABLED()) return; - - zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); - if (!coro) { - zend_throw_error(NULL, "Failed to create coroutine"); - return; - } - - // Set up coroutine that might throw - coro->internal_entry = error_prone_operation; - ZEND_ASYNC_ENQUEUE_COROUTINE(coro); - } - - static void error_prone_operation(void *arg) - { - // Simulate error condition - if (some_error_condition()) { - zend_object *exception = zend_throw_exception( - zend_ce_exception, "Async operation failed", 0 - ); - - // Resume current coroutine with error - zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; - if (current) { - ZEND_ASYNC_RESUME_WITH_ERROR(current, exception, true); - } - return; - } - - php_printf("Operation completed successfully\n"); - } - -**************************** - Memory Management Examples -**************************** - -Reference Counting Examples -=========================== - -Proper event reference management: - -.. code:: c - - void event_reference_example(void) - { - // Assuming we have an event from somewhere - zend_async_event_t *event = get_some_event(); - - // Add reference when storing event - ZEND_ASYNC_EVENT_ADD_REF(event); - - // Store event somewhere - store_event_reference(event); - - // Later: remove reference when done - ZEND_ASYNC_EVENT_DEL_REF(event); - - // Or use RELEASE which decrements and disposes if needed - // ZEND_ASYNC_EVENT_RELEASE(event); - } - -******************************* - Futures and Channels Examples -******************************* - -Future Creation -=============== - -Creating and using futures for async results: - -.. code:: c - - void future_example(void) - { - if (!ZEND_ASYNC_IS_ENABLED()) return; - - // Create thread-safe future - zend_future_t *future = ZEND_ASYNC_NEW_FUTURE(true); - if (!future) return; - - // Future can be resolved from any thread - // zend_future_resolve(future, &result_zval); - - // Or with exception - // zend_future_reject(future, exception); - } - -Channel Communication -===================== - -Creating channels for coroutine communication: - -.. code:: c - - void channel_example(void) - { - if (!ZEND_ASYNC_IS_ENABLED()) return; - - // Create buffered channel - zend_async_channel_t *channel = ZEND_ASYNC_NEW_CHANNEL(10, true, false); - if (!channel) return; - - // Channels support send/receive operations - // channel->send(channel, &value); - // channel->receive(channel, &result); - } - -*********************** - Extension Integration -*********************** - -Complete Extension Example -========================== - -Full example of async-aware extension: - -.. code:: c - - static uint32_t my_ext_context_key = 0; - - typedef struct { - bool async_enabled; - HashTable *pending_operations; - } my_extension_context_t; - - static bool my_ext_main_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) - { - if (is_finishing) { - // Cleanup on coroutine completion - zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_ext_context_key); - if (ctx_zval && Z_TYPE_P(ctx_zval) == IS_PTR) { - my_extension_context_t *ctx = (my_extension_context_t *)Z_PTR_P(ctx_zval); - if (ctx->pending_operations) { - zend_hash_destroy(ctx->pending_operations); - FREE_HASHTABLE(ctx->pending_operations); - } - efree(ctx); - } - return false; - } - - if (!is_enter) return true; - - // Initialize context for new main coroutine - my_extension_context_t *ctx = ecalloc(1, sizeof(my_extension_context_t)); - ctx->async_enabled = true; - - ALLOC_HASHTABLE(ctx->pending_operations); - zend_hash_init(ctx->pending_operations, 8, NULL, ZVAL_PTR_DTOR, 0); - - zval ctx_zval; - ZVAL_PTR(&ctx_zval, ctx); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_ext_context_key, &ctx_zval); - - return false; // Remove handler after init - } - - PHP_MINIT_FUNCTION(my_extension) - { - my_ext_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); - - if (ZEND_ASYNC_IS_ENABLED()) { - zend_async_add_main_coroutine_start_handler(my_ext_main_handler); - } - - return SUCCESS; - } - - // Extension function that works async-aware - PHP_FUNCTION(my_async_operation) - { - if (!ZEND_ASYNC_IS_ENABLED()) { - // Fallback to sync operation - my_sync_operation(INTERNAL_FUNCTION_PARAM_PASSTHRU); - return; - } - - zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; - if (current) { - my_extension_context_t *ctx = get_my_context(current); - if (ctx && ctx->async_enabled) { - // Perform async operation - perform_async_operation(current); - return; - } - } - - // Default to sync - my_sync_operation(INTERNAL_FUNCTION_PARAM_PASSTHRU); - } - -************************* - Examples Best Practices -************************* - -#. **Always check enablement** before using async APIs -#. **Use internal context** for per-coroutine data storage -#. **Handle reference counting** properly for events -#. **Register cleanup handlers** for resource management -#. **Provide sync fallbacks** when async is not available -#. **Use appropriate scopes** for coroutine isolation -#. **Handle exceptions** properly in async context diff --git a/docs/source/true_async_api/implementation-guide.rst b/docs/source/true_async_api/implementation-guide.rst deleted file mode 100644 index 4a049e1cea3b3..0000000000000 --- a/docs/source/true_async_api/implementation-guide.rst +++ /dev/null @@ -1,396 +0,0 @@ -###################### - Implementation Guide -###################### - -This guide explains how to implement TrueAsync API backends and integrate them with the PHP core. It -covers the registration process, implementation patterns, and best practices. - -********** - Overview -********** - -Implementing a TrueAsync API backend involves: - -#. **Creating function implementations** that match the API signatures -#. **Registering implementations** with the core during module initialization -#. **Following memory management** and error handling conventions -#. **Implementing event lifecycle** methods (start, stop, dispose) -#. **Handling callbacks** and result delivery - -********************** - Registration Process -********************** - -The registration process connects your implementations to the core API. - -Module Initialization -===================== - -Register your implementations during ``PHP_MINIT_FUNCTION``: - -.. code:: c - - PHP_MINIT_FUNCTION(my_async_backend) { - // Register reactor implementations - bool success = zend_async_reactor_register( - "MyAsyncBackend", // Module name - false, // Allow override - my_reactor_startup, // Startup function - my_reactor_shutdown, // Shutdown function - my_reactor_execute, // Execute function - my_reactor_loop_alive, // Loop alive check - my_new_socket_event, // Socket event factory - my_new_poll_event, // Poll event factory - my_new_timer_event, // Timer event factory - my_new_signal_event, // Signal event factory - my_new_process_event, // Process event factory - my_new_thread_event, // Thread event factory - my_new_filesystem_event, // Filesystem event factory - my_getnameinfo, // DNS nameinfo - my_getaddrinfo, // DNS addrinfo - my_freeaddrinfo, // DNS cleanup - my_new_exec_event, // Exec event factory - my_exec // Exec function - ); - - if (!success) { - php_error_docref(NULL, E_CORE_ERROR, "Failed to register async backend"); - return FAILURE; - } - - return SUCCESS; - } - -Function Pointer Types -====================== - -All implementations must match the exact function pointer signatures defined in -``zend_async_API.h``: - -.. code:: c - - // DNS function signatures - typedef zend_async_dns_addrinfo_t* (*zend_async_getaddrinfo_t)( - const char *node, const char *service, - const struct addrinfo *hints, size_t extra_size - ); - - typedef void (*zend_async_freeaddrinfo_t)(struct addrinfo *ai); - - // Timer function signature - typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)( - const zend_ulong timeout, const zend_ulong nanoseconds, - const bool is_periodic, size_t extra_size - ); - -************************* - Implementation Patterns -************************* - -Event Structure Pattern -======================= - -All async events follow a common pattern with base event structure plus implementation-specific -data: - -.. code:: c - - // Your implementation structure - typedef struct _my_dns_addrinfo_s { - zend_async_dns_addrinfo_t event; // Base event (MUST be first) - my_event_loop_handle_t handle; // Your event loop handle - // Additional implementation-specific fields... - } my_dns_addrinfo_t; - -Factory Function Pattern -======================== - -Event factory functions create and initialize event structures: - -.. code:: c - - static zend_async_dns_addrinfo_t* my_getaddrinfo( - const char *node, const char *service, - const struct addrinfo *hints, size_t extra_size - ) { - // Allocate memory (include extra_size if specified) - size_t total_size = sizeof(my_dns_addrinfo_t) + extra_size; - my_dns_addrinfo_t *dns = pecalloc(1, total_size, 0); - - // Initialize base event fields - dns->event.base.extra_offset = sizeof(my_dns_addrinfo_t); - dns->event.base.ref_count = 1; - dns->event.base.add_callback = my_add_callback; - dns->event.base.del_callback = my_remove_callback; - dns->event.base.start = my_dns_start; - dns->event.base.stop = my_dns_stop; - dns->event.base.dispose = my_dns_dispose; - - // Store request parameters - dns->event.node = node; - dns->event.service = service; - dns->event.hints = hints; - - // Initialize your backend-specific handle - int result = my_event_loop_init_dns(&dns->handle, node, service, hints); - if (result != 0) { - my_throw_error("Failed to initialize DNS request: %s", my_strerror(result)); - pefree(dns, 0); - return NULL; - } - - // Link handle to event data - dns->handle.data = dns; - - return &dns->event; - } - -Lifecycle Methods -================= - -Implement the three lifecycle methods for each event type: - -.. code:: c - - // Start the async operation - static void my_dns_start(zend_async_event_t *event) { - EVENT_START_PROLOGUE(event); // Standard prologue - - my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; - - int result = my_event_loop_start_dns(&dns->handle, my_dns_callback); - if (result != 0) { - my_throw_error("Failed to start DNS operation: %s", my_strerror(result)); - return; - } - - event->loop_ref_count++; - ZEND_ASYNC_INCREASE_EVENT_COUNT; - } - - // Stop the async operation - static void my_dns_stop(zend_async_event_t *event) { - EVENT_STOP_PROLOGUE(event); // Standard prologue - - my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; - - my_event_loop_stop_dns(&dns->handle); - - event->loop_ref_count = 0; - ZEND_ASYNC_DECREASE_EVENT_COUNT; - } - - // Clean up and dispose - static void my_dns_dispose(zend_async_event_t *event) { - if (ZEND_ASYNC_EVENT_REF(event) > 1) { - ZEND_ASYNC_EVENT_DEL_REF(event); - return; - } - - // Stop if still running - if (event->loop_ref_count > 0) { - event->loop_ref_count = 1; - event->stop(event); - } - - // Free callbacks - zend_async_callbacks_free(event); - - my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; - - // Clean up backend-specific resources - my_event_loop_cleanup_dns(&dns->handle); - - // Free the structure - pefree(dns, 0); - } - -******************* - Callback Handling -******************* - -Event Completion Callbacks -========================== - -When your backend completes an operation, notify the core through callbacks: - -.. code:: c - - // Your backend callback (called by event loop) - static void my_dns_callback(my_event_loop_handle_t *handle, int status, struct addrinfo *result) { - my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)handle->data; - zend_object *exception = NULL; - - // Close the event after one-time operations - ZEND_ASYNC_EVENT_SET_CLOSED(&dns->event.base); - dns->event.base.stop(&dns->event.base); - - // Handle errors - if (status != 0) { - exception = my_new_exception( - "DNS resolution failed: %s", my_strerror(status) - ); - } - - // Notify all registered callbacks - zend_async_callbacks_notify(&dns->event.base, result, exception); - - // Release exception if created - if (exception) { - zend_object_release(exception); - } - } - -Result Memory Management -======================== - -For operations that return allocated memory (like ``getaddrinfo``): - -.. code:: c - - static void my_freeaddrinfo(struct addrinfo *ai) { - if (ai != NULL) { - my_event_loop_free_addrinfo(ai); // Use your backend's free function - } - } - -**Make sure your ``getaddrinfo`` results can be freed by your ``freeaddrinfo`` implementation.** - -**************** - Error Handling -**************** - -Error Reporting -=============== - -Use the standard PHP error reporting mechanisms: - -.. code:: c - - // For recoverable errors during operation - static void my_throw_error(const char *format, ...) { - va_list args; - va_start(args, format); - zend_string *message = zend_vstrpprintf(0, format, args); - va_end(args); - - zend_throw_error(NULL, ZSTR_VAL(message)); - zend_string_release(message); - } - - // For creating exception objects - static zend_object* my_new_exception(const char *format, ...) { - va_list args; - va_start(args, format); - zend_string *message = zend_vstrpprintf(0, format, args); - va_end(args); - - zend_object *ex = zend_throw_exception(zend_ce_exception, ZSTR_VAL(message), 0); - zend_string_release(message); - return ex; - } - -Exception Classes -================= - -Use appropriate exception classes for different error types: - -.. code:: c - - // DNS errors - zend_object *dns_ex = async_new_exception(async_ce_dns_exception, "DNS error: %s", error_msg); - - // I/O errors - zend_object *io_ex = async_new_exception(async_ce_input_output_exception, "I/O error: %s", error_msg); - - // Timeout errors - zend_object *timeout_ex = async_new_exception(async_ce_timeout_exception, "Operation timed out"); - -******************* - Memory Management -******************* - -Reference Counting -================== - -The TrueAsync API uses reference counting for event objects: - -.. code:: c - - // Add reference (when storing event somewhere) - ZEND_ASYNC_EVENT_ADD_REF(event); - - // Remove reference (when done with event) - ZEND_ASYNC_EVENT_DEL_REF(event); - - // Release event (decrements and disposes if needed) - ZEND_ASYNC_EVENT_RELEASE(event); - -Memory Allocation -================= - -Use persistent allocation for long-lived structures: - -.. code:: c - - // For event structures that may outlive a request - my_event_t *event = pecalloc(1, sizeof(my_event_t), 0); // Persistent - - // For temporary data - char *buffer = emalloc(size); // Request-scoped - -Extra Data Support -================== - -Support the ``extra_size`` parameter for extensions: - -.. code:: c - - static zend_async_timer_event_t* my_new_timer_event( - const zend_ulong timeout, const zend_ulong nanoseconds, - const bool is_periodic, size_t extra_size - ) { - // Allocate base size + extra space - size_t total_size = sizeof(my_timer_event_t) + extra_size; - my_timer_event_t *timer = pecalloc(1, total_size, 0); - - // Set extra offset so extensions can find their data - timer->event.base.extra_offset = sizeof(my_timer_event_t); - - // ... rest of initialization - } - -**************** - Best Practices -**************** - -Performance Considerations -========================== - -- **Minimize allocations** in hot paths (event creation/callback handling) -- **Use object pools** for frequently created/destroyed events -- **Batch operations** when possible in your event loop -- **Profile memory usage** to detect leaks early - -Thread Safety -============= - -- **Document thread safety** guarantees of your implementation -- **Use proper locking** if supporting multi-threaded access -- **Consider thread-local storage** for per-thread state - -Testing -======= - -- **Create unit tests** for each API function implementation -- **Test error conditions** and memory cleanup -- **Verify callback behavior** under various scenarios -- **Load test** with many concurrent operations - -Documentation -============= - -- **Document implementation-specific behavior** and limitations -- **Provide configuration options** for tuning performance -- **Include examples** showing integration with your event loop -- **Document version compatibility** with different backend versions diff --git a/docs/source/true_async_api/index.rst b/docs/source/true_async_api/index.rst deleted file mode 100644 index e9c33c023474a..0000000000000 --- a/docs/source/true_async_api/index.rst +++ /dev/null @@ -1,108 +0,0 @@ -############### - TrueAsync API -############### - -.. toctree:: - :hidden: - - architecture - events - coroutines - api-reference - implementation-guide - patterns - examples - -The TrueAsync API provides a comprehensive asynchronous programming interface for PHP extensions, -built on top of the Zend Engine. This API enables non-blocking operations for I/O, timers, signals, -and other asynchronous tasks while maintaining clean separation between the API definition and its -implementation. - -.. warning:: - - TrueAsync API is currently in development. The API may change in future versions. - -The TrueAsync API follows a strict architectural pattern where the PHP core (php-src) defines the -API interfaces, while extensions provide concrete implementations. This design ensures modularity, -testability, and allows for multiple backend implementations (libuv, epoll, etc.). - -************** - Key Features -************** - -- **Non-blocking I/O operations** - File, network, and socket operations -- **Timer and scheduling support** - Timeout handling and periodic tasks -- **Signal handling** - Asynchronous signal processing -- **DNS resolution** - Async getaddrinfo/getnameinfo with proper memory management -- **Process management** - Spawn and monitor external processes -- **Filesystem monitoring** - Watch for file and directory changes -- **Clean separation** - API definition vs implementation -- **Memory safety** - Proper reference counting and cleanup - -************** - Architecture -************** - -The TrueAsync API consists of two main components: - -**API Definition Layer** (``Zend/zend_async_API.h``) - Defines function pointer types, structures, and macros that form the public API. Located in the - php-src core and used by all consumers. - -**Implementation Layer** (``ext/async/``) - Provides concrete implementations using libuv or other event loop libraries. Registers - implementations with the core through registration functions. - -This separation allows php-src core to use async functionality without directly depending on any -specific implementation, enabling clean modular design and testing. - -***************** - Getting Started -***************** - -To use the TrueAsync API in your extension: - -#. Include the API header: ``#include "Zend/zend_async_API.h"`` -#. Check if async is enabled: ``ZEND_ASYNC_IS_ENABLED()`` -#. Use API macros like ``ZEND_ASYNC_GETADDRINFO()`` for operations -#. Handle results through callback mechanisms -#. Clean up resources with appropriate free functions - -For implementation details, see the :doc:`implementation-guide` section. - -*************** - Quick Example -*************** - -.. code:: c - - // Async DNS resolution - zend_async_dns_addrinfo_t *event = ZEND_ASYNC_GETADDRINFO("example.com", "80", &hints); - - // Register callback for completion - zend_async_resume_when(coroutine, &event->base, true, my_callback, NULL); - - // Later, in callback: clean up - ZEND_ASYNC_FREEADDRINFO(result); - -************************ - Documentation Sections -************************ - -:doc:`architecture` - Detailed explanation of the two-repository architecture and component separation. - -:doc:`coroutines` - Complete guide to coroutines, context switching, and internal context management. - -:doc:`api-reference` - Complete API reference with function signatures, macros, and data structures. - -:doc:`implementation-guide` - Guide for implementing TrueAsync API backends and registering with the core. - -:doc:`patterns` - Common patterns, best practices, and coding conventions for the API. - -:doc:`examples` - Practical C code examples showing how to use different API components. diff --git a/docs/source/true_async_api/patterns.rst b/docs/source/true_async_api/patterns.rst deleted file mode 100644 index a064be37e7271..0000000000000 --- a/docs/source/true_async_api/patterns.rst +++ /dev/null @@ -1,568 +0,0 @@ -########## - Patterns -########## - -This section covers common patterns and best practices for implementing TrueAsync API in PHP -extensions. - -********************** - Fundamental Patterns -********************** - -Async-Aware Function Pattern -============================ - -The standard pattern for making extension functions async-aware: - -.. code:: c - - PHP_FUNCTION(my_extension_function) - { - // 1. Check if async is enabled - if (!ZEND_ASYNC_IS_ENABLED()) { - // Fallback to synchronous implementation - my_sync_implementation(INTERNAL_FUNCTION_PARAM_PASSTHRU); - return; - } - - // 2. Check if we're in coroutine context - zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; - if (!current) { - // Not in coroutine, use sync - my_sync_implementation(INTERNAL_FUNCTION_PARAM_PASSTHRU); - return; - } - - // 3. Perform async operation - my_async_implementation(current, INTERNAL_FUNCTION_PARAM_PASSTHRU); - } - -API Enablement Check Pattern -============================ - -Always check API availability before using async features: - -.. code:: c - - void my_extension_init(void) - { - if (ZEND_ASYNC_IS_ENABLED()) { - // Initialize async-specific resources - init_async_resources(); - - if (ZEND_ASYNC_REACTOR_IS_ENABLED()) { - // Reactor is available, can use events - init_event_handlers(); - } - } - } - -***************************** - Context Management Patterns -***************************** - -Extension Context Initialization -================================ - -Pattern for setting up per-coroutine extension data: - -.. code:: c - - static uint32_t my_context_key = 0; - - typedef struct { - HashTable *cache; - zend_async_timer_event_t *cleanup_timer; - bool initialized; - } my_context_t; - - static bool my_context_initializer(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) - { - if (is_finishing) { - // Cleanup context - zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_context_key); - if (ctx_zval && Z_TYPE_P(ctx_zval) == IS_PTR) { - my_context_t *ctx = (my_context_t *)Z_PTR_P(ctx_zval); - cleanup_my_context(ctx); - } - return false; // Remove handler - } - - if (!is_enter) return true; - - // Initialize context - my_context_t *ctx = ecalloc(1, sizeof(my_context_t)); - ALLOC_HASHTABLE(ctx->cache); - zend_hash_init(ctx->cache, 16, NULL, ZVAL_PTR_DTOR, 0); - ctx->initialized = true; - - zval ctx_zval; - ZVAL_PTR(&ctx_zval, ctx); - ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &ctx_zval); - - return false; // One-time initialization - } - - PHP_MINIT_FUNCTION(my_extension) - { - my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); - - if (ZEND_ASYNC_IS_ENABLED()) { - zend_async_add_main_coroutine_start_handler(my_context_initializer); - } - - return SUCCESS; - } - -Context Access Pattern -====================== - -Safe pattern for accessing extension context: - -.. code:: c - - my_context_t* get_my_context(zend_coroutine_t *coroutine) - { - if (!coroutine) { - coroutine = ZEND_ASYNC_CURRENT_COROUTINE; - if (!coroutine) return NULL; - } - - zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_context_key); - if (!ctx_zval || Z_TYPE_P(ctx_zval) != IS_PTR) { - return NULL; - } - - my_context_t *ctx = (my_context_t *)Z_PTR_P(ctx_zval); - return ctx->initialized ? ctx : NULL; - } - -****************************** - Resource Management Patterns -****************************** - -RAII Pattern for Events -======================= - -Resource Acquisition Is Initialization pattern for async events: - -.. code:: c - - typedef struct { - zend_async_event_t *event; - bool owns_reference; - } event_guard_t; - - static event_guard_t* event_guard_create(zend_async_event_t *event, bool add_ref) - { - event_guard_t *guard = emalloc(sizeof(event_guard_t)); - guard->event = event; - guard->owns_reference = add_ref; - - if (add_ref) { - ZEND_ASYNC_EVENT_ADD_REF(event); - } - - return guard; - } - - static void event_guard_destroy(event_guard_t *guard) - { - if (guard->owns_reference && guard->event) { - ZEND_ASYNC_EVENT_DEL_REF(guard->event); - } - efree(guard); - } - - void my_operation_with_event(void) - { - zend_async_event_t *event = create_some_event(); - event_guard_t *guard = event_guard_create(event, true); - - // Use event... - - // Automatic cleanup - event_guard_destroy(guard); - } - -Cleanup Callback Pattern -======================== - -Registering cleanup callbacks for async operations: - -.. code:: c - - typedef struct { - void *user_data; - void (*cleanup_fn)(void *data); - } cleanup_context_t; - - static void cleanup_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, - void *result, zend_object *exception) - { - cleanup_context_t *ctx = (cleanup_context_t *)callback->arg; - - if (ctx->cleanup_fn && ctx->user_data) { - ctx->cleanup_fn(ctx->user_data); - } - - efree(ctx); - } - - void register_cleanup(zend_async_event_t *event, void *data, void (*cleanup_fn)(void*)) - { - cleanup_context_t *ctx = emalloc(sizeof(cleanup_context_t)); - ctx->user_data = data; - ctx->cleanup_fn = cleanup_fn; - - zend_async_event_callback_t *callback = zend_async_event_callback_new( - event, cleanup_callback, 0 - ); - callback->arg = ctx; - - event->add_callback(event, callback); - } - -************************* - Error Handling Patterns -************************* - -Exception Propagation Pattern -============================= - -Proper exception handling in async context: - -.. code:: c - - static void async_operation_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, - void *result, zend_object *exception) - { - zend_coroutine_t *coroutine = (zend_coroutine_t *)callback->arg; - - if (exception) { - // Propagate exception to coroutine - ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, true); - } else { - // Process result and resume normally - process_async_result(result); - ZEND_ASYNC_RESUME(coroutine); - } - - // Cleanup - ZEND_ASYNC_EVENT_DEL_REF(event); - } - -Error Recovery Pattern -====================== - -Handling recoverable errors in async operations: - -.. code:: c - - typedef struct { - int retry_count; - int max_retries; - void *operation_data; - } retry_context_t; - - static void retry_operation_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, - void *result, zend_object *exception) - { - retry_context_t *ctx = (retry_context_t *)callback->arg; - - if (exception && ctx->retry_count < ctx->max_retries) { - // Retry the operation - ctx->retry_count++; - php_error(E_NOTICE, "Retrying operation (attempt %d/%d)", - ctx->retry_count, ctx->max_retries); - - retry_async_operation(ctx); - return; - } - - // Either success or max retries reached - if (exception) { - handle_final_error(exception, ctx); - } else { - handle_success(result, ctx); - } - - efree(ctx); - } - -********************** - Performance Patterns -********************** - -Object Pooling Pattern -====================== - -Reusing objects to reduce allocation overhead: - -.. code:: c - - static zend_async_event_callback_t *callback_pool[MAX_POOL_SIZE]; - static int pool_size = 0; - static zend_mutex_t pool_mutex; - - static zend_async_event_callback_t* get_pooled_callback(void) - { - zend_mutex_lock(&pool_mutex); - - zend_async_event_callback_t *callback = NULL; - if (pool_size > 0) { - callback = callback_pool[--pool_size]; - } - - zend_mutex_unlock(&pool_mutex); - - if (!callback) { - callback = emalloc(sizeof(zend_async_event_callback_t)); - memset(callback, 0, sizeof(zend_async_event_callback_t)); - } - - return callback; - } - - static void return_pooled_callback(zend_async_event_callback_t *callback) - { - // Reset callback state - memset(callback, 0, sizeof(zend_async_event_callback_t)); - - zend_mutex_lock(&pool_mutex); - - if (pool_size < MAX_POOL_SIZE) { - callback_pool[pool_size++] = callback; - } else { - efree(callback); - } - - zend_mutex_unlock(&pool_mutex); - } - -Batch Operations Pattern -======================== - -Grouping operations for better performance: - -.. code:: c - - typedef struct { - zend_async_event_t **events; - int event_count; - int completed_count; - void (*completion_callback)(void *batch_data); - void *batch_data; - } batch_operation_t; - - static void batch_item_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, - void *result, zend_object *exception) - { - batch_operation_t *batch = (batch_operation_t *)callback->arg; - - __sync_fetch_and_add(&batch->completed_count, 1); - - if (batch->completed_count == batch->event_count) { - // All operations completed - if (batch->completion_callback) { - batch->completion_callback(batch->batch_data); - } - cleanup_batch(batch); - } - } - -********************** - Integration Patterns -********************** - -Reactor Integration Pattern -=========================== - -Integrating with external event loops: - -.. code:: c - - typedef struct { - my_event_loop_t *external_loop; - HashTable *pending_events; - bool integrated; - } reactor_integration_t; - - static void bridge_event_callback(my_external_event_t *ext_event, void *data) - { - zend_async_event_t *async_event = (zend_async_event_t *)data; - - // Convert external event result to async event result - void *result = convert_external_result(ext_event); - zend_object *exception = check_external_error(ext_event); - - // Notify async event callbacks - async_event->callbacks_notify(async_event, result, exception); - } - - bool integrate_with_reactor(my_event_loop_t *loop) - { - if (!ZEND_ASYNC_REACTOR_IS_ENABLED()) { - return false; - } - - reactor_integration_t *integration = emalloc(sizeof(reactor_integration_t)); - integration->external_loop = loop; - - ALLOC_HASHTABLE(integration->pending_events); - zend_hash_init(integration->pending_events, 32, NULL, NULL, 0); - - // Set up bridge between external loop and async events - loop->set_callback(loop, bridge_event_callback); - - return true; - } - -****************** - Testing Patterns -****************** - -Mock Implementation Pattern -=========================== - -Creating testable async code: - -.. code:: c - - #ifdef TESTING_MODE - - typedef struct { - zend_async_event_t base; - void *expected_result; - zend_object *expected_exception; - bool should_fail; - } mock_event_t; - - static void mock_event_start(zend_async_event_t *event) - { - mock_event_t *mock = (mock_event_t *)event; - - // Simulate immediate completion for testing - if (mock->should_fail) { - event->callbacks_notify(event, NULL, mock->expected_exception); - } else { - event->callbacks_notify(event, mock->expected_result, NULL); - } - } - - mock_event_t* create_mock_event(void *result, zend_object *exception) - { - mock_event_t *mock = emalloc(sizeof(mock_event_t)); - memset(mock, 0, sizeof(mock_event_t)); - - mock->base.start = mock_event_start; - mock->expected_result = result; - mock->expected_exception = exception; - mock->should_fail = (exception != NULL); - - return mock; - } - - #endif - -******************* - Advanced Patterns -******************* - -State Machine Pattern -===================== - -Managing complex async state transitions: - -.. code:: c - - typedef enum { - STATE_IDLE, - STATE_CONNECTING, - STATE_CONNECTED, - STATE_SENDING, - STATE_RECEIVING, - STATE_ERROR, - STATE_CLOSED - } connection_state_t; - - typedef struct { - connection_state_t state; - zend_async_event_t *current_event; - void (*state_handlers[STATE_CLOSED + 1])(void *connection); - void *connection_data; - } async_state_machine_t; - - static void transition_state(async_state_machine_t *sm, connection_state_t new_state) - { - if (sm->state != new_state) { - sm->state = new_state; - - if (sm->state_handlers[new_state]) { - sm->state_handlers[new_state](sm->connection_data); - } - } - } - -Producer-Consumer Pattern -========================= - -Implementing async producer-consumer with channels: - -.. code:: c - - typedef struct { - zend_async_channel_t *channel; - zend_coroutine_t *producer_coro; - zend_coroutine_t *consumer_coro; - bool shutdown_requested; - } producer_consumer_t; - - static void producer_coroutine(void *arg) - { - producer_consumer_t *pc = (producer_consumer_t *)arg; - - while (!pc->shutdown_requested) { - // Produce data - zval data; - produce_data(&data); - - // Send to channel (may suspend if channel is full) - if (!pc->channel->send(pc->channel, &data)) { - break; // Channel closed - } - } - } - - static void consumer_coroutine(void *arg) - { - producer_consumer_t *pc = (producer_consumer_t *)arg; - - while (!pc->shutdown_requested) { - zval data; - - // Receive from channel (may suspend if channel is empty) - if (!pc->channel->receive(pc->channel, &data)) { - break; // Channel closed - } - - // Process data - process_data(&data); - zval_dtor(&data); - } - } - -************************ - Best Practices Summary -************************ - -#. **Always check enablement** before using async APIs -#. **Provide sync fallbacks** for non-async environments -#. **Use internal context** for per-coroutine data -#. **Handle reference counting** properly -#. **Register cleanup handlers** for resource management -#. **Use appropriate error handling** patterns -#. **Consider performance** with pooling and batching -#. **Test thoroughly** with mock implementations -#. **Document async behavior** clearly for users -#. **Follow reactor integration** patterns for external loops From e32de6b5120ea506d0516a3102463f98b49dafff Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:48:36 +0300 Subject: [PATCH 122/137] + true async doc --- docs/source/index.rst | 6 + docs/source/miscellaneous/writing-tests.rst | 22 +- docs/source/true_async_api/api-reference.rst | 424 +++++++++++++ docs/source/true_async_api/architecture.rst | 27 + docs/source/true_async_api/coroutines.rst | 469 +++++++++++++++ docs/source/true_async_api/events.rst | 301 ++++++++++ docs/source/true_async_api/examples.rst | 452 ++++++++++++++ .../true_async_api/implementation-guide.rst | 396 ++++++++++++ docs/source/true_async_api/index.rst | 108 ++++ docs/source/true_async_api/patterns.rst | 568 ++++++++++++++++++ 10 files changed, 2762 insertions(+), 11 deletions(-) create mode 100644 docs/source/true_async_api/api-reference.rst create mode 100644 docs/source/true_async_api/architecture.rst create mode 100644 docs/source/true_async_api/coroutines.rst create mode 100644 docs/source/true_async_api/events.rst create mode 100644 docs/source/true_async_api/examples.rst create mode 100644 docs/source/true_async_api/implementation-guide.rst create mode 100644 docs/source/true_async_api/index.rst create mode 100644 docs/source/true_async_api/patterns.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 21e2526f47f64..de0cd477c469d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,6 +15,12 @@ core/data-structures/index +.. toctree:: + :caption: TrueAsync API + :hidden: + + true_async_api/index + .. toctree:: :caption: Miscellaneous :hidden: diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.rst index fd09d80f1275e..46f03f94703ad 100644 --- a/docs/source/miscellaneous/writing-tests.rst +++ b/docs/source/miscellaneous/writing-tests.rst @@ -95,13 +95,13 @@ might use a variation tests to test boundary conditions. How big is a test case? ======================= -Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The reason -for this is that if we break something in PHP and it breaks your test case we need to be able to -find out quite quickly what we broke, going through 1000s of line of test case output is not easy. -Having said that it's sometimes just not practical to stay within the 10 line guideline, in this -case you can help a lot by commenting the output. You may find plenty of much longer tests in PHP - -the small tests message is something that we learnt over time, in fact we are slowly going through -and splitting tests up when we need to. +Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The +reason for this is that if we break something in PHP and it breaks your test case we need to be able +to find out quite quickly what we broke, going through 1000s of line of test case output is not +easy. Having said that it's sometimes just not practical to stay within the 10 line guideline, in +this case you can help a lot by commenting the output. You may find plenty of much longer tests in +PHP - the small tests message is something that we learnt over time, in fact we are slowly going +through and splitting tests up when we need to. Comments ======== @@ -183,10 +183,10 @@ Testing your test cases Most people who write tests for PHP don't have access to a huge number of operating systems but the tests are run on every system that runs PHP. It's good to test your test on as many platforms as you -can — Linux and Windows are the most important, it's increasingly important to make sure that tests -run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — don't -worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of failures on -other platforms. If you don't have karma to commit have a look at the next section. +can — Linux and Windows are the most important, it's increasingly important to make sure that +tests run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — +don't worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of +failures on other platforms. If you don't have karma to commit have a look at the next section. When you are testing your test case it's really important to make sure that you clean up any temporary resources (eg files) that you used in the test. There is a special ``--CLEAN--`` section diff --git a/docs/source/true_async_api/api-reference.rst b/docs/source/true_async_api/api-reference.rst new file mode 100644 index 0000000000000..5deecf10dbd7c --- /dev/null +++ b/docs/source/true_async_api/api-reference.rst @@ -0,0 +1,424 @@ +############### + API Reference +############### + +This section provides comprehensive documentation for the TrueAsync API functions, macros, and data +structures based on the actual implementation in ``Zend/zend_async_API.h``. + +************* + API Version +************* + +.. code:: c + + #define ZEND_ASYNC_API "TrueAsync API v0.4.0" + #define ZEND_ASYNC_API_VERSION_MAJOR 0 + #define ZEND_ASYNC_API_VERSION_MINOR 4 + #define ZEND_ASYNC_API_VERSION_PATCH 0 + +***************** + Core API Macros +***************** + +Enablement Check +================ + +.. code:: c + + ZEND_ASYNC_IS_ENABLED() + +Returns ``true`` if async functionality is available, ``false`` otherwise. Always check this before +using any async APIs. + +.. code:: c + + ZEND_ASYNC_REACTOR_IS_ENABLED() + +Returns ``true`` if reactor (event loop) is available and functional. + +********************** + Coroutine Operations +********************** + +Coroutine Creation +================== + +.. code:: c + + ZEND_ASYNC_SPAWN() + ZEND_ASYNC_SPAWN_WITH(scope) + ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider) + ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority) + ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority) + +Creates and spawns new coroutines with various configuration options: + +- ``ZEND_ASYNC_SPAWN()`` - Create coroutine with default settings +- ``ZEND_ASYNC_SPAWN_WITH(scope)`` - Create coroutine within specific scope +- ``ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider)`` - Use scope provider object +- ``ZEND_ASYNC_SPAWN_WITH_PRIORITY(priority)`` - Set coroutine priority +- ``ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority)`` - Both scope and priority + +**Priority levels:** + +.. code:: c + + typedef enum { + ZEND_COROUTINE_NORMAL = 0, + ZEND_COROUTINE_HI_PRIORITY = 255 + } zend_coroutine_priority; + +Coroutine Management +==================== + +.. code:: c + + ZEND_ASYNC_NEW_COROUTINE(scope) + +Creates a new coroutine object without starting it. + +.. code:: c + + ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine) + +Adds coroutine to execution queue for scheduling. + +.. code:: c + + ZEND_ASYNC_SUSPEND() + ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN() + +Suspends current coroutine execution: + +- ``SUSPEND()`` - Normal suspension +- ``RUN_SCHEDULER_AFTER_MAIN()`` - Suspend from main context + +Coroutine Control +================= + +.. code:: c + + ZEND_ASYNC_RESUME(coroutine) + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error) + +Resume coroutine execution: + +- ``RESUME(coroutine)`` - Normal resume +- ``RESUME_WITH_ERROR(coroutine, error, transfer_error)`` - Resume with exception + +.. code:: c + + ZEND_ASYNC_CANCEL(coroutine, error, transfer_error) + ZEND_ASYNC_CANCEL_EX(coroutine, error, transfer_error, is_safely) + +Cancel coroutine execution: + +- ``CANCEL()`` - Cancel with default safety +- ``CANCEL_EX()`` - Cancel with explicit safety flag + +*********** + Scope API +*********** + +Scope Creation +============== + +.. code:: c + + ZEND_ASYNC_NEW_SCOPE(parent) + ZEND_ASYNC_NEW_SCOPE_WITH_OBJECT(parent) + +Create new async scopes for coroutine isolation: + +- ``NEW_SCOPE(parent)`` - Create scope with parent reference +- ``NEW_SCOPE_WITH_OBJECT(parent)`` - Create scope backed by zend_object + +Scope Flags +=========== + +.. code:: c + + #define ZEND_ASYNC_SCOPE_IS_CLOSED(scope) + #define ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) + #define ZEND_ASYNC_SCOPE_IS_DISPOSING(scope) + +Check scope status: + +- ``IS_CLOSED`` - Scope has been closed +- ``IS_CANCELLED`` - Scope was cancelled +- ``IS_DISPOSING`` - Scope is being disposed + +*********** + Event API +*********** + +Event Lifecycle +=============== + +All async events implement these core function pointers: + +.. code:: c + + typedef void (*zend_async_event_start_t)(zend_async_event_t *event); + typedef void (*zend_async_event_stop_t)(zend_async_event_t *event); + typedef void (*zend_async_event_dispose_t)(zend_async_event_t *event); + +Event State Checking +==================== + +.. code:: c + + ZEND_ASYNC_EVENT_IS_CLOSED(event) + +Check if event has been closed. + +Event Reference Counting +======================== + +.. code:: c + + ZEND_ASYNC_EVENT_REF(event) + ZEND_ASYNC_EVENT_ADD_REF(event) + ZEND_ASYNC_EVENT_DEL_REF(event) + +Manage event reference counts for memory safety. + +Event Flags +=========== + +.. code:: c + + #define ZEND_ASYNC_EVENT_F_CLOSED (1u << 0) /* event was closed */ + #define ZEND_ASYNC_EVENT_F_RESULT_USED (1u << 1) /* result will be used */ + #define ZEND_ASYNC_EVENT_F_EXC_CAUGHT (1u << 2) /* error was caught */ + #define ZEND_ASYNC_EVENT_F_ZVAL_RESULT (1u << 3) /* result is zval */ + +.. code:: c + + ZEND_ASYNC_EVENT_SET_CLOSED(event) + ZEND_ASYNC_EVENT_CLR_CLOSED(event) + +Manipulate event flags. + +************* + Reactor API +************* + +Reactor Control +=============== + +.. code:: c + + ZEND_ASYNC_REACTOR_STARTUP() + +Start the async reactor (event loop). + +Poll Events +=========== + +.. code:: c + + typedef enum { + ASYNC_READABLE = 1, + ASYNC_WRITABLE = 2, + ASYNC_DISCONNECT = 4, + ASYNC_PRIORITIZED = 8 + } async_poll_event; + +Event flags for socket polling operations. + +****************** + Signal Constants +****************** + +.. code:: c + + #define ZEND_ASYNC_SIGHUP 1 + #define ZEND_ASYNC_SIGINT 2 + #define ZEND_ASYNC_SIGQUIT 3 + #define ZEND_ASYNC_SIGILL 4 + #define ZEND_ASYNC_SIGTERM 15 + #define ZEND_ASYNC_SIGBREAK 21 + #define ZEND_ASYNC_SIGABRT 22 + #define ZEND_ASYNC_SIGWINCH 28 + +Cross-platform signal constants for async signal handling. + +******************* + Process Execution +******************* + +Execution Modes +=============== + +.. code:: c + + typedef enum { + ZEND_ASYNC_EXEC_MODE_EXEC, /* exec() - last line only */ + ZEND_ASYNC_EXEC_MODE_SYSTEM, /* system() - print + return last */ + ZEND_ASYNC_EXEC_MODE_EXEC_ARRAY, /* exec() with array output */ + ZEND_ASYNC_EXEC_MODE_PASSTHRU, /* passthru() - binary output */ + ZEND_ASYNC_EXEC_MODE_SHELL_EXEC /* shell_exec() - buffer output */ + } zend_async_exec_mode; + +************************ + Future and Channel API +************************ + +Futures +======= + +.. code:: c + + ZEND_ASYNC_NEW_FUTURE(thread_safe) + ZEND_ASYNC_NEW_FUTURE_EX(thread_safe, extra_size) + +Create future objects for async result handling. + +Channels +======== + +.. code:: c + + ZEND_ASYNC_NEW_CHANNEL(buffer_size, resizable, thread_safe) + ZEND_ASYNC_NEW_CHANNEL_EX(buffer_size, resizable, thread_safe, extra_size) + +Create channels for coroutine communication. + +***************************** + API Context Switch Handlers +***************************** + +Registration +============ + +.. code:: c + + uint32_t zend_coroutine_add_switch_handler( + zend_coroutine_t *coroutine, + zend_coroutine_switch_handler_fn handler + ); + + bool zend_coroutine_remove_switch_handler( + zend_coroutine_t *coroutine, + uint32_t handler_index + ); + +Add/remove context switch handlers for individual coroutines. + +Global Handlers +=============== + +.. code:: c + + void zend_async_add_main_coroutine_start_handler( + zend_coroutine_switch_handler_fn handler + ); + +Add global handlers that are called when main coroutines start. + +Handler Function Type +===================== + +.. code:: c + + typedef bool (*zend_coroutine_switch_handler_fn)( + zend_coroutine_t *coroutine, + bool is_enter, /* true = entering, false = leaving */ + bool is_finishing /* true = coroutine finishing */ + ); + +Returns ``true`` to keep handler, ``false`` to remove it after execution. + +******************* + Utility Functions +******************* + +System Management +================= + +.. code:: c + + ZEND_ASYNC_SHUTDOWN() + ZEND_ASYNC_ENGINE_SHUTDOWN() + +Initiate graceful shutdown of async subsystem. + +.. code:: c + + ZEND_ASYNC_GET_COROUTINES() + ZEND_ASYNC_GET_AWAITING_INFO(coroutine) + +Get information about active coroutines and their await status. + +Microtasks +========== + +.. code:: c + + ZEND_ASYNC_ADD_MICROTASK(microtask) + +Add microtask to execution queue. + +Exception Handling +================== + +.. code:: c + + ZEND_ASYNC_SPAWN_AND_THROW(exception, scope, priority) + +Spawn coroutine specifically for throwing an exception within a scope. + +********************** + API Error Management +********************** + +API Exception Classes +===================== + +.. code:: c + + typedef enum { + ZEND_ASYNC_CLASS_NO = 0, + ZEND_ASYNC_CLASS_AWAITABLE = 1, + ZEND_ASYNC_CLASS_COROUTINE = 2 + } zend_async_class; + + ZEND_ASYNC_GET_CE(type) + ZEND_ASYNC_GET_EXCEPTION_CE(type) + +Get class entries for async-related classes and exceptions. + +************ + Data Types +************ + +Platform Types +============== + +.. code:: c + + #ifdef PHP_WIN32 + typedef HANDLE zend_file_descriptor_t; + typedef DWORD zend_process_id_t; + typedef HANDLE zend_process_t; + typedef SOCKET zend_socket_t; + #else + typedef int zend_file_descriptor_t; + typedef pid_t zend_process_id_t; + typedef pid_t zend_process_t; + typedef int zend_socket_t; + #endif + +Cross-platform type definitions for file descriptors, processes, and sockets. + +******************** + API Best Practices +******************** + +#. **Always check enablement** with ``ZEND_ASYNC_IS_ENABLED()`` before using APIs +#. **Use appropriate scopes** for coroutine isolation +#. **Handle reference counting** properly with event objects +#. **Register cleanup handlers** using context switch handlers +#. **Check reactor availability** before using event-based operations +#. **Use priority levels** appropriately for coroutine scheduling diff --git a/docs/source/true_async_api/architecture.rst b/docs/source/true_async_api/architecture.rst new file mode 100644 index 0000000000000..8a61ab8ae6b2f --- /dev/null +++ b/docs/source/true_async_api/architecture.rst @@ -0,0 +1,27 @@ +############## + Architecture +############## + +The TrueAsync API follows a clean separation-of-concerns architecture that splits API definition +from implementation. This design enables modularity, testability, and support for multiple backend +implementations. + +The **True Async API** is divided into three main functional blocks: + +- **Scheduler** Manages coroutines and the main event loop. +- **Reactor** Handles everything related to input/output (I/O), including sockets, timers, and file + descriptors. +- **Thread Pool** Allows execution of blocking or heavy tasks in a thread pool. + +--- + +## Implementation Flexibility + +The True Async API provides clearly separated interfaces for each block, allowing them to be +implemented independently—even across different PHP extensions. For example: + +- The Scheduler can be implemented as part of the PHP core. +- The Reactor can be implemented in a separate extension using `libuv`. +- The Thread Pool can be based on native threads or a library like `pthreads`. + +This approach gives developers maximum flexibility and control over asynchronous execution. diff --git a/docs/source/true_async_api/coroutines.rst b/docs/source/true_async_api/coroutines.rst new file mode 100644 index 0000000000000..8e94cc71b9250 --- /dev/null +++ b/docs/source/true_async_api/coroutines.rst @@ -0,0 +1,469 @@ +############ + Coroutines +############ + +Coroutines are the core building blocks of the TrueAsync API. They represent lightweight execution +contexts that can be suspended and resumed, enabling cooperative multitasking without traditional +thread overhead. Every coroutine is implemented as an event (``zend_coroutine_t`` extends +``zend_async_event_t``) and participates in the standard event lifecycle. + +****************** + Coroutine Basics +****************** + +A coroutine represents a function or code block that can be paused during execution and resumed +later. Unlike traditional threads, coroutines are cooperatively scheduled - they yield control +voluntarily rather than being preemptively interrupted. + +Key characteristics: + +- **Lightweight** - Minimal memory overhead compared to threads +- **Cooperative** - Explicit yield points, no race conditions +- **Event-based** - Coroutines are events that can await other events +- **Scoped** - Each coroutine runs within a scope for context isolation + +********************* + Creating Coroutines +********************* + +The TrueAsync API provides several ways to create coroutines: + +.. code:: c + + // Create a new coroutine within default scope + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + + // Create with specific scope + zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(parent_scope); + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH(scope); + + // Create with scope provider object + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH_PROVIDER(scope_provider); + +Coroutines can be either: + +**Userland coroutines** + Execute PHP code through ``zend_fcall_t`` callback mechanism + +**Internal coroutines** + Execute C code through ``zend_coroutine_entry_t`` function pointer + +********************* + Coroutine Lifecycle +********************* + +Coroutines follow a well-defined lifecycle: + +#. **Creation** - Coroutine is spawned but not yet started +#. **Enqueue** - Added to execution queue via ``ZEND_ASYNC_ENQUEUE_COROUTINE()`` +#. **Execution** - Runs until suspension point or completion +#. **Suspension** - Yields control while waiting for events +#. **Resume** - Continues execution when events complete +#. **Completion** - Finishes with result or exception + +.. code:: c + + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + + // Set up coroutine execution + coro->fcall = &my_function_call; + coro->scope = current_scope; + + // Start execution + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + + // Later: resume with result + ZEND_ASYNC_RESUME(coro); + + // Or resume with error + ZEND_ASYNC_RESUME_WITH_ERROR(coro, exception, transfer_error); + +******************* + Coroutine Context +******************* + +Each coroutine maintains its own context information: + +**Scope Context** (``zend_async_scope_t *scope``) + Links to parent scope for hierarchical context management + +**Async Context** (``zend_async_context_t *context``) + User-defined context variables accessible from PHP code + +**Internal Context** (``HashTable *internal_context``) + Extension-private data storage with numeric keys + +**Switch Handlers** (``zend_coroutine_switch_handlers_vector_t *switch_handlers``) + Functions called when entering/leaving the coroutine + +************************* + Context Switch Handlers +************************* + +Context switch handlers are one of the most powerful features of the coroutine system. They allow +extensions to hook into coroutine context switches and perform necessary cleanup, initialization, or +state management. + +Handler Function Signature +========================== + +.. code:: c + + typedef bool (*zend_coroutine_switch_handler_fn)( + zend_coroutine_t *coroutine, + bool is_enter, /* true = entering coroutine, false = leaving */ + bool is_finishing /* true = coroutine is finishing */ + /* returns: true = keep handler, false = remove handler after execution */ + ); + +The handler receives three parameters: + +- **coroutine** - The coroutine being switched to/from +- **is_enter** - ``true`` when entering coroutine, ``false`` when leaving +- **is_finishing** - ``true`` when coroutine is finishing execution + +The return value determines handler lifetime: + +- **true** - Keep handler for future context switches +- **false** - Remove handler immediately after execution + +This allows handlers to perform one-time initialization and then remove themselves automatically. + +Registering Handlers +==================== + +There are two types of switch handlers: + +**Per-Coroutine Handlers** + +.. code:: c + + // Add handler to specific coroutine + uint32_t handler_id = ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, my_handler); + + // Remove specific handler + zend_coroutine_remove_switch_handler(coroutine, handler_id); + +**Global Main Coroutine Handlers** + +.. code:: c + + // Register global handler for all main coroutines + ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(my_main_handler); + +Global handlers are automatically copied to each main coroutine when it starts and can remove +themselves by returning ``false``. + +Practical Example: Output Buffering +=================================== + +The ``main/output.c`` module provides an excellent example of context switch handlers in action. The +output system needs to isolate output buffers between the main execution context and coroutines to +prevent buffer conflicts. + +.. code:: c + + static bool php_output_main_coroutine_start_handler( + zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (false == is_enter || OG(handlers).elements == 0) { + return false; /* Remove handler - no work to do */ + } + + /* Create coroutine-specific output context */ + php_output_context_t *ctx = ecalloc(1, sizeof(php_output_context_t)); + php_output_init_async_context(ctx); + + /* Copy output handlers from global to coroutine context */ + php_output_handler **src_handlers = (php_output_handler **)zend_stack_base(&OG(handlers)); + int handler_count = zend_stack_count(&OG(handlers)); + + for (int i = 0; i < handler_count; i++) { + php_output_handler *handler = src_handlers[i]; + zend_stack_push(&ctx->handlers, &handler); + } + + /* Store context in coroutine internal_context */ + zval ctx_zval; + ZVAL_PTR(&ctx_zval, ctx); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, php_output_context_key, &ctx_zval); + + /* Clean global handlers to avoid conflicts */ + zend_stack_destroy(&OG(handlers)); + zend_stack_init(&OG(handlers), sizeof(php_output_handler *)); + OG(active) = NULL; + + /* Add cleanup callback for coroutine completion */ + zend_coroutine_event_callback_t *cleanup_callback = + zend_async_coroutine_callback_new(coroutine, php_output_coroutine_cleanup_callback, 0); + coroutine->event.add_callback(&coroutine->event, &cleanup_callback->base); + + return false; /* Remove handler - initialization is complete */ + } + +This handler: + +#. **Detects main coroutine entry** - Only acts when ``is_enter`` is true +#. **Creates isolated context** - Copies output handlers to coroutine-specific storage +#. **Stores in internal_context** - Uses numeric key for fast access +#. **Cleans global state** - Prevents conflicts between main and coroutine contexts +#. **Registers cleanup** - Ensures proper resource cleanup on coroutine completion +#. **Removes itself** - Returns ``false`` since initialization is one-time only + +When to Use Context Switch Handlers +=================================== + +Context switch handlers are ideal for: + +**Resource Isolation** + Separating global state between main execution and coroutines (like output buffers, error + handlers, etc.) + +**Context Migration** + Moving data from one execution context to another + +**State Initialization** + Setting up coroutine-specific resources or configurations + +**Cleanup Management** + Registering cleanup callbacks for coroutine completion + +**Performance Optimization** + Pre-computing or caching data when entering frequently-used coroutines + +********************** + Internal Context API +********************** + +The internal context system provides a type-safe, efficient way for PHP extensions to store private +data associated with coroutines. Unlike userland context variables, internal context uses numeric +keys for faster access and is completely isolated from PHP code. + +Why Internal Context? +===================== + +Before internal context, extensions had limited options for storing coroutine-specific data: + +- **Global variables** - Not coroutine-safe, cause conflicts +- **Object properties** - Not available for internal coroutines +- **Manual management** - Complex, error-prone cleanup + +Internal context solves these problems by providing: + +- **Automatic cleanup** - Data is freed when coroutine completes +- **Type safety** - Values stored as ``zval`` with proper reference counting +- **Uniqueness** - Each extension gets private numeric keys +- **Performance** - Hash table lookup by integer key + +Key Allocation +============== + +Extensions allocate unique keys during module initialization: + +.. code:: c + + static uint32_t my_extension_context_key = 0; + + PHP_MINIT_FUNCTION(my_extension) + { + // Allocate unique key for this extension + my_extension_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension_data"); + return SUCCESS; + } + +The ``ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC`` macro: + +- Takes a static string identifier (for debugging) +- Returns a unique numeric key +- Validates that the same string address always gets the same key +- Is thread-safe in ZTS builds + +Storing and Retrieving Data +=========================== + +.. code:: c + + void my_coroutine_handler(zend_coroutine_t *coroutine) + { + // Store data in coroutine context + zval my_data; + ZVAL_LONG(&my_data, 42); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_extension_context_key, &my_data); + + // Later: retrieve data + zval *stored_data = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_extension_context_key); + if (stored_data && Z_TYPE_P(stored_data) == IS_LONG) { + long value = Z_LVAL_P(stored_data); + // Use value... + } + + // Optional: remove data explicitly + ZEND_ASYNC_INTERNAL_CONTEXT_UNSET(coroutine, my_extension_context_key); + } + +The API automatically handles: + +- **Reference counting** - ``zval`` reference counts are managed properly +- **Memory management** - All context data is freed when coroutine ends +- **Thread safety** - Operations are safe in ZTS builds + +Advanced Usage Patterns +======================= + +**Caching expensive computations:** + +.. code:: c + + zval *cached_result = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, cache_key); + if (!cached_result) { + // Expensive operation + zval computed_value; + ZVAL_STRING(&computed_value, expensive_computation()); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, cache_key, &computed_value); + cached_result = &computed_value; + } + +**Tracking state across suspensions:** + +.. code:: c + + // Before suspending + zval state; + ZVAL_LONG(&state, OPERATION_IN_PROGRESS); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, state_key, &state); + + // After resuming + zval *state_val = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, state_key); + if (state_val && Z_LVAL_P(state_val) == OPERATION_IN_PROGRESS) { + // Continue operation... + } + +**Storing complex objects:** + +.. code:: c + + zval object_container; + ZVAL_OBJ(&object_container, my_object); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, object_key, &object_container); + // Object reference count is automatically incremented + +Best Practices +============== + +#. **Allocate keys in MINIT** - Ensures keys are available when needed +#. **Use descriptive key names** - Helps with debugging and maintenance +#. **Check return values** - ``FIND`` returns ``NULL`` if key doesn't exist +#. **Validate data types** - Always check ``Z_TYPE_P`` before accessing data +#. **Clean up appropriately** - Use ``UNSET`` for early cleanup if needed + +*************** + Coroutine API +*************** + +Core Functions +============== + +.. code:: c + + // Coroutine creation and management + zend_coroutine_t *ZEND_ASYNC_SPAWN(); + zend_coroutine_t *ZEND_ASYNC_NEW_COROUTINE(scope); + void ZEND_ASYNC_ENQUEUE_COROUTINE(coroutine); + + // Execution control + void ZEND_ASYNC_SUSPEND(); + void ZEND_ASYNC_RESUME(coroutine); + void ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, error, transfer_error); + void ZEND_ASYNC_CANCEL(coroutine, error, transfer_error); + + // Context switch handlers + uint32_t ZEND_COROUTINE_ADD_SWITCH_HANDLER(coroutine, handler); + bool zend_coroutine_remove_switch_handler(coroutine, handler_index); + void ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(handler); + +State Checking Macros +===================== + +.. code:: c + + // Coroutine state + ZEND_COROUTINE_IS_STARTED(coroutine) + ZEND_COROUTINE_IS_FINISHED(coroutine) + ZEND_COROUTINE_IS_CANCELLED(coroutine) + ZEND_COROUTINE_SUSPENDED(coroutine) + ZEND_COROUTINE_IS_MAIN(coroutine) + + // Current context + ZEND_ASYNC_CURRENT_COROUTINE + ZEND_ASYNC_CURRENT_SCOPE + +Memory Management +================= + +Coroutines follow standard event reference counting: + +.. code:: c + + ZEND_ASYNC_EVENT_ADD_REF(&coroutine->event); // Increment reference + ZEND_ASYNC_EVENT_RELEASE(&coroutine->event); // Decrement and cleanup if needed + +The coroutine automatically cleans up: + +- Internal context data +- Switch handlers +- Event callbacks +- Scope references + +**************** + Best Practices +**************** + +#. **Always check if async is enabled** before using coroutine APIs +#. **Use appropriate scopes** to maintain context isolation +#. **Handle exceptions properly** in coroutine callbacks +#. **Register cleanup handlers** for long-running operations +#. **Use internal context** instead of global variables for coroutine data +#. **Remove one-time handlers** by returning ``false`` from switch handlers +#. **Validate coroutine state** before performing operations + +Example: Complete Coroutine Usage +================================= + +.. code:: c + + static uint32_t my_context_key = 0; + + PHP_MINIT_FUNCTION(my_extension) + { + my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); + ZEND_ASYNC_ADD_MAIN_COROUTINE_START_HANDLER(my_main_handler); + return SUCCESS; + } + + static bool my_main_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (!is_enter) return true; + + // Initialize coroutine-specific data + zval init_data; + ZVAL_STRING(&init_data, "initialized"); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &init_data); + + return false; // Remove handler after initialization + } + + void my_async_operation(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) { + return; // Fallback to synchronous operation + } + + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + coro->internal_entry = my_coroutine_function; + + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + ZEND_ASYNC_SUSPEND(); // Wait for completion + } + +This comprehensive coroutine system enables powerful asynchronous programming patterns while +maintaining clean separation of concerns and proper resource management. diff --git a/docs/source/true_async_api/events.rst b/docs/source/true_async_api/events.rst new file mode 100644 index 0000000000000..1af86924f76c2 --- /dev/null +++ b/docs/source/true_async_api/events.rst @@ -0,0 +1,301 @@ +######## + Events +######## + +The TrueAsync API revolves around the concept of **events**. An event represents any asynchronous +action that can produce a result or error in the future. All specific event types share a common +base structure ``zend_async_event_t`` which provides memory management, state flags and a standard +lifecycle. + +******************* + Creating an Event +******************* + +To create an event you typically call one of the ``ZEND_ASYNC_NEW_*`` macros which delegate to the +underlying implementation. Each macro returns a structure that contains ``zend_async_event_t`` as +its first member. + +.. code:: c + + zend_async_timer_event_t *timer = ZEND_ASYNC_NEW_TIMER_EVENT(1000, false); + timer->base.start(&timer->base); + +The ``base`` field exposes the common functions ``start``, ``stop`` and ``dispose`` as well as +reference counting helpers. + +************* + Event Flags +************* + +Each event has a ``flags`` field that controls its state and behaviour. The most important flags +are: + +``ZEND_ASYNC_EVENT_F_CLOSED`` + The event has been closed and may no longer be started or stopped. + +``ZEND_ASYNC_EVENT_F_RESULT_USED`` + Indicates that the result will be consumed by an awaiting context. When this flag is set the + awaiting code keeps the result alive until it is processed in the exception handler. + +``ZEND_ASYNC_EVENT_F_EXC_CAUGHT`` + Marks that an error produced by the event was caught in a callback and will not be rethrown + automatically. + +``ZEND_ASYNC_EVENT_F_ZVAL_RESULT`` + Signals that the callback result is a ``zval`` pointer. The TrueAsync core will properly + increment the reference count before inserting the value into userland arrays. + +``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` + Specifies that the structure also acts as a Zend object implementing ``Awaitable``. + +``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` + The dispose handler must not free the memory of the event itself. This is used when the event is + embedded into another structure. + +``ZEND_ASYNC_EVENT_F_EXCEPTION_HANDLED`` + Set by the callback once it has fully processed an exception. If this flag is not set the + exception will be rethrown after callbacks finish. + +``ZEND_ASYNC_EVENT_F_REFERENCE`` + Indicates that this structure is only an event reference which stores a pointer to the real + event. + +``ZEND_ASYNC_EVENT_F_OBJ_REF`` + Marks that the event keeps a pointer to the Zend object in its ``extra_offset`` region so + reference counting works through the object. + +Convenience macros such as ``ZEND_ASYNC_EVENT_SET_CLOSED`` and +``ZEND_ASYNC_EVENT_IS_EXCEPTION_HANDLED`` are provided to manipulate these flags. + +***************** + Event Callbacks +***************** + +The core maintains a dynamic vector of callbacks for each event. Implementations provide the +``add_callback`` and ``del_callback`` methods which internally use ``zend_async_callbacks_push`` and +``zend_async_callbacks_remove``. The ``zend_async_callbacks_notify`` helper iterates over all +registered callbacks and passes the result or exception. If a ``notify_handler`` is set on the event +it is invoked first and can adjust the result or exception as needed. The handler is expected to +call ``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` to forward the values to all listeners. The +``php-async`` extension provides convenience macros ``ZEND_ASYNC_CALLBACKS_NOTIFY`` for regular use, +``ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER`` when called from inside a ``notify_handler``, and +``ZEND_ASYNC_CALLBACKS_NOTIFY_AND_CLOSE`` to close the event before notifying listeners. + +The following example shows a libuv poll event that dispatches its callbacks once the underlying +handle becomes readable: + +.. code:: c + + static void on_poll_event(uv_poll_t *handle, int status, int events) { + async_poll_event_t *poll = handle->data; + zend_object *exception = NULL; + + if (status < 0) { + exception = async_new_exception( + async_ce_input_output_exception, + "Input output error: %s", + uv_strerror(status) + ); + } + + poll->event.triggered_events = events; + + ZEND_ASYNC_CALLBACKS_NOTIFY(&poll->event.base, NULL, exception); + + if (exception != NULL) { + zend_object_release(exception); + } + } + +****************** + Event Coroutines +****************** + +Coroutines themselves are implemented as events using the ``zend_coroutine_t`` structure. When a +coroutine yields, its waker waits on multiple events and resumes the coroutine once any of them +triggers. + +.. code:: c + + // Spawn a coroutine that waits for a timer + zend_coroutine_t *co = ZEND_ASYNC_SPAWN(NULL); + zend_async_resume_when(co, &timer->base, false, zend_async_waker_callback_resolve, NULL); + zend_async_enqueue_coroutine(co); + +When the coroutine finishes execution the event triggers again to deliver the result or exception. +The coroutine implementation marks the callback result as a ``zval`` value using +``ZEND_ASYNC_EVENT_SET_ZVAL_RESULT``. Callback handlers may also set +``ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED`` to indicate that the thrown exception has been processed +and should not be rethrown by the runtime. + +****************** + Extending Events +****************** + +Custom event types embed ``zend_async_event_t`` at the beginning of their structure and may allocate +additional memory beyond the end of the struct. The ``extra_size`` argument in +``ZEND_ASYNC_NEW_*_EX`` controls how much extra space is reserved, and ``extra_offset`` records +where that region begins. + +.. code:: c + + // Allocate extra space for user data + zend_async_poll_event_t *poll = ZEND_ASYNC_NEW_POLL_EVENT_EX(fd, false, sizeof(my_data_t)); + my_data_t *data = (my_data_t *)((char*)poll + poll->base.extra_offset); + +The libuv backend defines event wrappers that embed libuv handles. A timer event, for example, +extends ``zend_async_timer_event_t`` as follows: + +.. code:: c + + typedef struct { + zend_async_timer_event_t event; + uv_timer_t uv_handle; + } async_timer_event_t; + + // Initialize callbacks for the event + event->event.base.add_callback = libuv_add_callback; + event->event.base.del_callback = libuv_remove_callback; + event->event.base.start = libuv_timer_start; + event->event.base.stop = libuv_timer_stop; + event->event.base.dispose = libuv_timer_dispose; + +Every extended event defines its own ``start``, ``stop`` and ``dispose`` functions. The dispose +handler must release all resources associated with the event and is called when the reference count +reaches ``1``. It is common to stop the event first and then close the underlying libuv handle so +that memory gets freed in the ``uv_close`` callback. + +.. code:: c + + static void libuv_timer_dispose(zend_async_event_t *event) + { + if (ZEND_ASYNC_EVENT_REF(event) > 1) { + ZEND_ASYNC_EVENT_DEL_REF(event); + return; + } + + if (event->loop_ref_count > 0) { + event->loop_ref_count = 1; + event->stop(event); + } + + zend_async_callbacks_free(event); + + async_timer_event_t *timer = (async_timer_event_t *)event; + uv_close((uv_handle_t *)&timer->uv_handle, libuv_close_handle_cb); + } + +If ``ZEND_ASYNC_EVENT_F_NO_FREE_MEMORY`` is set the dispose handler must not free the event memory +itself because the structure is embedded in another object (e.g. ``async_coroutine_t``). The libuv +close callback will only free the libuv handle in this case. + +************************ + Custom Event Callbacks +************************ + +Callbacks can also be extended to store additional state. The await logic in ``php-async`` defines a +callback that inherits from ``zend_coroutine_event_callback_t`` and keeps a reference to the +awaiting context: + +.. code:: c + + typedef struct { + zend_coroutine_event_callback_t callback; + async_await_context_t *await_context; + zval key; + zend_async_event_callback_dispose_fn prev_dispose; + } async_await_callback_t; + + async_await_callback_t *cb = ecalloc(1, sizeof(async_await_callback_t)); + cb->callback.base.callback = async_waiting_callback; + cb->await_context = ctx; + zend_async_resume_when(co, awaitable, false, NULL, &cb->callback); + +************************ + Events as Zend Objects +************************ + +If ``ZEND_ASYNC_EVENT_F_ZEND_OBJ`` is set, the event also acts as a Zend object implementing +``Awaitable``. The ``zend_object_offset`` field stores the location of the ``zend_object`` within +the structure. Reference counting macros automatically use either the internal counter or +``GC_REFCOUNT`` depending on this flag. + +This allows events to be exposed to userland seamlessly while keeping the internal lifecycle +consistent. + +For events that are destroyed asynchronously (e.g. libuv timers) the actual event structure cannot +be a Zend object. Instead a lightweight reference structure is used. ``ZEND_ASYNC_EVENT_REF_PROLOG`` +reserves the required fields in the Zend object and ``ZEND_ASYNC_EVENT_REF_SET`` stores the pointer +to the real event together with the ``zend_object`` offset. The event must then be flagged with +``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` so that reference counting delegates to the object. + +When accessing the event from userland objects use ``ZEND_ASYNC_OBJECT_TO_EVENT`` and +``ZEND_ASYNC_EVENT_TO_OBJECT`` which handle both direct and reference-based layouts transparently. + +The ``php-async`` extension provides ``Async\\Timeout`` objects that embed a timer event. Recent +updates introduce a helper API for linking an event with a Zend object. A helper macro +``ZEND_ASYNC_EVENT_REF_PROLOG`` reserves fields at the beginning of the object to hold an event +reference. ``ZEND_ASYNC_EVENT_REF_SET`` stores the pointer to the newly created event and the offset +of the ``zend_object`` inside the structure. ``ZEND_ASYNC_EVENT_WITH_OBJECT_REF`` then marks the +event so reference counting will use the Zend object rather than the internal counter. + +The object factory now uses these helpers when creating the timer: + +.. code:: + + static zend_object *async_timeout_create(const zend_ulong ms, const bool is_periodic) + { + async_timeout_object_t *object = + ecalloc(1, sizeof(async_timeout_object_t) + + zend_object_properties_size(async_ce_timeout)); + + zend_async_event_t *event = (zend_async_event_t *) ZEND_ASYNC_NEW_TIMER_EVENT_EX( + ms, is_periodic, sizeof(async_timeout_ext_t) + ); + + if (UNEXPECTED(event == NULL)) { + efree(object); + return NULL; + } + + ZEND_ASYNC_EVENT_REF_SET(object, XtOffsetOf(async_timeout_object_t, std), (zend_async_timer_event_t *) event); + ZEND_ASYNC_EVENT_WITH_OBJECT_REF(event); + + async_timeout_ext_t *timeout = ASYNC_TIMEOUT_FROM_EVENT(event); + timeout->std = &object->std; + timeout->prev_dispose = event->dispose; + event->notify_handler = timeout_before_notify_handler; + event->dispose = async_timeout_event_dispose; + + zend_object_std_init(&object->std, async_ce_timeout); + object_properties_init(&object->std, async_ce_timeout); + object->std.handlers = &async_timeout_handlers; + + return &object->std; + } + + static void timeout_before_notify_handler(zend_async_event_t *event, + void *result, + zend_object *exception) + { + if (UNEXPECTED(exception != NULL)) { + ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER(event, result, exception); + return; + } + + zend_object *timeout_exception = async_new_exception( + async_ce_timeout_exception, + "Timeout occurred after %lu milliseconds", + ((zend_async_timer_event_t *) event)->timeout + ); + + ZEND_ASYNC_CALLBACKS_NOTIFY_FROM_HANDLER(event, result, timeout_exception); + zend_object_release(timeout_exception); + } + +.. note:: + + Events must not be exposed as Zend objects if their memory is released asynchronously. Zend + assumes that object destruction happens entirely during the ``zend_object_release`` call and + cannot wait for callbacks such as ``uv_close`` to free the underlying event. The + ``Async\\Timeout`` class will be redesigned to avoid this pattern. diff --git a/docs/source/true_async_api/examples.rst b/docs/source/true_async_api/examples.rst new file mode 100644 index 0000000000000..aae751a5725bd --- /dev/null +++ b/docs/source/true_async_api/examples.rst @@ -0,0 +1,452 @@ +########## + Examples +########## + +This section provides practical C code examples for using the TrueAsync API in PHP extensions. + +**************** + Basic Examples +**************** + +Checking API Availability +========================= + +Always check if async is enabled before using the API: + +.. code:: c + + #include "Zend/zend_async_API.h" + + void my_async_function(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) { + // Fallback to synchronous operation + my_sync_function(); + return; + } + + // Use async API + my_actual_async_function(); + } + +Simple Coroutine Creation +========================= + +Creating and spawning a basic coroutine: + +.. code:: c + + static void my_coroutine_entry(void *arg) + { + php_printf("Hello from coroutine!\n"); + } + + void spawn_example(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + if (!coro) { + php_error(E_WARNING, "Failed to create coroutine"); + return; + } + + coro->internal_entry = my_coroutine_entry; + coro->internal_arg = NULL; + + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + } + +Using Scopes +============ + +Creating coroutines with custom scopes: + +.. code:: c + + void scope_example(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + // Create new scope + zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(NULL); + if (!scope) return; + + // Spawn coroutine within scope + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN_WITH(scope); + if (coro) { + coro->internal_entry = my_coroutine_entry; + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + } + } + +********************************* + Context Switch Handler Examples +********************************* + +Per-Coroutine Handler +===================== + +Adding context switch handlers to specific coroutines: + +.. code:: c + + static bool my_switch_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (is_finishing) { + php_printf("Coroutine finishing\n"); + return false; // Remove handler + } + + if (is_enter) { + php_printf("Entering coroutine\n"); + } else { + php_printf("Leaving coroutine\n"); + } + + return true; // Keep handler + } + + void add_handler_example(void) + { + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + if (!coro) return; + + uint32_t handler_id = zend_coroutine_add_switch_handler(coro, my_switch_handler); + + coro->internal_entry = my_coroutine_entry; + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + + // Later: remove handler if needed + // zend_coroutine_remove_switch_handler(coro, handler_id); + } + +Global Main Coroutine Handler +============================= + +Handler that applies to all main coroutines: + +.. code:: c + + static uint32_t my_context_key = 0; + + static bool main_coroutine_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (!is_enter) return true; + + // Initialize extension data for this coroutine + my_extension_data_t *data = ecalloc(1, sizeof(my_extension_data_t)); + data->initialized = true; + + // Store in coroutine internal context + zval data_zval; + ZVAL_PTR(&data_zval, data); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &data_zval); + + return false; // Remove handler after initialization + } + + PHP_MINIT_FUNCTION(my_extension) + { + my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); + zend_async_add_main_coroutine_start_handler(main_coroutine_handler); + return SUCCESS; + } + +****************** + Event Operations +****************** + +Basic Event Usage +================= + +Working with async events (using timer as example): + +.. code:: c + + static void timer_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + if (exception) { + php_printf("Timer error occurred\n"); + } else { + php_printf("Timer fired!\n"); + } + + // Clean up + ZEND_ASYNC_EVENT_DEL_REF(event); + } + + void timer_example(void) + { + if (!ZEND_ASYNC_REACTOR_IS_ENABLED()) return; + + // This would need actual reactor implementation + // zend_async_timer_event_t *timer = zend_async_new_timer_event_fn(1000, 0, false, 0); + // if (!timer) return; + + // zend_async_event_callback_t *callback = ...; + // timer->base.add_callback(&timer->base, callback); + // timer->base.start(&timer->base); + } + +*************************** + Internal Context Examples +*************************** + +Storing Extension Data +====================== + +Using internal context to store per-coroutine data: + +.. code:: c + + static uint32_t cache_context_key = 0; + + typedef struct { + HashTable *cached_results; + time_t last_access; + } cache_data_t; + + PHP_MINIT_FUNCTION(cache_extension) + { + cache_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("cache_data"); + return SUCCESS; + } + + cache_data_t* get_cache_data(zend_coroutine_t *coroutine) + { + zval *cached = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, cache_context_key); + if (cached && Z_TYPE_P(cached) == IS_PTR) { + return (cache_data_t *)Z_PTR_P(cached); + } + + // Create new cache data + cache_data_t *data = ecalloc(1, sizeof(cache_data_t)); + ALLOC_HASHTABLE(data->cached_results); + zend_hash_init(data->cached_results, 16, NULL, ZVAL_PTR_DTOR, 0); + data->last_access = time(NULL); + + // Store in context + zval data_zval; + ZVAL_PTR(&data_zval, data); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, cache_context_key, &data_zval); + + return data; + } + +************************* + Error Handling Examples +************************* + +Exception Creation +================== + +Creating and handling exceptions in async context: + +.. code:: c + + void async_operation_with_error(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + zend_coroutine_t *coro = ZEND_ASYNC_SPAWN(); + if (!coro) { + zend_throw_error(NULL, "Failed to create coroutine"); + return; + } + + // Set up coroutine that might throw + coro->internal_entry = error_prone_operation; + ZEND_ASYNC_ENQUEUE_COROUTINE(coro); + } + + static void error_prone_operation(void *arg) + { + // Simulate error condition + if (some_error_condition()) { + zend_object *exception = zend_throw_exception( + zend_ce_exception, "Async operation failed", 0 + ); + + // Resume current coroutine with error + zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; + if (current) { + ZEND_ASYNC_RESUME_WITH_ERROR(current, exception, true); + } + return; + } + + php_printf("Operation completed successfully\n"); + } + +**************************** + Memory Management Examples +**************************** + +Reference Counting Examples +=========================== + +Proper event reference management: + +.. code:: c + + void event_reference_example(void) + { + // Assuming we have an event from somewhere + zend_async_event_t *event = get_some_event(); + + // Add reference when storing event + ZEND_ASYNC_EVENT_ADD_REF(event); + + // Store event somewhere + store_event_reference(event); + + // Later: remove reference when done + ZEND_ASYNC_EVENT_DEL_REF(event); + + // Or use RELEASE which decrements and disposes if needed + // ZEND_ASYNC_EVENT_RELEASE(event); + } + +******************************* + Futures and Channels Examples +******************************* + +Future Creation +=============== + +Creating and using futures for async results: + +.. code:: c + + void future_example(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + // Create thread-safe future + zend_future_t *future = ZEND_ASYNC_NEW_FUTURE(true); + if (!future) return; + + // Future can be resolved from any thread + // zend_future_resolve(future, &result_zval); + + // Or with exception + // zend_future_reject(future, exception); + } + +Channel Communication +===================== + +Creating channels for coroutine communication: + +.. code:: c + + void channel_example(void) + { + if (!ZEND_ASYNC_IS_ENABLED()) return; + + // Create buffered channel + zend_async_channel_t *channel = ZEND_ASYNC_NEW_CHANNEL(10, true, false); + if (!channel) return; + + // Channels support send/receive operations + // channel->send(channel, &value); + // channel->receive(channel, &result); + } + +*********************** + Extension Integration +*********************** + +Complete Extension Example +========================== + +Full example of async-aware extension: + +.. code:: c + + static uint32_t my_ext_context_key = 0; + + typedef struct { + bool async_enabled; + HashTable *pending_operations; + } my_extension_context_t; + + static bool my_ext_main_handler(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (is_finishing) { + // Cleanup on coroutine completion + zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_ext_context_key); + if (ctx_zval && Z_TYPE_P(ctx_zval) == IS_PTR) { + my_extension_context_t *ctx = (my_extension_context_t *)Z_PTR_P(ctx_zval); + if (ctx->pending_operations) { + zend_hash_destroy(ctx->pending_operations); + FREE_HASHTABLE(ctx->pending_operations); + } + efree(ctx); + } + return false; + } + + if (!is_enter) return true; + + // Initialize context for new main coroutine + my_extension_context_t *ctx = ecalloc(1, sizeof(my_extension_context_t)); + ctx->async_enabled = true; + + ALLOC_HASHTABLE(ctx->pending_operations); + zend_hash_init(ctx->pending_operations, 8, NULL, ZVAL_PTR_DTOR, 0); + + zval ctx_zval; + ZVAL_PTR(&ctx_zval, ctx); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_ext_context_key, &ctx_zval); + + return false; // Remove handler after init + } + + PHP_MINIT_FUNCTION(my_extension) + { + my_ext_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); + + if (ZEND_ASYNC_IS_ENABLED()) { + zend_async_add_main_coroutine_start_handler(my_ext_main_handler); + } + + return SUCCESS; + } + + // Extension function that works async-aware + PHP_FUNCTION(my_async_operation) + { + if (!ZEND_ASYNC_IS_ENABLED()) { + // Fallback to sync operation + my_sync_operation(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } + + zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; + if (current) { + my_extension_context_t *ctx = get_my_context(current); + if (ctx && ctx->async_enabled) { + // Perform async operation + perform_async_operation(current); + return; + } + } + + // Default to sync + my_sync_operation(INTERNAL_FUNCTION_PARAM_PASSTHRU); + } + +************************* + Examples Best Practices +************************* + +#. **Always check enablement** before using async APIs +#. **Use internal context** for per-coroutine data storage +#. **Handle reference counting** properly for events +#. **Register cleanup handlers** for resource management +#. **Provide sync fallbacks** when async is not available +#. **Use appropriate scopes** for coroutine isolation +#. **Handle exceptions** properly in async context diff --git a/docs/source/true_async_api/implementation-guide.rst b/docs/source/true_async_api/implementation-guide.rst new file mode 100644 index 0000000000000..4a049e1cea3b3 --- /dev/null +++ b/docs/source/true_async_api/implementation-guide.rst @@ -0,0 +1,396 @@ +###################### + Implementation Guide +###################### + +This guide explains how to implement TrueAsync API backends and integrate them with the PHP core. It +covers the registration process, implementation patterns, and best practices. + +********** + Overview +********** + +Implementing a TrueAsync API backend involves: + +#. **Creating function implementations** that match the API signatures +#. **Registering implementations** with the core during module initialization +#. **Following memory management** and error handling conventions +#. **Implementing event lifecycle** methods (start, stop, dispose) +#. **Handling callbacks** and result delivery + +********************** + Registration Process +********************** + +The registration process connects your implementations to the core API. + +Module Initialization +===================== + +Register your implementations during ``PHP_MINIT_FUNCTION``: + +.. code:: c + + PHP_MINIT_FUNCTION(my_async_backend) { + // Register reactor implementations + bool success = zend_async_reactor_register( + "MyAsyncBackend", // Module name + false, // Allow override + my_reactor_startup, // Startup function + my_reactor_shutdown, // Shutdown function + my_reactor_execute, // Execute function + my_reactor_loop_alive, // Loop alive check + my_new_socket_event, // Socket event factory + my_new_poll_event, // Poll event factory + my_new_timer_event, // Timer event factory + my_new_signal_event, // Signal event factory + my_new_process_event, // Process event factory + my_new_thread_event, // Thread event factory + my_new_filesystem_event, // Filesystem event factory + my_getnameinfo, // DNS nameinfo + my_getaddrinfo, // DNS addrinfo + my_freeaddrinfo, // DNS cleanup + my_new_exec_event, // Exec event factory + my_exec // Exec function + ); + + if (!success) { + php_error_docref(NULL, E_CORE_ERROR, "Failed to register async backend"); + return FAILURE; + } + + return SUCCESS; + } + +Function Pointer Types +====================== + +All implementations must match the exact function pointer signatures defined in +``zend_async_API.h``: + +.. code:: c + + // DNS function signatures + typedef zend_async_dns_addrinfo_t* (*zend_async_getaddrinfo_t)( + const char *node, const char *service, + const struct addrinfo *hints, size_t extra_size + ); + + typedef void (*zend_async_freeaddrinfo_t)(struct addrinfo *ai); + + // Timer function signature + typedef zend_async_timer_event_t* (*zend_async_new_timer_event_t)( + const zend_ulong timeout, const zend_ulong nanoseconds, + const bool is_periodic, size_t extra_size + ); + +************************* + Implementation Patterns +************************* + +Event Structure Pattern +======================= + +All async events follow a common pattern with base event structure plus implementation-specific +data: + +.. code:: c + + // Your implementation structure + typedef struct _my_dns_addrinfo_s { + zend_async_dns_addrinfo_t event; // Base event (MUST be first) + my_event_loop_handle_t handle; // Your event loop handle + // Additional implementation-specific fields... + } my_dns_addrinfo_t; + +Factory Function Pattern +======================== + +Event factory functions create and initialize event structures: + +.. code:: c + + static zend_async_dns_addrinfo_t* my_getaddrinfo( + const char *node, const char *service, + const struct addrinfo *hints, size_t extra_size + ) { + // Allocate memory (include extra_size if specified) + size_t total_size = sizeof(my_dns_addrinfo_t) + extra_size; + my_dns_addrinfo_t *dns = pecalloc(1, total_size, 0); + + // Initialize base event fields + dns->event.base.extra_offset = sizeof(my_dns_addrinfo_t); + dns->event.base.ref_count = 1; + dns->event.base.add_callback = my_add_callback; + dns->event.base.del_callback = my_remove_callback; + dns->event.base.start = my_dns_start; + dns->event.base.stop = my_dns_stop; + dns->event.base.dispose = my_dns_dispose; + + // Store request parameters + dns->event.node = node; + dns->event.service = service; + dns->event.hints = hints; + + // Initialize your backend-specific handle + int result = my_event_loop_init_dns(&dns->handle, node, service, hints); + if (result != 0) { + my_throw_error("Failed to initialize DNS request: %s", my_strerror(result)); + pefree(dns, 0); + return NULL; + } + + // Link handle to event data + dns->handle.data = dns; + + return &dns->event; + } + +Lifecycle Methods +================= + +Implement the three lifecycle methods for each event type: + +.. code:: c + + // Start the async operation + static void my_dns_start(zend_async_event_t *event) { + EVENT_START_PROLOGUE(event); // Standard prologue + + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; + + int result = my_event_loop_start_dns(&dns->handle, my_dns_callback); + if (result != 0) { + my_throw_error("Failed to start DNS operation: %s", my_strerror(result)); + return; + } + + event->loop_ref_count++; + ZEND_ASYNC_INCREASE_EVENT_COUNT; + } + + // Stop the async operation + static void my_dns_stop(zend_async_event_t *event) { + EVENT_STOP_PROLOGUE(event); // Standard prologue + + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; + + my_event_loop_stop_dns(&dns->handle); + + event->loop_ref_count = 0; + ZEND_ASYNC_DECREASE_EVENT_COUNT; + } + + // Clean up and dispose + static void my_dns_dispose(zend_async_event_t *event) { + if (ZEND_ASYNC_EVENT_REF(event) > 1) { + ZEND_ASYNC_EVENT_DEL_REF(event); + return; + } + + // Stop if still running + if (event->loop_ref_count > 0) { + event->loop_ref_count = 1; + event->stop(event); + } + + // Free callbacks + zend_async_callbacks_free(event); + + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)event; + + // Clean up backend-specific resources + my_event_loop_cleanup_dns(&dns->handle); + + // Free the structure + pefree(dns, 0); + } + +******************* + Callback Handling +******************* + +Event Completion Callbacks +========================== + +When your backend completes an operation, notify the core through callbacks: + +.. code:: c + + // Your backend callback (called by event loop) + static void my_dns_callback(my_event_loop_handle_t *handle, int status, struct addrinfo *result) { + my_dns_addrinfo_t *dns = (my_dns_addrinfo_t *)handle->data; + zend_object *exception = NULL; + + // Close the event after one-time operations + ZEND_ASYNC_EVENT_SET_CLOSED(&dns->event.base); + dns->event.base.stop(&dns->event.base); + + // Handle errors + if (status != 0) { + exception = my_new_exception( + "DNS resolution failed: %s", my_strerror(status) + ); + } + + // Notify all registered callbacks + zend_async_callbacks_notify(&dns->event.base, result, exception); + + // Release exception if created + if (exception) { + zend_object_release(exception); + } + } + +Result Memory Management +======================== + +For operations that return allocated memory (like ``getaddrinfo``): + +.. code:: c + + static void my_freeaddrinfo(struct addrinfo *ai) { + if (ai != NULL) { + my_event_loop_free_addrinfo(ai); // Use your backend's free function + } + } + +**Make sure your ``getaddrinfo`` results can be freed by your ``freeaddrinfo`` implementation.** + +**************** + Error Handling +**************** + +Error Reporting +=============== + +Use the standard PHP error reporting mechanisms: + +.. code:: c + + // For recoverable errors during operation + static void my_throw_error(const char *format, ...) { + va_list args; + va_start(args, format); + zend_string *message = zend_vstrpprintf(0, format, args); + va_end(args); + + zend_throw_error(NULL, ZSTR_VAL(message)); + zend_string_release(message); + } + + // For creating exception objects + static zend_object* my_new_exception(const char *format, ...) { + va_list args; + va_start(args, format); + zend_string *message = zend_vstrpprintf(0, format, args); + va_end(args); + + zend_object *ex = zend_throw_exception(zend_ce_exception, ZSTR_VAL(message), 0); + zend_string_release(message); + return ex; + } + +Exception Classes +================= + +Use appropriate exception classes for different error types: + +.. code:: c + + // DNS errors + zend_object *dns_ex = async_new_exception(async_ce_dns_exception, "DNS error: %s", error_msg); + + // I/O errors + zend_object *io_ex = async_new_exception(async_ce_input_output_exception, "I/O error: %s", error_msg); + + // Timeout errors + zend_object *timeout_ex = async_new_exception(async_ce_timeout_exception, "Operation timed out"); + +******************* + Memory Management +******************* + +Reference Counting +================== + +The TrueAsync API uses reference counting for event objects: + +.. code:: c + + // Add reference (when storing event somewhere) + ZEND_ASYNC_EVENT_ADD_REF(event); + + // Remove reference (when done with event) + ZEND_ASYNC_EVENT_DEL_REF(event); + + // Release event (decrements and disposes if needed) + ZEND_ASYNC_EVENT_RELEASE(event); + +Memory Allocation +================= + +Use persistent allocation for long-lived structures: + +.. code:: c + + // For event structures that may outlive a request + my_event_t *event = pecalloc(1, sizeof(my_event_t), 0); // Persistent + + // For temporary data + char *buffer = emalloc(size); // Request-scoped + +Extra Data Support +================== + +Support the ``extra_size`` parameter for extensions: + +.. code:: c + + static zend_async_timer_event_t* my_new_timer_event( + const zend_ulong timeout, const zend_ulong nanoseconds, + const bool is_periodic, size_t extra_size + ) { + // Allocate base size + extra space + size_t total_size = sizeof(my_timer_event_t) + extra_size; + my_timer_event_t *timer = pecalloc(1, total_size, 0); + + // Set extra offset so extensions can find their data + timer->event.base.extra_offset = sizeof(my_timer_event_t); + + // ... rest of initialization + } + +**************** + Best Practices +**************** + +Performance Considerations +========================== + +- **Minimize allocations** in hot paths (event creation/callback handling) +- **Use object pools** for frequently created/destroyed events +- **Batch operations** when possible in your event loop +- **Profile memory usage** to detect leaks early + +Thread Safety +============= + +- **Document thread safety** guarantees of your implementation +- **Use proper locking** if supporting multi-threaded access +- **Consider thread-local storage** for per-thread state + +Testing +======= + +- **Create unit tests** for each API function implementation +- **Test error conditions** and memory cleanup +- **Verify callback behavior** under various scenarios +- **Load test** with many concurrent operations + +Documentation +============= + +- **Document implementation-specific behavior** and limitations +- **Provide configuration options** for tuning performance +- **Include examples** showing integration with your event loop +- **Document version compatibility** with different backend versions diff --git a/docs/source/true_async_api/index.rst b/docs/source/true_async_api/index.rst new file mode 100644 index 0000000000000..e9c33c023474a --- /dev/null +++ b/docs/source/true_async_api/index.rst @@ -0,0 +1,108 @@ +############### + TrueAsync API +############### + +.. toctree:: + :hidden: + + architecture + events + coroutines + api-reference + implementation-guide + patterns + examples + +The TrueAsync API provides a comprehensive asynchronous programming interface for PHP extensions, +built on top of the Zend Engine. This API enables non-blocking operations for I/O, timers, signals, +and other asynchronous tasks while maintaining clean separation between the API definition and its +implementation. + +.. warning:: + + TrueAsync API is currently in development. The API may change in future versions. + +The TrueAsync API follows a strict architectural pattern where the PHP core (php-src) defines the +API interfaces, while extensions provide concrete implementations. This design ensures modularity, +testability, and allows for multiple backend implementations (libuv, epoll, etc.). + +************** + Key Features +************** + +- **Non-blocking I/O operations** - File, network, and socket operations +- **Timer and scheduling support** - Timeout handling and periodic tasks +- **Signal handling** - Asynchronous signal processing +- **DNS resolution** - Async getaddrinfo/getnameinfo with proper memory management +- **Process management** - Spawn and monitor external processes +- **Filesystem monitoring** - Watch for file and directory changes +- **Clean separation** - API definition vs implementation +- **Memory safety** - Proper reference counting and cleanup + +************** + Architecture +************** + +The TrueAsync API consists of two main components: + +**API Definition Layer** (``Zend/zend_async_API.h``) + Defines function pointer types, structures, and macros that form the public API. Located in the + php-src core and used by all consumers. + +**Implementation Layer** (``ext/async/``) + Provides concrete implementations using libuv or other event loop libraries. Registers + implementations with the core through registration functions. + +This separation allows php-src core to use async functionality without directly depending on any +specific implementation, enabling clean modular design and testing. + +***************** + Getting Started +***************** + +To use the TrueAsync API in your extension: + +#. Include the API header: ``#include "Zend/zend_async_API.h"`` +#. Check if async is enabled: ``ZEND_ASYNC_IS_ENABLED()`` +#. Use API macros like ``ZEND_ASYNC_GETADDRINFO()`` for operations +#. Handle results through callback mechanisms +#. Clean up resources with appropriate free functions + +For implementation details, see the :doc:`implementation-guide` section. + +*************** + Quick Example +*************** + +.. code:: c + + // Async DNS resolution + zend_async_dns_addrinfo_t *event = ZEND_ASYNC_GETADDRINFO("example.com", "80", &hints); + + // Register callback for completion + zend_async_resume_when(coroutine, &event->base, true, my_callback, NULL); + + // Later, in callback: clean up + ZEND_ASYNC_FREEADDRINFO(result); + +************************ + Documentation Sections +************************ + +:doc:`architecture` + Detailed explanation of the two-repository architecture and component separation. + +:doc:`coroutines` + Complete guide to coroutines, context switching, and internal context management. + +:doc:`api-reference` + Complete API reference with function signatures, macros, and data structures. + +:doc:`implementation-guide` + Guide for implementing TrueAsync API backends and registering with the core. + +:doc:`patterns` + Common patterns, best practices, and coding conventions for the API. + +:doc:`examples` + Practical C code examples showing how to use different API components. diff --git a/docs/source/true_async_api/patterns.rst b/docs/source/true_async_api/patterns.rst new file mode 100644 index 0000000000000..a064be37e7271 --- /dev/null +++ b/docs/source/true_async_api/patterns.rst @@ -0,0 +1,568 @@ +########## + Patterns +########## + +This section covers common patterns and best practices for implementing TrueAsync API in PHP +extensions. + +********************** + Fundamental Patterns +********************** + +Async-Aware Function Pattern +============================ + +The standard pattern for making extension functions async-aware: + +.. code:: c + + PHP_FUNCTION(my_extension_function) + { + // 1. Check if async is enabled + if (!ZEND_ASYNC_IS_ENABLED()) { + // Fallback to synchronous implementation + my_sync_implementation(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } + + // 2. Check if we're in coroutine context + zend_coroutine_t *current = ZEND_ASYNC_CURRENT_COROUTINE; + if (!current) { + // Not in coroutine, use sync + my_sync_implementation(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } + + // 3. Perform async operation + my_async_implementation(current, INTERNAL_FUNCTION_PARAM_PASSTHRU); + } + +API Enablement Check Pattern +============================ + +Always check API availability before using async features: + +.. code:: c + + void my_extension_init(void) + { + if (ZEND_ASYNC_IS_ENABLED()) { + // Initialize async-specific resources + init_async_resources(); + + if (ZEND_ASYNC_REACTOR_IS_ENABLED()) { + // Reactor is available, can use events + init_event_handlers(); + } + } + } + +***************************** + Context Management Patterns +***************************** + +Extension Context Initialization +================================ + +Pattern for setting up per-coroutine extension data: + +.. code:: c + + static uint32_t my_context_key = 0; + + typedef struct { + HashTable *cache; + zend_async_timer_event_t *cleanup_timer; + bool initialized; + } my_context_t; + + static bool my_context_initializer(zend_coroutine_t *coroutine, bool is_enter, bool is_finishing) + { + if (is_finishing) { + // Cleanup context + zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_context_key); + if (ctx_zval && Z_TYPE_P(ctx_zval) == IS_PTR) { + my_context_t *ctx = (my_context_t *)Z_PTR_P(ctx_zval); + cleanup_my_context(ctx); + } + return false; // Remove handler + } + + if (!is_enter) return true; + + // Initialize context + my_context_t *ctx = ecalloc(1, sizeof(my_context_t)); + ALLOC_HASHTABLE(ctx->cache); + zend_hash_init(ctx->cache, 16, NULL, ZVAL_PTR_DTOR, 0); + ctx->initialized = true; + + zval ctx_zval; + ZVAL_PTR(&ctx_zval, ctx); + ZEND_ASYNC_INTERNAL_CONTEXT_SET(coroutine, my_context_key, &ctx_zval); + + return false; // One-time initialization + } + + PHP_MINIT_FUNCTION(my_extension) + { + my_context_key = ZEND_ASYNC_INTERNAL_CONTEXT_KEY_ALLOC("my_extension"); + + if (ZEND_ASYNC_IS_ENABLED()) { + zend_async_add_main_coroutine_start_handler(my_context_initializer); + } + + return SUCCESS; + } + +Context Access Pattern +====================== + +Safe pattern for accessing extension context: + +.. code:: c + + my_context_t* get_my_context(zend_coroutine_t *coroutine) + { + if (!coroutine) { + coroutine = ZEND_ASYNC_CURRENT_COROUTINE; + if (!coroutine) return NULL; + } + + zval *ctx_zval = ZEND_ASYNC_INTERNAL_CONTEXT_FIND(coroutine, my_context_key); + if (!ctx_zval || Z_TYPE_P(ctx_zval) != IS_PTR) { + return NULL; + } + + my_context_t *ctx = (my_context_t *)Z_PTR_P(ctx_zval); + return ctx->initialized ? ctx : NULL; + } + +****************************** + Resource Management Patterns +****************************** + +RAII Pattern for Events +======================= + +Resource Acquisition Is Initialization pattern for async events: + +.. code:: c + + typedef struct { + zend_async_event_t *event; + bool owns_reference; + } event_guard_t; + + static event_guard_t* event_guard_create(zend_async_event_t *event, bool add_ref) + { + event_guard_t *guard = emalloc(sizeof(event_guard_t)); + guard->event = event; + guard->owns_reference = add_ref; + + if (add_ref) { + ZEND_ASYNC_EVENT_ADD_REF(event); + } + + return guard; + } + + static void event_guard_destroy(event_guard_t *guard) + { + if (guard->owns_reference && guard->event) { + ZEND_ASYNC_EVENT_DEL_REF(guard->event); + } + efree(guard); + } + + void my_operation_with_event(void) + { + zend_async_event_t *event = create_some_event(); + event_guard_t *guard = event_guard_create(event, true); + + // Use event... + + // Automatic cleanup + event_guard_destroy(guard); + } + +Cleanup Callback Pattern +======================== + +Registering cleanup callbacks for async operations: + +.. code:: c + + typedef struct { + void *user_data; + void (*cleanup_fn)(void *data); + } cleanup_context_t; + + static void cleanup_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + cleanup_context_t *ctx = (cleanup_context_t *)callback->arg; + + if (ctx->cleanup_fn && ctx->user_data) { + ctx->cleanup_fn(ctx->user_data); + } + + efree(ctx); + } + + void register_cleanup(zend_async_event_t *event, void *data, void (*cleanup_fn)(void*)) + { + cleanup_context_t *ctx = emalloc(sizeof(cleanup_context_t)); + ctx->user_data = data; + ctx->cleanup_fn = cleanup_fn; + + zend_async_event_callback_t *callback = zend_async_event_callback_new( + event, cleanup_callback, 0 + ); + callback->arg = ctx; + + event->add_callback(event, callback); + } + +************************* + Error Handling Patterns +************************* + +Exception Propagation Pattern +============================= + +Proper exception handling in async context: + +.. code:: c + + static void async_operation_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + zend_coroutine_t *coroutine = (zend_coroutine_t *)callback->arg; + + if (exception) { + // Propagate exception to coroutine + ZEND_ASYNC_RESUME_WITH_ERROR(coroutine, exception, true); + } else { + // Process result and resume normally + process_async_result(result); + ZEND_ASYNC_RESUME(coroutine); + } + + // Cleanup + ZEND_ASYNC_EVENT_DEL_REF(event); + } + +Error Recovery Pattern +====================== + +Handling recoverable errors in async operations: + +.. code:: c + + typedef struct { + int retry_count; + int max_retries; + void *operation_data; + } retry_context_t; + + static void retry_operation_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + retry_context_t *ctx = (retry_context_t *)callback->arg; + + if (exception && ctx->retry_count < ctx->max_retries) { + // Retry the operation + ctx->retry_count++; + php_error(E_NOTICE, "Retrying operation (attempt %d/%d)", + ctx->retry_count, ctx->max_retries); + + retry_async_operation(ctx); + return; + } + + // Either success or max retries reached + if (exception) { + handle_final_error(exception, ctx); + } else { + handle_success(result, ctx); + } + + efree(ctx); + } + +********************** + Performance Patterns +********************** + +Object Pooling Pattern +====================== + +Reusing objects to reduce allocation overhead: + +.. code:: c + + static zend_async_event_callback_t *callback_pool[MAX_POOL_SIZE]; + static int pool_size = 0; + static zend_mutex_t pool_mutex; + + static zend_async_event_callback_t* get_pooled_callback(void) + { + zend_mutex_lock(&pool_mutex); + + zend_async_event_callback_t *callback = NULL; + if (pool_size > 0) { + callback = callback_pool[--pool_size]; + } + + zend_mutex_unlock(&pool_mutex); + + if (!callback) { + callback = emalloc(sizeof(zend_async_event_callback_t)); + memset(callback, 0, sizeof(zend_async_event_callback_t)); + } + + return callback; + } + + static void return_pooled_callback(zend_async_event_callback_t *callback) + { + // Reset callback state + memset(callback, 0, sizeof(zend_async_event_callback_t)); + + zend_mutex_lock(&pool_mutex); + + if (pool_size < MAX_POOL_SIZE) { + callback_pool[pool_size++] = callback; + } else { + efree(callback); + } + + zend_mutex_unlock(&pool_mutex); + } + +Batch Operations Pattern +======================== + +Grouping operations for better performance: + +.. code:: c + + typedef struct { + zend_async_event_t **events; + int event_count; + int completed_count; + void (*completion_callback)(void *batch_data); + void *batch_data; + } batch_operation_t; + + static void batch_item_callback(zend_async_event_t *event, zend_async_event_callback_t *callback, + void *result, zend_object *exception) + { + batch_operation_t *batch = (batch_operation_t *)callback->arg; + + __sync_fetch_and_add(&batch->completed_count, 1); + + if (batch->completed_count == batch->event_count) { + // All operations completed + if (batch->completion_callback) { + batch->completion_callback(batch->batch_data); + } + cleanup_batch(batch); + } + } + +********************** + Integration Patterns +********************** + +Reactor Integration Pattern +=========================== + +Integrating with external event loops: + +.. code:: c + + typedef struct { + my_event_loop_t *external_loop; + HashTable *pending_events; + bool integrated; + } reactor_integration_t; + + static void bridge_event_callback(my_external_event_t *ext_event, void *data) + { + zend_async_event_t *async_event = (zend_async_event_t *)data; + + // Convert external event result to async event result + void *result = convert_external_result(ext_event); + zend_object *exception = check_external_error(ext_event); + + // Notify async event callbacks + async_event->callbacks_notify(async_event, result, exception); + } + + bool integrate_with_reactor(my_event_loop_t *loop) + { + if (!ZEND_ASYNC_REACTOR_IS_ENABLED()) { + return false; + } + + reactor_integration_t *integration = emalloc(sizeof(reactor_integration_t)); + integration->external_loop = loop; + + ALLOC_HASHTABLE(integration->pending_events); + zend_hash_init(integration->pending_events, 32, NULL, NULL, 0); + + // Set up bridge between external loop and async events + loop->set_callback(loop, bridge_event_callback); + + return true; + } + +****************** + Testing Patterns +****************** + +Mock Implementation Pattern +=========================== + +Creating testable async code: + +.. code:: c + + #ifdef TESTING_MODE + + typedef struct { + zend_async_event_t base; + void *expected_result; + zend_object *expected_exception; + bool should_fail; + } mock_event_t; + + static void mock_event_start(zend_async_event_t *event) + { + mock_event_t *mock = (mock_event_t *)event; + + // Simulate immediate completion for testing + if (mock->should_fail) { + event->callbacks_notify(event, NULL, mock->expected_exception); + } else { + event->callbacks_notify(event, mock->expected_result, NULL); + } + } + + mock_event_t* create_mock_event(void *result, zend_object *exception) + { + mock_event_t *mock = emalloc(sizeof(mock_event_t)); + memset(mock, 0, sizeof(mock_event_t)); + + mock->base.start = mock_event_start; + mock->expected_result = result; + mock->expected_exception = exception; + mock->should_fail = (exception != NULL); + + return mock; + } + + #endif + +******************* + Advanced Patterns +******************* + +State Machine Pattern +===================== + +Managing complex async state transitions: + +.. code:: c + + typedef enum { + STATE_IDLE, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_SENDING, + STATE_RECEIVING, + STATE_ERROR, + STATE_CLOSED + } connection_state_t; + + typedef struct { + connection_state_t state; + zend_async_event_t *current_event; + void (*state_handlers[STATE_CLOSED + 1])(void *connection); + void *connection_data; + } async_state_machine_t; + + static void transition_state(async_state_machine_t *sm, connection_state_t new_state) + { + if (sm->state != new_state) { + sm->state = new_state; + + if (sm->state_handlers[new_state]) { + sm->state_handlers[new_state](sm->connection_data); + } + } + } + +Producer-Consumer Pattern +========================= + +Implementing async producer-consumer with channels: + +.. code:: c + + typedef struct { + zend_async_channel_t *channel; + zend_coroutine_t *producer_coro; + zend_coroutine_t *consumer_coro; + bool shutdown_requested; + } producer_consumer_t; + + static void producer_coroutine(void *arg) + { + producer_consumer_t *pc = (producer_consumer_t *)arg; + + while (!pc->shutdown_requested) { + // Produce data + zval data; + produce_data(&data); + + // Send to channel (may suspend if channel is full) + if (!pc->channel->send(pc->channel, &data)) { + break; // Channel closed + } + } + } + + static void consumer_coroutine(void *arg) + { + producer_consumer_t *pc = (producer_consumer_t *)arg; + + while (!pc->shutdown_requested) { + zval data; + + // Receive from channel (may suspend if channel is empty) + if (!pc->channel->receive(pc->channel, &data)) { + break; // Channel closed + } + + // Process data + process_data(&data); + zval_dtor(&data); + } + } + +************************ + Best Practices Summary +************************ + +#. **Always check enablement** before using async APIs +#. **Provide sync fallbacks** for non-async environments +#. **Use internal context** for per-coroutine data +#. **Handle reference counting** properly +#. **Register cleanup handlers** for resource management +#. **Use appropriate error handling** patterns +#. **Consider performance** with pooling and batching +#. **Test thoroughly** with mock implementations +#. **Document async behavior** clearly for users +#. **Follow reactor integration** patterns for external loops From fdd756aa9c69a5514326fa875c6c3ce0d32740cd Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 22:09:55 +0300 Subject: [PATCH 123/137] * rename dtor functions --- Zend/zend_async_API.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 97902e5c69d88..4e8ca3c8e1624 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -199,7 +199,7 @@ ZEND_API bool zend_async_reactor_is_enabled(void) return reactor_module_name != NULL; } -static void ts_globals_ctor(zend_async_globals_t *globals) +static void internal_globals_ctor(zend_async_globals_t *globals) { globals->state = ZEND_ASYNC_OFF; zend_atomic_bool_store(&globals->heartbeat, false); @@ -213,7 +213,7 @@ static void ts_globals_ctor(zend_async_globals_t *globals) globals->exit_exception = NULL; } -static void ts_globals_dtor(zend_async_globals_t *globals) +static void internal_globals_dtor(zend_async_globals_t *globals) { } @@ -221,13 +221,13 @@ void zend_async_globals_ctor(void) { #ifdef ZTS ts_allocate_fast_id(&zend_async_globals_id, &zend_async_globals_offset, - sizeof(zend_async_globals_t), (ts_allocate_ctor) ts_globals_ctor, - (ts_allocate_dtor) ts_globals_dtor); + sizeof(zend_async_globals_t), (ts_allocate_ctor) internal_globals_ctor, + (ts_allocate_dtor) internal_globals_dtor); ZEND_ASSERT(zend_async_globals_id != 0 && "zend_async_globals allocation failed"); #else - ts_globals_ctor(&zend_async_globals_api); + internal_globals_ctor(&zend_async_globals_api); #endif } @@ -239,7 +239,7 @@ void zend_async_globals_dtor(void) { #ifdef ZTS #else - ts_globals_dtor(&zend_async_globals_api); + internal_globals_dtor(&zend_async_globals_api); #endif } From ef88e6424c79f2b99598113f2ad144afa0fa3d81 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Wed, 16 Jul 2025 22:26:46 +0300 Subject: [PATCH 124/137] % Code refactoring according to the PR. --- Zend/zend.c | 15 +-------------- Zend/zend_exceptions.c | 8 -------- Zend/zend_execute.h | 3 --- Zend/zend_execute_API.c | 31 ++++--------------------------- Zend/zend_gc.c | 2 -- configure.ac | 12 ------------ main/main.c | 13 +++++-------- win32/build/config.w32 | 5 +---- win32/build/confutils.js | 7 ++----- 9 files changed, 13 insertions(+), 83 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index d52459a204d17..de1bb7f7d2bbe 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -41,9 +41,7 @@ #include "Optimizer/zend_optimizer.h" #include "php.h" #include "php_globals.h" -#ifdef PHP_ASYNC_API #include "zend_async_API.h" -#endif // FIXME: Breaks the declaration of the function below #undef zenderror @@ -1335,11 +1333,7 @@ ZEND_API void zend_activate(void) /* {{{ */ void zend_call_destructors(void) /* {{{ */ { zend_try { -#ifdef PHP_ASYNC_API - shutdown_destructors_async(); -#else shutdown_destructors(); -#endif } zend_end_try(); } /* }}} */ @@ -1356,11 +1350,9 @@ ZEND_API void zend_deactivate(void) /* {{{ */ /* shutdown_executor() takes care of its own bailout handling */ shutdown_executor(); -#ifdef PHP_ASYNC_API // The execution of the True Async API should end here, // after the GC has been run. ZEND_ASYNC_ENGINE_SHUTDOWN(); -#endif zend_try { zend_ini_deactivate(); @@ -1959,18 +1951,13 @@ ZEND_API zend_result zend_execute_script(int type, zval *retval, zend_file_handl if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) { zend_user_exception_handler(); } -#ifdef PHP_ASYNC_API + // If we are inside a coroutine, // we do not call the final error handler, // as the exception will be handled higher up in the method ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN if (false == ZEND_ASYNC_CURRENT_COROUTINE && EG(exception)) { ret = zend_exception_error(EG(exception), E_ERROR); } -#else - if (EG(exception)) { - ret = zend_exception_error(EG(exception), E_ERROR); - } -#endif } zend_destroy_static_vars(op_array); destroy_op_array(op_array); diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index deda28deb1ac0..728db1abd6162 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -28,9 +28,7 @@ #include "zend_dtrace.h" #include "zend_smart_str.h" #include "zend_exceptions_arginfo.h" -#ifdef PHP_ASYNC_API #include "zend_cancellation_exception_arginfo.h" -#endif #include "zend_observer.h" #define ZEND_EXCEPTION_MESSAGE_OFF 0 @@ -45,9 +43,7 @@ ZEND_API zend_class_entry *zend_ce_throwable; ZEND_API zend_class_entry *zend_ce_exception; ZEND_API zend_class_entry *zend_ce_error_exception; ZEND_API zend_class_entry *zend_ce_error; -#ifdef PHP_ASYNC_API ZEND_API zend_class_entry *zend_ce_cancellation_exception; -#endif ZEND_API zend_class_entry *zend_ce_compile_error; ZEND_API zend_class_entry *zend_ce_parse_error; ZEND_API zend_class_entry *zend_ce_type_error; @@ -101,9 +97,6 @@ static int zend_implement_throwable(zend_class_entry *interface, zend_class_entr static inline zend_class_entry *i_get_exception_base(zend_object *object) /* {{{ */ { -#ifndef PHP_ASYNC_API - return instanceof_function(object->ce, zend_ce_exception) ? zend_ce_exception : zend_ce_error; -#else zend_class_entry *instance_ce = object->ce; do @@ -121,7 +114,6 @@ static inline zend_class_entry *i_get_exception_base(zend_object *object) /* {{{ } while (instance_ce != NULL); return NULL; -#endif } /* }}} */ diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 6b3bc50256a59..cf15c9e3b2db5 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -40,9 +40,6 @@ ZEND_API extern zend_class_entry *(*zend_autoload)(zend_string *name, zend_strin void init_executor(void); void shutdown_executor(void); void shutdown_destructors(void); -#ifdef PHP_ASYNC_API -void shutdown_destructors_async(void); -#endif ZEND_API void zend_shutdown_executor_values(bool fast_shutdown); ZEND_API void zend_init_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 64a80faf1fa9d..fbd1594583a0e 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -49,6 +49,8 @@ #include #endif +#include "zend_async_API.h" + ZEND_API void (*zend_execute_ex)(zend_execute_data *execute_data); ZEND_API void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value); ZEND_API zend_class_entry *(*zend_autoload)(zend_string *name, zend_string *lc_name); @@ -199,14 +201,12 @@ void init_executor(void) /* {{{ */ EG(filename_override) = NULL; EG(lineno_override) = -1; -#ifdef PHP_ASYNC_API EG(shutdown_context) = (zend_shutdown_context_t) { .is_started = false, .coroutine = NULL, .num_elements = 0, .idx = 0 }; -#endif zend_max_execution_timer_init(); zend_fiber_init(); @@ -257,29 +257,7 @@ static ZEND_COLD void zend_throw_or_error(int fetch_type, zend_class_entry *exce } /* }}} */ -void shutdown_destructors(void) /* {{{ */ -{ - if (CG(unclean_shutdown)) { - EG(symbol_table).pDestructor = zend_unclean_zval_ptr_dtor; - } - zend_try { - uint32_t symbols; - do { - symbols = zend_hash_num_elements(&EG(symbol_table)); - zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor); - } while (symbols != zend_hash_num_elements(&EG(symbol_table))); - zend_objects_store_call_destructors(&EG(objects_store)); - } zend_catch { - /* if we couldn't destruct cleanly, mark all objects as destructed anyway */ - zend_objects_store_mark_destructed(&EG(objects_store)); - } zend_end_try(); -} -/* }}} */ - -#ifdef PHP_ASYNC_API -#include "zend_async_API.h" - -static void shutdown_destructors_coroutine_dtor(zend_coroutine_t *coroutine) +static void shutdown_destructors_coroutine_dtor(zend_coroutine_t *coroutine) /* {{{ */ { zend_shutdown_context_t *shutdown_context = &EG(shutdown_context); @@ -318,7 +296,7 @@ static bool shutdown_destructors_context_switch_handler( return false; } -void shutdown_destructors_async(void) /* {{{ */ +void shutdown_destructors(void) /* {{{ */ { zend_coroutine_t *coroutine = ZEND_ASYNC_CURRENT_COROUTINE; bool should_continue = false; @@ -400,7 +378,6 @@ void shutdown_destructors_async(void) /* {{{ */ shutdown_context->is_started = false; } /* }}} */ -#endif /* Free values held by the executor. */ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 1d050f19e2ab6..4efeb53841db3 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -68,9 +68,7 @@ */ #include "zend.h" #include "zend_API.h" -#ifdef PHP_ASYNC_API #include "zend_async_API.h" -#endif #include "zend_compile.h" #include "zend_errors.h" #include "zend_fibers.h" diff --git a/configure.ac b/configure.ac index 7761e4a538c5f..ec934f13bdb37 100644 --- a/configure.ac +++ b/configure.ac @@ -1046,16 +1046,6 @@ PHP_ARG_ENABLE([undefined-sanitizer],, [Enable undefined sanitizer])], [no], [no]) -PHP_ARG_ENABLE([async-api],, - [AS_HELP_STRING([--disable-async-api], - [Disable async API support])], - [yes], - [no]) - -AS_VAR_IF([PHP_ASYNC_API], [yes], [ - AC_DEFINE([PHP_ASYNC_API], [1], [Define to 1 if async API support is enabled.]) -]) - dnl Extension configuration. dnl ---------------------------------------------------------------------------- @@ -1796,10 +1786,8 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ ]), [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $fiber_asm_cflag]) -if test "$PHP_ASYNC_API" = "yes"; then PHP_ADD_SOURCES([Zend], m4_normalize([zend_async_API.c]), [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $fiber_asm_cflag]) -fi PHP_ADD_MAKEFILE_FRAGMENT([$abs_srcdir/scripts/Makefile.frag], [$abs_srcdir/scripts], diff --git a/main/main.c b/main/main.c index e7e55046a1ac1..0edf5282d92b4 100644 --- a/main/main.c +++ b/main/main.c @@ -2614,10 +2614,9 @@ PHPAPI bool php_execute_script_ex(zend_file_handle *primary_file, zval *retval) if (append_file_p && result) { result = zend_execute_script(ZEND_REQUIRE, NULL, append_file_p) == SUCCESS; } - #ifdef PHP_ASYNC_API - ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN(); - ZEND_ASYNC_INITIALIZE; - #endif + + ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN(); + ZEND_ASYNC_INITIALIZE; } zend_catch { result = false; } zend_end_try(); @@ -2789,10 +2788,8 @@ PHPAPI void php_reserve_tsrm_memory(void) TSRM_ALIGNED_SIZE(zend_mm_globals_size()) + TSRM_ALIGNED_SIZE(zend_gc_globals_size()) + TSRM_ALIGNED_SIZE(sizeof(php_core_globals)) + - TSRM_ALIGNED_SIZE(sizeof(sapi_globals_struct)) -#ifdef PHP_ASYNC_API - + TSRM_ALIGNED_SIZE(sizeof(zend_async_globals_t)) -#endif + TSRM_ALIGNED_SIZE(sizeof(sapi_globals_struct)) + + TSRM_ALIGNED_SIZE(sizeof(zend_async_globals_t)) ); } /* }}} */ diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 8771bf7fb0cec..bd68db44c7b9a 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -410,7 +410,4 @@ ARG_ENABLE('vs-link-compat', 'Allow linking of libraries built with compatible v ARG_ENABLE("async-api", "Disable async API support", "yes"); -if (PHP_ASYNC_API == "yes") { - AC_DEFINE('PHP_ASYNC_API', 1); - ADD_SOURCES("Zend", "zend_async_API.c"); -} \ No newline at end of file +ADD_SOURCES("Zend", "zend_async_API.c"); \ No newline at end of file diff --git a/win32/build/confutils.js b/win32/build/confutils.js index caf99144830b0..128826f68b79f 100644 --- a/win32/build/confutils.js +++ b/win32/build/confutils.js @@ -1997,11 +1997,8 @@ function write_summary() } else { ar[k++] = ['Static analyzer', 'disabled']; } - if (typeof PHP_ASYNC_API !== "undefined" && PHP_ASYNC_API == "yes") { - ar[k++] = ['True Async API', 'Yes']; - } else { - ar[k++] = ['True Async API', 'No']; - } + + ar[k++] = ['True Async API', 'Yes']; output_as_table(["",""], ar); STDOUT.WriteBlankLines(2); From 7b5ce94cf93aa4af468b2d4924d682e9de3ca194 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:24:14 +0300 Subject: [PATCH 125/137] % remove PHP_ASYNC_API ifdef --- Zend/zend_async_API.c | 4 ---- Zend/zend_async_API.h | 1 - main/main.c | 11 ----------- 3 files changed, 16 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 4e8ca3c8e1624..40c805256aa5c 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -231,10 +231,6 @@ void zend_async_globals_ctor(void) #endif } -void zend_async_init(void) -{ -} - void zend_async_globals_dtor(void) { #ifdef ZTS diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 04f4b643807f9..2a5cd948f9b38 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -1261,7 +1261,6 @@ BEGIN_EXTERN_C() ZEND_API bool zend_async_is_enabled(void); ZEND_API bool zend_scheduler_is_enabled(void); -void zend_async_init(void); void zend_async_api_shutdown(void); void zend_async_globals_ctor(void); void zend_async_globals_dtor(void); diff --git a/main/main.c b/main/main.c index 0edf5282d92b4..bbfeb77fd5a4a 100644 --- a/main/main.c +++ b/main/main.c @@ -85,10 +85,7 @@ #include "rfc1867.h" #include "main_arginfo.h" - -#ifdef PHP_ASYNC_API #include "zend_async_API.h" -#endif /* }}} */ PHPAPI int (*php_register_internal_extensions_func)(void) = php_register_internal_extensions; @@ -146,9 +143,7 @@ PHPAPI char *php_get_version(sapi_module_struct *sapi_module) #ifdef HAVE_GCOV " GCOV" #endif -#ifdef PHP_ASYNC_API " " ZEND_ASYNC_API -#endif ); smart_string_appends(&version_info, "Copyright (c) The PHP Group\n"); if (php_build_provider()) { @@ -1935,9 +1930,7 @@ void php_request_shutdown(void *dummy) zend_call_destructors(); } zend_end_try(); -#ifdef PHP_ASYNC_API ZEND_ASYNC_RUN_SCHEDULER_AFTER_MAIN(); -#endif /* 3. Flush all output buffers */ zend_try { @@ -2186,10 +2179,8 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi php_startup_ticks(); #endif gc_globals_ctor(); -#ifdef PHP_ASYNC_API zend_async_globals_ctor(); zend_async_init_internal_context_api(); -#endif zend_observer_startup(); #if ZEND_DEBUG @@ -2515,9 +2506,7 @@ void php_module_shutdown(void) module_initialized = false; -#ifdef PHP_ASYNC_API zend_async_api_shutdown(); -#endif #ifndef ZTS core_globals_dtor(&core_globals); From 48f9e03fea0bf7fff7b709ee58e52a1abc52f288 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:51:19 +0300 Subject: [PATCH 126/137] % remove PHP_ASYNC_API ifdef --- Zend/zend_exceptions.c | 4 ---- Zend/zend_exceptions.h | 2 -- Zend/zend_fibers.c | 14 -------------- Zend/zend_globals.h | 4 ---- Zend/zend_objects_API.c | 2 -- Zend/zend_objects_API.h | 2 -- 6 files changed, 28 deletions(-) diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 728db1abd6162..dedb421df9d62 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -75,9 +75,7 @@ static int zend_implement_throwable(zend_class_entry *interface, zend_class_entr } if (zend_string_equals_literal(root->name, "Exception") || zend_string_equals_literal(root->name, "Error") -#ifdef PHP_ASYNC_API || zend_string_equals_literal(root->name, "CancellationException") -#endif ) { return SUCCESS; } @@ -835,10 +833,8 @@ void zend_register_default_exception(void) /* {{{ */ zend_ce_error = register_class_Error(zend_ce_throwable); zend_init_exception_class_entry(zend_ce_error); -#ifdef PHP_ASYNC_API zend_ce_cancellation_exception = register_class_CancellationException(zend_ce_throwable); zend_init_exception_class_entry(zend_ce_cancellation_exception); -#endif zend_ce_compile_error = register_class_CompileError(zend_ce_error); zend_init_exception_class_entry(zend_ce_compile_error); diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index 7968ea9bc7df5..33f09c0eeec12 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -30,9 +30,7 @@ extern ZEND_API zend_class_entry *zend_ce_throwable; extern ZEND_API zend_class_entry *zend_ce_exception; extern ZEND_API zend_class_entry *zend_ce_error_exception; extern ZEND_API zend_class_entry *zend_ce_error; -#ifdef PHP_ASYNC_API extern ZEND_API zend_class_entry *zend_ce_cancellation_exception; -#endif extern ZEND_API zend_class_entry *zend_ce_compile_error; extern ZEND_API zend_class_entry *zend_ce_parse_error; extern ZEND_API zend_class_entry *zend_ce_type_error; diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 13b530952f0a8..2748aa119b13a 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -564,7 +564,6 @@ static void zend_fiber_cleanup(zend_fiber_context *context) fiber->caller = NULL; } -#ifdef PHP_ASYNC_API static zend_always_inline bool can_use_fiber(void) { if (UNEXPECTED(ZEND_ASYNC_IS_ACTIVE)) { @@ -575,7 +574,6 @@ static zend_always_inline bool can_use_fiber(void) ZEND_ASYNC_DEACTIVATE; return true; } -#endif static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer) { @@ -726,11 +724,9 @@ ZEND_API zend_result zend_fiber_start(zend_fiber *fiber, zval *return_value) { ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_INIT); -#ifdef PHP_ASYNC_API if (UNEXPECTED(false == can_use_fiber())) { return FAILURE; } -#endif if (zend_fiber_init_context(&fiber->context, zend_ce_fiber, zend_fiber_execute, EG(fiber_stack_size)) == FAILURE) { return FAILURE; @@ -747,11 +743,9 @@ ZEND_API zend_result zend_fiber_start(zend_fiber *fiber, zval *return_value) ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value) { -#ifdef PHP_ASYNC_API if (UNEXPECTED(false == can_use_fiber())) { return; } -#endif ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL); @@ -764,11 +758,9 @@ ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_val ZEND_API void zend_fiber_resume_exception(zend_fiber *fiber, zval *exception, zval *return_value) { -#ifdef PHP_ASYNC_API if (UNEXPECTED(false == can_use_fiber())) { return; } -#endif ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL); @@ -781,11 +773,9 @@ ZEND_API void zend_fiber_resume_exception(zend_fiber *fiber, zval *exception, zv ZEND_API void zend_fiber_suspend(zend_fiber *fiber, zval *value, zval *return_value) { -#ifdef PHP_ASYNC_API if (UNEXPECTED(false == can_use_fiber())) { return; } -#endif fiber->stack_bottom->prev_execute_data = NULL; @@ -915,11 +905,9 @@ ZEND_METHOD(Fiber, __construct) Z_PARAM_FUNC(fci, fcc) ZEND_PARSE_PARAMETERS_END(); -#ifdef PHP_ASYNC_API if (UNEXPECTED(false == can_use_fiber())) { RETURN_THROWS(); } -#endif zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); @@ -943,11 +931,9 @@ ZEND_METHOD(Fiber, start) Z_PARAM_VARIADIC_WITH_NAMED(fiber->fci.params, fiber->fci.param_count, fiber->fci.named_params); ZEND_PARSE_PARAMETERS_END(); -#ifdef PHP_ASYNC_API if (UNEXPECTED(false == can_use_fiber())) { RETURN_THROWS(); } -#endif if (UNEXPECTED(zend_fiber_switch_blocked())) { zend_throw_error(zend_ce_fiber_error, "Cannot switch fibers in current execution context"); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 0adef66b7b042..038a13e85fcf2 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -163,14 +163,12 @@ struct _zend_compiler_globals { #endif }; -#ifdef PHP_ASYNC_API typedef struct { bool is_started; void *coroutine; uint32_t num_elements; uint32_t idx; } zend_shutdown_context_t; -#endif struct _zend_executor_globals { zval uninitialized_zval; @@ -267,10 +265,8 @@ struct _zend_executor_globals { const zend_op *opline_before_exception; zend_op exception_op[3]; -#ifdef PHP_ASYNC_API // Used to track the state of shutdown destructors in coroutines zend_shutdown_context_t shutdown_context; -#endif struct _zend_module_entry *current_module; diff --git a/Zend/zend_objects_API.c b/Zend/zend_objects_API.c index 08d3997ab8a3a..9342453533f24 100644 --- a/Zend/zend_objects_API.c +++ b/Zend/zend_objects_API.c @@ -65,7 +65,6 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_sto } } -#ifdef PHP_ASYNC_API static void store_call_destructors_coroutine_dtor(zend_coroutine_t *coroutine) { zend_shutdown_context_t *shutdown_context = &EG(shutdown_context); @@ -157,7 +156,6 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors_async(zend_objec shutdown_context->is_started = false; } -#endif ZEND_API void ZEND_FASTCALL zend_objects_store_mark_destructed(zend_objects_store *objects) { diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h index 472da8b746f9c..8bd7392de9804 100644 --- a/Zend/zend_objects_API.h +++ b/Zend/zend_objects_API.h @@ -55,9 +55,7 @@ typedef struct _zend_objects_store { BEGIN_EXTERN_C() ZEND_API void ZEND_FASTCALL zend_objects_store_init(zend_objects_store *objects, uint32_t init_size); ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_store *objects); -#ifdef PHP_ASYNC_API ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors_async(zend_objects_store *objects); -#endif ZEND_API void ZEND_FASTCALL zend_objects_store_mark_destructed(zend_objects_store *objects); ZEND_API void ZEND_FASTCALL zend_objects_store_free_object_storage(zend_objects_store *objects, bool fast_shutdown); ZEND_API void ZEND_FASTCALL zend_objects_store_destroy(zend_objects_store *objects); From dfc0d123de351923cde36b78655c0865528ab5cf Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 17 Jul 2025 23:00:01 +0300 Subject: [PATCH 127/137] * fix docs --- docs/source/miscellaneous/writing-tests.rst | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.rst index 46f03f94703ad..fd09d80f1275e 100644 --- a/docs/source/miscellaneous/writing-tests.rst +++ b/docs/source/miscellaneous/writing-tests.rst @@ -95,13 +95,13 @@ might use a variation tests to test boundary conditions. How big is a test case? ======================= -Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The -reason for this is that if we break something in PHP and it breaks your test case we need to be able -to find out quite quickly what we broke, going through 1000s of line of test case output is not -easy. Having said that it's sometimes just not practical to stay within the 10 line guideline, in -this case you can help a lot by commenting the output. You may find plenty of much longer tests in -PHP - the small tests message is something that we learnt over time, in fact we are slowly going -through and splitting tests up when we need to. +Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The reason +for this is that if we break something in PHP and it breaks your test case we need to be able to +find out quite quickly what we broke, going through 1000s of line of test case output is not easy. +Having said that it's sometimes just not practical to stay within the 10 line guideline, in this +case you can help a lot by commenting the output. You may find plenty of much longer tests in PHP - +the small tests message is something that we learnt over time, in fact we are slowly going through +and splitting tests up when we need to. Comments ======== @@ -183,10 +183,10 @@ Testing your test cases Most people who write tests for PHP don't have access to a huge number of operating systems but the tests are run on every system that runs PHP. It's good to test your test on as many platforms as you -can — Linux and Windows are the most important, it's increasingly important to make sure that -tests run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — -don't worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of -failures on other platforms. If you don't have karma to commit have a look at the next section. +can — Linux and Windows are the most important, it's increasingly important to make sure that tests +run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — don't +worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of failures on +other platforms. If you don't have karma to commit have a look at the next section. When you are testing your test case it's really important to make sure that you clean up any temporary resources (eg files) that you used in the test. There is a special ``--CLEAN--`` section From 1aead245cc687e8dc8f525772fb2905960235f5a Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 17 Jul 2025 23:14:59 +0300 Subject: [PATCH 128/137] % remove PHP_ASYNC_API --- Zend/zend_execute_API.c | 2 +- Zend/zend_gc.c | 125 ++++++++++------------------------------ 2 files changed, 31 insertions(+), 96 deletions(-) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index fbd1594583a0e..6bdc95c062fa2 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -290,7 +290,7 @@ static bool shutdown_destructors_context_switch_handler( } zend_coroutine_t *shutdown_coroutine = ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(ZEND_ASYNC_MAIN_SCOPE, 1); - shutdown_coroutine->internal_entry = shutdown_destructors_async; + shutdown_coroutine->internal_entry = shutdown_destructors; shutdown_coroutine->extended_dispose = shutdown_destructors_coroutine_dtor; return false; diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 4efeb53841db3..38634ee300126 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -259,8 +259,6 @@ struct _gc_stack { zend_refcounted *data[GC_STACK_SEGMENT_SIZE]; }; -#ifdef PHP_ASYNC_API - typedef enum { GC_ASYNC_STATE_NONE = 0, GC_ASYNC_STATE_INIT, // initial state @@ -280,7 +278,6 @@ typedef struct { zend_hrtime_t start_time; // start of full GC pass zend_hrtime_t dtor_start_time; // start of dtor phase } gc_async_context_t; -#endif typedef struct _zend_gc_globals { gc_root_buffer *buf; /* preallocated arrays of buffers */ @@ -308,13 +305,11 @@ typedef struct _zend_gc_globals { uint32_t dtor_end; zend_fiber *dtor_fiber; bool dtor_fiber_running; -#ifdef PHP_ASYNC_API gc_async_context_t async_context; /* async context for gc */ gc_stack *gc_stack; /* local mark/scan stack */ zend_coroutine_t *dtor_coroutine; zend_async_scope_t *dtor_scope; zend_async_microtask_t *microtask; -#endif #if GC_BENCH uint32_t root_buf_length; @@ -534,13 +529,11 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals) gc_globals->dtor_fiber = NULL; gc_globals->dtor_fiber_running = false; -#ifdef PHP_ASYNC_API gc_globals->dtor_coroutine = NULL; gc_globals->dtor_scope = NULL; gc_globals->microtask = NULL; gc_globals->async_context.state = GC_ASYNC_STATE_NONE; gc_globals->gc_stack = NULL; -#endif #if GC_BENCH gc_globals->root_buf_length = 0; @@ -567,11 +560,9 @@ void gc_globals_dtor(void) root_buffer_dtor(&gc_globals); #endif -#ifdef PHP_ASYNC_API if (GC_G(dtor_scope)) { GC_G(dtor_scope) = NULL; } -#endif } void gc_reset(void) @@ -1857,16 +1848,8 @@ static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t gc_root_buffer *current; zend_refcounted *p; -#ifdef PHP_ASYNC_API const bool in_coroutine = GC_G(dtor_coroutine) != NULL; -#define RESUMED_AFTER_SUSPENSION (fiber != NULL && GC_G(dtor_fiber) != fiber) \ - || (in_coroutine && GC_G(dtor_coroutine) != ZEND_ASYNC_CURRENT_COROUTINE) -#else - const bool in_coroutine = false; -#define RESUMED_AFTER_SUSPENSION fiber != NULL && GC_G(dtor_fiber) != fiber -#endif - /* The root buffer might be reallocated during destructors calls, * make sure to reload pointers as necessary. */ while (idx != end) { @@ -1886,20 +1869,18 @@ static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t GC_TRACE_REF(obj, "calling destructor"); GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); GC_ADDREF(obj); -#ifdef PHP_ASYNC_API if (in_coroutine) { ZEND_ASYNC_CURRENT_COROUTINE->extended_data = obj; } -#endif + obj->handlers->dtor_obj(obj); -#ifdef PHP_ASYNC_API if (in_coroutine) { ZEND_ASYNC_CURRENT_COROUTINE->extended_data = NULL; } -#endif GC_TRACE_REF(obj, "returned from destructor"); GC_DELREF(obj); - if (UNEXPECTED(RESUMED_AFTER_SUSPENSION)) { + if (UNEXPECTED((fiber != NULL && GC_G(dtor_fiber) != fiber) + || (in_coroutine && GC_G(dtor_coroutine) != ZEND_ASYNC_CURRENT_COROUTINE))) { /* We resumed after suspension */ gc_check_possible_root((zend_refcounted*)&obj->gc); return FAILURE; @@ -1974,7 +1955,6 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end) } } -#ifdef PHP_ASYNC_API static void zend_gc_collect_cycles_microtask(zend_async_microtask_t *task); static void zend_gc_collect_cycles_microtask_dtor(zend_async_microtask_t *task) @@ -2134,12 +2114,9 @@ static zend_always_inline void start_gc_in_coroutine(void) zend_error_noreturn(E_ERROR, "Unable to spawn destructor coroutine"); } } -#endif ZEND_API int zend_gc_collect_cycles(void) { -#ifdef PHP_ASYNC_API - if (UNEXPECTED(ZEND_ASYNC_IS_ACTIVE && ZEND_ASYNC_CURRENT_COROUTINE != GC_G(dtor_coroutine))) { if (GC_G(dtor_coroutine)) { @@ -2150,24 +2127,8 @@ ZEND_API int zend_gc_collect_cycles(void) return 0; } -#endif - zend_hrtime_t dtor_start_time = 0; -#ifdef PHP_ASYNC_API -#define GC_COLLECT_TOTAL_COUNT (context->total_count) -#define GC_COLLECT_COUNT (context->count) -#define GC_COLLECT_START_TIME (context->start_time) -#define GC_COLLECT_SHOULD_RERUN_GC (context->should_rerun_gc) -#define GC_COLLECT_DID_RERUN_GC (context->did_rerun_gc) -#define GC_COLLECT_GC_FLAGS (context->gc_flags) -#define GC_COLLECT_STACK (stack) -#define GC_COLLECT_FREE_STACK GC_G(gc_stack) = NULL;\ - gc_stack_free(stack); \ - efree(stack) -#define GC_COLLECT_FINISH_0 GC_G(async_context).state = GC_ASYNC_STATE_NONE; \ - return 0 - // Someone is trying to invoke GC from within a destructor? // We don’t know what that is, but we have nothing to do here. if (UNEXPECTED(GC_G(async_context).state == GC_ASYNC_STATE_RUNNING)) { @@ -2225,23 +2186,6 @@ ZEND_API int zend_gc_collect_cycles(void) context->state = GC_ASYNC_STATE_RUNNING; -#else -#define GC_COLLECT_COUNT count -#define GC_COLLECT_TOTAL_COUNT total_count -#define GC_COLLECT_START_TIME start_time -#define GC_COLLECT_SHOULD_RERUN_GC should_rerun_gc -#define GC_COLLECT_DID_RERUN_GC did_rerun_gc -#define GC_COLLECT_GC_FLAGS gc_flags -#define GC_COLLECT_STACK (&stack) -#define GC_COLLECT_FREE_STACK gc_stack_free(&stack); -#define GC_COLLECT_FINISH_0 return 0 - - int total_count = 0; - bool should_rerun_gc = 0; - bool did_rerun_gc = 0; - - zend_hrtime_t start_time = zend_hrtime(); -#endif if (GC_G(num_roots) && !GC_G(gc_active)) { zend_gc_remove_root_tmpvars(); @@ -2252,20 +2196,15 @@ ZEND_API int zend_gc_collect_cycles(void) gc_root_buffer *current, *last; zend_refcounted *p; uint32_t gc_flags = 0; - uint32_t idx, end; -#ifdef PHP_ASYNC_API + uint32_t idx, end = 0; + stack->next = NULL; stack->prev = NULL; -#else - int count; - gc_stack stack; - stack.prev = NULL; - stack.next = NULL; -#endif if (GC_G(gc_active)) { - GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; - GC_COLLECT_FINISH_0; + GC_G(collector_time) += zend_hrtime() - context->start_time; + GC_G(async_context).state = GC_ASYNC_STATE_NONE; + return 0; } GC_TRACE("Collecting cycles"); @@ -2273,17 +2212,19 @@ ZEND_API int zend_gc_collect_cycles(void) GC_G(gc_active) = 1; GC_TRACE("Marking roots"); - gc_mark_roots(GC_COLLECT_STACK); + gc_mark_roots(stack); GC_TRACE("Scanning roots"); - gc_scan_roots(GC_COLLECT_STACK); + gc_scan_roots(stack); GC_TRACE("Collecting roots"); - GC_COLLECT_COUNT = gc_collect_roots(&gc_flags, GC_COLLECT_STACK); + context->count = gc_collect_roots(&gc_flags, stack); if (!GC_G(num_roots)) { /* nothing to free */ GC_TRACE("Nothing to free"); - GC_COLLECT_FREE_STACK; + GC_G(gc_stack) = NULL; + gc_stack_free(stack); + efree(stack); GC_G(gc_active) = 0; goto finish; } @@ -2298,7 +2239,7 @@ ZEND_API int zend_gc_collect_cycles(void) * modify any refcounts, so we have no real way to detect this situation * short of rerunning full GC tracing. What we do instead is to only run * destructors at this point and automatically re-run GC afterwards. */ - GC_COLLECT_SHOULD_RERUN_GC = 1; + context->should_rerun_gc = 1; /* Mark all roots for which a dtor will be invoked as DTOR_GARBAGE. Additionally * color them purple. This serves a double purpose: First, they should be @@ -2332,7 +2273,7 @@ ZEND_API int zend_gc_collect_cycles(void) while (idx != end) { if (GC_IS_DTOR_GARBAGE(current->ref)) { p = GC_GET_PTR(current->ref); - GC_COLLECT_COUNT -= gc_remove_nested_data_from_buffer(p, current, GC_COLLECT_STACK); + context->count -= gc_remove_nested_data_from_buffer(p, current, stack); } current++; idx++; @@ -2341,9 +2282,7 @@ ZEND_API int zend_gc_collect_cycles(void) /* Actually call destructors. */ dtor_start_time = zend_hrtime(); if (EXPECTED(!EG(active_fiber))) { -#ifdef PHP_ASYNC_API continue_calling_destructors: - end = GC_G(first_unused); if (UNEXPECTED(FAILURE == gc_call_destructors(GC_G(dtor_idx), GC_G(first_unused), NULL))) { // // gc_call_destructors returns FAILURE when a destructor interrupts execution, @@ -2352,11 +2291,8 @@ ZEND_API int zend_gc_collect_cycles(void) // because the process continues elsewhere. // GC_G(dtor_time) += zend_hrtime() - dtor_start_time; - return GC_COLLECT_TOTAL_COUNT; + return context->total_count; } -#else - gc_call_destructors(GC_FIRST_ROOT, end, NULL); -#endif } else { gc_call_destructors_in_fiber(end); } @@ -2365,18 +2301,17 @@ ZEND_API int zend_gc_collect_cycles(void) if (GC_G(gc_protected)) { /* something went wrong */ zend_get_gc_buffer_release(); - GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; - GC_COLLECT_FINISH_0; + GC_G(collector_time) += zend_hrtime() - context->start_time; + GC_G(async_context).state = GC_ASYNC_STATE_NONE; + return 0; } } - gc_stack_free(GC_COLLECT_STACK); + gc_stack_free(stack); -#ifdef PHP_ASYNC_API if (false == in_fiber) { end = GC_G(first_unused); } -#endif /* Destroy zvals. The root buffer may be reallocated. */ GC_TRACE("Destroying zvals"); @@ -2434,8 +2369,8 @@ ZEND_API int zend_gc_collect_cycles(void) GC_G(free_time) += zend_hrtime() - free_start_time; GC_TRACE("Collection finished"); - GC_G(collected) += GC_COLLECT_COUNT; - GC_COLLECT_TOTAL_COUNT += GC_COLLECT_COUNT; + GC_G(collected) += context->count; + context->total_count += context->count; GC_G(gc_active) = 0; } @@ -2444,8 +2379,8 @@ ZEND_API int zend_gc_collect_cycles(void) /* Objects with destructors were removed from this GC run. Rerun GC right away to clean them * up. We do this only once: If we encounter more destructors on the second run, we'll not * run GC another time. */ - if (GC_COLLECT_SHOULD_RERUN_GC && !GC_COLLECT_DID_RERUN_GC) { - GC_COLLECT_DID_RERUN_GC = 1; + if (context->should_rerun_gc && !context->did_rerun_gc) { + context->did_rerun_gc = 1; goto rerun_gc; } @@ -2458,10 +2393,10 @@ ZEND_API int zend_gc_collect_cycles(void) zend_gc_check_root_tmpvars(); GC_G(gc_active) = 0; - GC_G(collector_time) += zend_hrtime() - GC_COLLECT_START_TIME; -#ifdef PHP_ASYNC_API + GC_G(collector_time) += zend_hrtime() - context->start_time; + if (in_fiber) { - const int total_count = GC_COLLECT_TOTAL_COUNT; + const int total_count = context->total_count; efree(context); efree(stack); return total_count; @@ -2472,8 +2407,8 @@ ZEND_API int zend_gc_collect_cycles(void) efree(stack); } } -#endif - return GC_COLLECT_TOTAL_COUNT; + + return context->total_count; } ZEND_API void zend_gc_get_status(zend_gc_status *status) From a32b9c773223a58d623ae517663d7bfca9031143 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 17 Jul 2025 23:24:22 +0300 Subject: [PATCH 129/137] % remove zval_call_destructor --- Zend/zend_execute_API.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 6bdc95c062fa2..2e0ec8c464bcf 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -216,19 +216,6 @@ void init_executor(void) /* {{{ */ } /* }}} */ -static int zval_call_destructor(zval *zv) /* {{{ */ -{ - if (Z_TYPE_P(zv) == IS_INDIRECT) { - zv = Z_INDIRECT_P(zv); - } - if (Z_TYPE_P(zv) == IS_OBJECT && Z_REFCOUNT_P(zv) == 1) { - return ZEND_HASH_APPLY_REMOVE; - } else { - return ZEND_HASH_APPLY_KEEP; - } -} -/* }}} */ - static void zend_unclean_zval_ptr_dtor(zval *zv) /* {{{ */ { if (Z_TYPE_P(zv) == IS_INDIRECT) { From e6b31813f7d278719156ca1647e1162052843136 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Thu, 17 Jul 2025 23:46:37 +0300 Subject: [PATCH 130/137] * fix memleack gc --- Zend/zend_gc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 38634ee300126..6d8b94dec5f2e 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2222,9 +2222,7 @@ ZEND_API int zend_gc_collect_cycles(void) if (!GC_G(num_roots)) { /* nothing to free */ GC_TRACE("Nothing to free"); - GC_G(gc_stack) = NULL; gc_stack_free(stack); - efree(stack); GC_G(gc_active) = 0; goto finish; } From 03e32c5d009102ae8527d4e011834d0f4c33fd4d Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:00:07 +0300 Subject: [PATCH 131/137] % update email --- Zend/zend_async_API.c | 12 ++++-------- Zend/zend_async_API.h | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 40c805256aa5c..0cfd248e54ee0 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -10,7 +10,7 @@ | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ - | Author: Edmond | + | Author: Edmond | +----------------------------------------------------------------------+ */ #include "zend_async_API.h" @@ -45,11 +45,7 @@ static void enqueue_coroutine(zend_coroutine_t *coroutine) ASYNC_THROW_ERROR("Async API is not enabled"); } -static void engine_shutdown_stub(void) -{ -} - -static void shutdown_stub(void) +static void void_stub(void) { } @@ -135,8 +131,8 @@ zend_async_enqueue_coroutine_t zend_async_enqueue_coroutine_fn = enqueue_corouti zend_async_resume_t zend_async_resume_fn = NULL; zend_async_cancel_t zend_async_cancel_fn = NULL; zend_async_spawn_and_throw_t zend_async_spawn_and_throw_fn = spawn_and_throw; -zend_async_shutdown_t zend_async_shutdown_fn = shutdown_stub; -zend_async_engine_shutdown_t zend_async_engine_shutdown_fn = engine_shutdown_stub; +zend_async_shutdown_t zend_async_shutdown_fn = void_stub; +zend_async_engine_shutdown_t zend_async_engine_shutdown_fn = void_stub; zend_async_get_coroutines_t zend_async_get_coroutines_fn = get_coroutines_stub; zend_async_add_microtask_t zend_async_add_microtask_fn = add_microtask_stub; zend_async_get_awaiting_info_t zend_async_get_awaiting_info_fn = get_awaiting_info_stub; diff --git a/Zend/zend_async_API.h b/Zend/zend_async_API.h index 2a5cd948f9b38..36a5294309896 100644 --- a/Zend/zend_async_API.h +++ b/Zend/zend_async_API.h @@ -10,7 +10,7 @@ | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ - | Author: Edmond | + | Author: Edmond | +----------------------------------------------------------------------+ */ #ifndef ZEND_ASYNC_API_H From 4310a08959a5e7e5f2468cde1726b0f8ed52d2f2 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:11:59 +0300 Subject: [PATCH 132/137] =?UTF-8?q?*=20fix=20error:=20=E2=80=98end?= =?UTF-8?q?=E2=80=99=20may=20be=20used=20uninitialized?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Zend/zend_gc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 6d8b94dec5f2e..5fcd9a6685de7 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -2281,6 +2281,7 @@ ZEND_API int zend_gc_collect_cycles(void) dtor_start_time = zend_hrtime(); if (EXPECTED(!EG(active_fiber))) { continue_calling_destructors: + end = GC_G(first_unused); if (UNEXPECTED(FAILURE == gc_call_destructors(GC_G(dtor_idx), GC_G(first_unused), NULL))) { // // gc_call_destructors returns FAILURE when a destructor interrupts execution, From 9486c427c7b0988ac1baf448d611d8c51c277bbd Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:39:51 +0300 Subject: [PATCH 133/137] + zend_async_init_internal_context_api early initialization of the async context --- main/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/main.c b/main/main.c index bbfeb77fd5a4a..6322d0e24b0e6 100644 --- a/main/main.c +++ b/main/main.c @@ -2158,6 +2158,7 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi module_shutdown = false; module_startup = true; + zend_async_init_internal_context_api(); sapi_initialize_empty_request(); sapi_activate(); @@ -2180,7 +2181,6 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi #endif gc_globals_ctor(); zend_async_globals_ctor(); - zend_async_init_internal_context_api(); zend_observer_startup(); #if ZEND_DEBUG From 460e4fa947105a2e9e1630995f8424002993f864 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 20 Jul 2025 07:59:06 +0300 Subject: [PATCH 134/137] * fix zend_async_event_callback_new --- Zend/zend_async_API.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 0cfd248e54ee0..e8732d3772704 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -423,8 +423,11 @@ static void event_callback_dispose(zend_async_event_callback_t *callback, zend_a ZEND_API zend_async_event_callback_t *zend_async_event_callback_new( zend_async_event_callback_fn callback, size_t size) { + ZEND_ASSERT(size == 0 || size >= sizeof(zend_async_event_callback_t) && + "Size must be at least sizeof(zend_async_event_callback_t)"); + zend_async_event_callback_t *event_callback - = ecalloc(1, size == 0 ? size : sizeof(zend_async_event_callback_t)); + = ecalloc(1, size != 0 ? size : sizeof(zend_async_event_callback_t)); event_callback->ref_count = 1; event_callback->callback = callback; From 50b16fc3348f6e49be25566dc84aec95048f737a Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 20 Jul 2025 08:00:31 +0300 Subject: [PATCH 135/137] * fix zend_async_coroutine_callback_new --- Zend/zend_async_API.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index e8732d3772704..cc624bc7782a6 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -442,6 +442,9 @@ void coroutine_event_callback_dispose( ZEND_API zend_coroutine_event_callback_t *zend_async_coroutine_callback_new( zend_coroutine_t *coroutine, zend_async_event_callback_fn callback, size_t size) { + ZEND_ASSERT(size == 0 || size >= sizeof(zend_coroutine_event_callback_t) && + "Size must be at least sizeof(zend_coroutine_event_callback_t)"); + zend_coroutine_event_callback_t *coroutine_callback = ecalloc(1, size != 0 ? size : sizeof(zend_coroutine_event_callback_t)); From f7b02c3644eeebd91badbdc44f29d941e87767d8 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 20 Jul 2025 08:24:12 +0300 Subject: [PATCH 136/137] * fix zend_async_coroutine_callback_new warning --- Zend/zend_async_API.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index cc624bc7782a6..1affa7bf4f0a6 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -423,7 +423,7 @@ static void event_callback_dispose(zend_async_event_callback_t *callback, zend_a ZEND_API zend_async_event_callback_t *zend_async_event_callback_new( zend_async_event_callback_fn callback, size_t size) { - ZEND_ASSERT(size == 0 || size >= sizeof(zend_async_event_callback_t) && + ZEND_ASSERT((size == 0 || size >= sizeof(zend_async_event_callback_t)) && "Size must be at least sizeof(zend_async_event_callback_t)"); zend_async_event_callback_t *event_callback @@ -442,7 +442,7 @@ void coroutine_event_callback_dispose( ZEND_API zend_coroutine_event_callback_t *zend_async_coroutine_callback_new( zend_coroutine_t *coroutine, zend_async_event_callback_fn callback, size_t size) { - ZEND_ASSERT(size == 0 || size >= sizeof(zend_coroutine_event_callback_t) && + ZEND_ASSERT((size == 0 || size >= sizeof(zend_coroutine_event_callback_t)) && "Size must be at least sizeof(zend_coroutine_event_callback_t)"); zend_coroutine_event_callback_t *coroutine_callback From 20991b1af304a89570835945769897ce09cf54e4 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+EdmondDantes@users.noreply.github.com> Date: Sun, 20 Jul 2025 08:44:18 +0300 Subject: [PATCH 137/137] % remove CANCELLATION exception from PHPCore --- Zend/zend_async_API.c | 6 +- Zend/zend_cancellation_exception.stub.php | 63 ------------- Zend/zend_cancellation_exception_arginfo.h | 102 --------------------- Zend/zend_exceptions.c | 27 +----- Zend/zend_exceptions.h | 1 - 5 files changed, 6 insertions(+), 193 deletions(-) delete mode 100644 Zend/zend_cancellation_exception.stub.php delete mode 100644 Zend/zend_cancellation_exception_arginfo.h diff --git a/Zend/zend_async_API.c b/Zend/zend_async_API.c index 1affa7bf4f0a6..66565e8e78d7c 100644 --- a/Zend/zend_async_API.c +++ b/Zend/zend_async_API.c @@ -115,7 +115,7 @@ static zend_class_entry *get_class_ce(zend_async_class type) || type == ZEND_ASYNC_EXCEPTION_INPUT_OUTPUT) { return zend_ce_exception; } else if (type == ZEND_ASYNC_EXCEPTION_CANCELLATION) { - return zend_ce_cancellation_exception; + return zend_ce_error; } return NULL; @@ -908,7 +908,9 @@ ZEND_API bool zend_async_waker_apply_error(zend_async_waker_t *waker, zend_objec return true; } - if (for_cancellation && instanceof_function(waker->error->ce, zend_ce_cancellation_exception)) { + const zend_class_entry *ce_cancellation_exception = ZEND_ASYNC_GET_EXCEPTION_CE(ZEND_ASYNC_EXCEPTION_CANCELLATION); + + if (for_cancellation && instanceof_function(waker->error->ce, ce_cancellation_exception)) { // If the waker already has a cancellation exception, we do not override it if (tranfer_error) { OBJ_RELEASE(error); diff --git a/Zend/zend_cancellation_exception.stub.php b/Zend/zend_cancellation_exception.stub.php deleted file mode 100644 index 8ffdd2a0fdc33..0000000000000 --- a/Zend/zend_cancellation_exception.stub.php +++ /dev/null @@ -1,63 +0,0 @@ -parent; } if (zend_string_equals_literal(root->name, "Exception") - || zend_string_equals_literal(root->name, "Error") - || zend_string_equals_literal(root->name, "CancellationException") - ) { + || zend_string_equals_literal(root->name, "Error")) { return SUCCESS; } @@ -95,23 +91,7 @@ static int zend_implement_throwable(zend_class_entry *interface, zend_class_entr static inline zend_class_entry *i_get_exception_base(zend_object *object) /* {{{ */ { - zend_class_entry *instance_ce = object->ce; - - do - { - if (instance_ce == zend_ce_exception) { - return zend_ce_exception; - } else if (instance_ce == zend_ce_error) { - return zend_ce_error; - } else if (instance_ce == zend_ce_cancellation_exception) { - return zend_ce_cancellation_exception; - } - - instance_ce = instance_ce->parent; - - } while (instance_ce != NULL); - - return NULL; + return instanceof_function(object->ce, zend_ce_exception) ? zend_ce_exception : zend_ce_error; } /* }}} */ @@ -833,9 +813,6 @@ void zend_register_default_exception(void) /* {{{ */ zend_ce_error = register_class_Error(zend_ce_throwable); zend_init_exception_class_entry(zend_ce_error); - zend_ce_cancellation_exception = register_class_CancellationException(zend_ce_throwable); - zend_init_exception_class_entry(zend_ce_cancellation_exception); - zend_ce_compile_error = register_class_CompileError(zend_ce_error); zend_init_exception_class_entry(zend_ce_compile_error); diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index 33f09c0eeec12..5df49dcd6a3a6 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -30,7 +30,6 @@ extern ZEND_API zend_class_entry *zend_ce_throwable; extern ZEND_API zend_class_entry *zend_ce_exception; extern ZEND_API zend_class_entry *zend_ce_error_exception; extern ZEND_API zend_class_entry *zend_ce_error; -extern ZEND_API zend_class_entry *zend_ce_cancellation_exception; extern ZEND_API zend_class_entry *zend_ce_compile_error; extern ZEND_API zend_class_entry *zend_ce_parse_error; extern ZEND_API zend_class_entry *zend_ce_type_error;