Skip to content

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

@CVanF5

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions