Summary
Scheduler::wake_expired_timers() has an arm that deliberately does not
enqueue a thread it has just published as Ready:
- it pops the expired timer-heap entry,
- publishes
ThreadState::Ready,
- clears
wake_time_ns,
- and returns without putting the thread on any ready queue,
on the documented assumption that either the running thread notices after its
own halt, or DEFERRED_REQUEUE picks it up. That arm is taken whenever
is_current_on_any_cpu is true — i.e. whenever the woken thread is still named
by some CPU's current_thread.
Both stated fallbacks are aarch64-only. DEFERRED_REQUEUE, and every caller
of requeue_thread_after_save(), live in
kernel/src/arch_impl/aarch64/context_switch.rs. x86 has zero of either. So on
x86 the enqueue in that arm is unowned: whether the thread ever runs again
depends entirely on the caller noticing for itself.
Status: latent, not demonstrated
Nothing reachable exercises it today, and this is explicitly not a claim
that a live wake is being lost on x86. The one caller that made the window
deterministic — sys_poll, which re-arms a 1 ms slice so its deadline has
essentially always expired by the time schedule() runs — was investigated
under #568 and the mechanism could not be demonstrated: the mutation offered as
proof did not separate, and an A/B battery on beast KVM (8 boots per arm,
differing only by the ordering hunk) showed zero poll-oracle failures on either
side. See docs/planning/green-program/sockets/EVIDENCE-2026-08-29.md §5 and §9.
What remains after that investigation is the structural asymmetry itself, which
is real regardless: an architecture-neutral scheduler path documents two
fallbacks that exist on one architecture only.
Why file it
Any future x86 caller that blocks with a short deadline lands in the same
arm with no owner for its enqueue, and the failure mode is silent — the thread
is Ready, in no queue, with no heap entry. The code reads as if a fallback
exists.
Suggested shape
Either give the arm an owner on x86 (the x86 equivalent of
requeue_thread_after_save() / DEFERRED_REQUEUE), or make the arm's comment
name the architecture its fallbacks belong to and refuse — loudly — when it is
reached on an architecture that has none. Silently relying on an absent
mechanism is the shape this campaign has been closing under the "unowned
delivery" heading (#669).
Disclosed by-catch from #568.
Summary
Scheduler::wake_expired_timers()has an arm that deliberately does notenqueue a thread it has just published as
Ready:ThreadState::Ready,wake_time_ns,on the documented assumption that either the running thread notices after its
own halt, or
DEFERRED_REQUEUEpicks it up. That arm is taken wheneveris_current_on_any_cpuis true — i.e. whenever the woken thread is still namedby some CPU's
current_thread.Both stated fallbacks are aarch64-only.
DEFERRED_REQUEUE, and every callerof
requeue_thread_after_save(), live inkernel/src/arch_impl/aarch64/context_switch.rs. x86 has zero of either. So onx86 the enqueue in that arm is unowned: whether the thread ever runs again
depends entirely on the caller noticing for itself.
Status: latent, not demonstrated
Nothing reachable exercises it today, and this is explicitly not a claim
that a live wake is being lost on x86. The one caller that made the window
deterministic —
sys_poll, which re-arms a 1 ms slice so its deadline hasessentially always expired by the time
schedule()runs — was investigatedunder #568 and the mechanism could not be demonstrated: the mutation offered as
proof did not separate, and an A/B battery on beast KVM (8 boots per arm,
differing only by the ordering hunk) showed zero poll-oracle failures on either
side. See
docs/planning/green-program/sockets/EVIDENCE-2026-08-29.md§5 and §9.What remains after that investigation is the structural asymmetry itself, which
is real regardless: an architecture-neutral scheduler path documents two
fallbacks that exist on one architecture only.
Why file it
Any future x86 caller that blocks with a short deadline lands in the same
arm with no owner for its enqueue, and the failure mode is silent — the thread
is
Ready, in no queue, with no heap entry. The code reads as if a fallbackexists.
Suggested shape
Either give the arm an owner on x86 (the x86 equivalent of
requeue_thread_after_save()/DEFERRED_REQUEUE), or make the arm's commentname the architecture its fallbacks belong to and refuse — loudly — when it is
reached on an architecture that has none. Silently relying on an absent
mechanism is the shape this campaign has been closing under the "unowned
delivery" heading (#669).
Disclosed by-catch from #568.