Split out of the #580 / DEBT-3 work so it does not change 14 call sites' behaviour in a PR whose subject is the inventory.
The incompleteness
Scheduler::block_current (kernel/src/task/scheduler.rs) charges elapsed CPU ticks and publishes ThreadState::Blocked. It does not:
- remove the thread from the ready queue, and
- set
blocked_in_syscall.
Its sibling primitives do both — block_current_for_io_publish sets blocked_in_syscall and block_current_for_io_with_timeout scrubs the per-CPU queues; block_current_for_timer and block_current_for_compositor scrub too.
So every one of block_current's 14 production callers open-codes one or both afterwards:
kernel/src/syscall/handlers.rs (7)
kernel/src/syscall/socket.rs (4)
kernel/src/syscall/fs.rs (1)
kernel/src/net/loopback_pump.rs (1)
kernel/src/test_framework/registry.rs (1)
plus kthread_park() (kernel/src/task/kthread.rs), which the #580 PR converted to call it and which keeps its own remove_from_ready_queue.
Why it matters
A blocking primitive whose contract is "publish the state, caller does the rest" is exactly the shape P9's no-new-block admission interlock has to install a guard into. If the primitive is incomplete, every caller is a place the guard can be bypassed or the post-conditions can be forgotten — which is the failure mode DEBT-3 exists to close.
Why it was not fixed in the #580 PR
Completing the primitive changes the behaviour of 14 call sites in one commit, several on live syscall paths (socket, fs, handlers). That is a different PR with a different gate profile. The #580 PR discloses it rather than leaving it undisclosed.
Suggested disposition
P9, alongside the interlock: complete block_current (queue removal + blocked_in_syscall, matching the family), then delete the open-coded follow-ups at the 14 callers, with a census pinning that no caller re-adds one.
Referenced by #580 and by docs/planning/teardown-unification/{DESIGN,PLAN}.md's DEBT-3 rows.
Split out of the #580 / DEBT-3 work so it does not change 14 call sites' behaviour in a PR whose subject is the inventory.
The incompleteness
Scheduler::block_current(kernel/src/task/scheduler.rs) charges elapsed CPU ticks and publishesThreadState::Blocked. It does not:blocked_in_syscall.Its sibling primitives do both —
block_current_for_io_publishsetsblocked_in_syscallandblock_current_for_io_with_timeoutscrubs the per-CPU queues;block_current_for_timerandblock_current_for_compositorscrub too.So every one of
block_current's 14 production callers open-codes one or both afterwards:kernel/src/syscall/handlers.rs(7)kernel/src/syscall/socket.rs(4)kernel/src/syscall/fs.rs(1)kernel/src/net/loopback_pump.rs(1)kernel/src/test_framework/registry.rs(1)plus
kthread_park()(kernel/src/task/kthread.rs), which the #580 PR converted to call it and which keeps its ownremove_from_ready_queue.Why it matters
A blocking primitive whose contract is "publish the state, caller does the rest" is exactly the shape P9's no-new-block admission interlock has to install a guard into. If the primitive is incomplete, every caller is a place the guard can be bypassed or the post-conditions can be forgotten — which is the failure mode DEBT-3 exists to close.
Why it was not fixed in the #580 PR
Completing the primitive changes the behaviour of 14 call sites in one commit, several on live syscall paths (
socket,fs,handlers). That is a different PR with a different gate profile. The #580 PR discloses it rather than leaving it undisclosed.Suggested disposition
P9, alongside the interlock: complete
block_current(queue removal +blocked_in_syscall, matching the family), then delete the open-coded follow-ups at the 14 callers, with a census pinning that no caller re-adds one.Referenced by #580 and by
docs/planning/teardown-unification/{DESIGN,PLAN}.md's DEBT-3 rows.