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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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