From 9832f423478cc86a4299e1c493bc4ed28cc80a5f Mon Sep 17 00:00:00 2001 From: Edmond <1571649+edmonddantes@users.noreply.github.com> Date: Tue, 7 Jul 2026 06:22:47 +0000 Subject: [PATCH] fix(pool): mark the connection-pool healthcheck timer HIDDEN so it does not block a graceful worker shutdown (true-async/server#93) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pool healthcheck timer is a background heartbeat, but pool_start_healthcheck_timer started it as a normal reactor event, so it counted toward active_event_count. An idle pool (e.g. a warmed PDO pool with ATTR_POOL_HEALTHCHECK_INTERVAL > 0) therefore kept ZEND_ASYNC_REACTOR_LOOP_ALIVE() true after all coroutines were cancelled on graceful shutdown. On worker hot-reload the scheduler fiber then tripped ZEND_ASSERT(REACTOR_LOOP_ALIVE() == false) in fiber_entry (debug build); on release the idle heartbeat needlessly kept the retiring worker's loop alive. The HIDDEN flag's own documentation lists "background timers (health checks)" as the canonical case, so mark the timer HIDDEN — it still fires; it just no longer counts as application work for deadlock/loop-alive accounting. --- pool.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pool.c b/pool.c index 4a13009f..698302ac 100644 --- a/pool.c +++ b/pool.c @@ -667,6 +667,12 @@ static void pool_start_healthcheck_timer(async_pool_t *pool) return; } + /* The healthcheck timer is a background heartbeat, not application work. + * Mark it HIDDEN so it does not count toward active_event_count — otherwise + * an idle pool keeps the reactor loop alive and blocks a graceful worker + * shutdown (e.g. on hot-reload), tripping the scheduler's loop-alive assert. */ + ZEND_ASYNC_EVENT_SET_HIDDEN(&pool->healthcheck_timer->base); + /* Use inline callback embedded in pool structure */ pool->healthcheck_callback.ref_count = 1; pool->healthcheck_callback.callback = pool_healthcheck_timer_callback;