Skip to content

fix(executor): keep the hot-queue walk alive across nested ticks - #1059

Open
x-at-01 wants to merge 1 commit into
compio-rs:masterfrom
webc-fork:fix/executor-tick-reentrant
Open

x-at-01 wants to merge 1 commit into
compio-rs:masterfrom
webc-fork:fix/executor-tick-reentrant

Conversation

@x-at-01

@x-at-01 x-at-01 commented Sep 24, 2026 •

Copy link
Copy Markdown

Problem

Executor::tick walks the hot queue with an iterator that lazily prefetches the successor via TaskQueue::next_hot, while the loop body has already moved the previously returned task off the hot list (make_cold also clears its next pointer).

Whenever a task's poll re-enters tick on the same executor — nested driving, which happens in practice with a synchronous harvest inside a thread-per-core runtime (calling the low-level run/poll pair from within a task's poll, in the spirit of a blocking-wait bridge) — the inner tick transitions the prefetched task from hot to cold (or completes and removes it). The outer iteration then:

  • panics on debug builds: debug_assert!(item.is_hot) in next_hot, or
  • silently truncates the walk on release builds (item.next is already None or task removed), leaving queued tasks unpolled until the next tick.

Fix

Handle stale/non-hot cursors cleanly during iteration:

  1. next_hot: when the cursor's task is no longer hot or has been removed from the map, fall back to the current hot head (inner.hot.head).
  2. Iter::next: verify that the cursor is currently hot before yielding. If non-hot/removed (e.g. processed by a nested tick, completed, or cancelled), advance directly from hot_head() in a loop so non-hot tasks are skipped without wasting interval slots or causing double polls.
  3. tick's loop body: retain defense-in-depth check queue.is_hot(id) to prevent double polling under re-entrancy.

@github-actions github-actions Bot added bug Something isn't working package: executor Related to compio-executor labels Sep 24, 2026
The hot queue was walked with an iterator that lazily prefetches the
successor through `next_hot` while `tick`'s loop body had already moved
the previously returned task off the hot list (`make_cold` also clears
its `next`). Whenever a task's poll re-entered `tick` on the same
executor — nested driving, e.g. a synchronous harvest inside a
thread-per-core runtime — the inner tick could transition the prefetched
task from hot to cold. The outer iteration then either tripped
`debug_assert!(item.is_hot)` on debug builds or silently truncated the
walk on release builds, leaving queued tasks unpolled until the next
tick.

Handle the stale cursor explicitly: when the cursor's task is no longer
hot, restart from the current hot head; and in the loop body, skip tasks
that a nested tick has already taken off the hot list instead of
asserting or polling them a second time. Re-entry is now crash-free and
lossless, and the iteration still visits every queued task exactly once
per tick when no nesting occurs.
@x-at-01
x-at-01 force-pushed the fix/executor-tick-reentrant branch from 416f4df to 30b331e Compare September 24, 2026 04:27

@Berrysoft Berrysoft left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please separate your removal of unneeded dependencies to a dedicated PR.

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

Labels

bug Something isn't working package: executor Related to compio-executor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants