Skip to content

schedule() re-polls synchronously on wake, violating the Waker contract #294

Description

@CVanF5

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_lockWouldBlock, 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions