From 6f6efbff8b0b1b18fb66ffb02cc923dca66f89d0 Mon Sep 17 00:00:00 2001 From: Christopher van de Sande Date: Fri, 29 May 2026 10:34:10 +0100 Subject: [PATCH] async: always defer task wakes via ngx_post_event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `schedule()` ran `runnable.run()` synchronously when a task was woken from outside its own poll (`woken_while_running == false`). That violates the `Waker::wake()` contract (wakes must be non-blocking and non-re-entrant): when a wake fires from a `Drop` that holds a lock the woken task also needs — e.g. h2's `Streams::drop` waking its `Connection` task while holding `Arc>` — the synchronous re-poll re-enters and deadlocks on that lock. Always defer the wake via `ngx_post_event` instead; the runnable is re-polled on the next event-loop tick by `ngx_event_process_posted`. On the single-threaded event loop that is one worker-local list insert — one tick of latency. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/async_/spawn.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/async_/spawn.rs b/src/async_/spawn.rs index fd32bed1..fa6008ff 100644 --- a/src/async_/spawn.rs +++ b/src/async_/spawn.rs @@ -118,13 +118,16 @@ impl Drop for SchedulerInner { } } -fn schedule(runnable: Runnable, info: ScheduleInfo) { - if info.woken_while_running { - SCHEDULER.schedule(runnable); - ngx_log_debug!(ngx_cycle_log().as_ptr(), "async: task scheduled while running"); - } else { - runnable.run(); - } +fn schedule(runnable: Runnable, _info: ScheduleInfo) { + // Always defer the wake via `ngx_post_event`; never re-poll synchronously. + // + // `Waker::wake()` may fire from arbitrary contexts, including a future's + // `Drop` while a lock is held (e.g. h2's `Streams::drop` wakes its parked + // `Connection` task while holding `Arc>`). A synchronous + // re-poll would re-enter the task and deadlock on that lock. Deferring + // costs one event-loop tick: `ngx_event_process_posted` drains the queue + // at the end of each cycle. + SCHEDULER.schedule(runnable); } /// Creates a new task running on the NGINX event loop.