schedule() re-polls synchronously on wake, violating the Waker contract
Problem
In src/async_/spawn.rs, schedule() runs the task synchronously when it is
woken from outside its own poll:
fn schedule(runnable: Runnable, info: ScheduleInfo) {
if info.woken_while_running {
SCHEDULER.schedule(runnable);
...
} else {
runnable.run(); // synchronous re-poll on the waker's stack
}
}
Waker::wake() may be called from arbitrary contexts, including a future's
Drop while a lock is held. The canonical case is h2's Streams::drop, which
wakes its parked Connection task while holding Arc<Mutex<Inner>>. The
synchronous re-poll re-enters that task, which tries to lock the same Mutex
→ deadlock. Any future whose wake fires from a lock-holding, non-poll context
hits this; wake() is required to be non-blocking and non-re-entrant.
Reproducer
Freestanding, no deps beyond async_task: a Drop impl wakes a parked task
while holding a lock. Synchronous re-poll observes the lock still held (surfaced
via Mutex::try_lock → WouldBlock, so it doesn't hang); a deferred wake
acquires the lock cleanly. Included in the linked PR.
Suggested fix
Always defer the wake via ngx_post_event (re-poll on the next event-loop tick)
instead of runnable.run(). One tick of latency on the single-threaded loop.
schedule()re-polls synchronously on wake, violating theWakercontractProblem
In
src/async_/spawn.rs,schedule()runs the task synchronously when it iswoken from outside its own poll:
Waker::wake()may be called from arbitrary contexts, including a future'sDropwhile a lock is held. The canonical case is h2'sStreams::drop, whichwakes its parked
Connectiontask while holdingArc<Mutex<Inner>>. Thesynchronous re-poll re-enters that task, which tries to lock the same
Mutex→ deadlock. Any future whose wake fires from a lock-holding, non-poll context
hits this;
wake()is required to be non-blocking and non-re-entrant.Reproducer
Freestanding, no deps beyond
async_task: aDropimpl wakes a parked taskwhile holding a lock. Synchronous re-poll observes the lock still held (surfaced
via
Mutex::try_lock→WouldBlock, so it doesn't hang); a deferred wakeacquires the lock cleanly. Included in the linked PR.
Suggested fix
Always defer the wake via
ngx_post_event(re-poll on the next event-loop tick)instead of
runnable.run(). One tick of latency on the single-threaded loop.