Stream: git-wasmtime

Topic: wasmtime / PR #14620 component: protect fiber handoffs wi...


view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 03:00):

Byte-Naut opened PR #14620 from Byte-Naut:issue-14241-3 to bytecodealliance:main:

Fixes #14241.

This supersedes #14418. Based on @alexcrichton's feedback there, this takes the smaller ownership-repair path: GuestThreadState, WaitMode, and WorkItem keep their existing shapes — WaitMode::Fiber and WorkItem::ResumeFiber still carry the fiber directly, and waiting or scheduled threads continue to appear Running to the thread intrinsics — and the fix concentrates on guarding the intervals where a fiber is outside the store's scheduler state.

Motivation

An unfinished StoreFiber that is dropped directly triggers an assertion in StoreFiber::drop. Several paths in the scheduler moved a live fiber out of one store-owned location and into another with a fallible step in between. When that step fails — a table lookup error, bail_bug!, or a panic — the fiber is dropped mid-transfer, turning an ordinary error into a host panic or process abort.

Changes

Commit 1 — protect fiber handoffs without adding thread states

resume_fiber is converted from async fn to a plain function returning impl Future. A ResumingFiber guard is constructed before returning the future, so a caller that drops the future before its first poll still disposes of the fiber through the store. The fiber enters the guard after being taken from a WaitMode::Fiber entry, a WorkItem::ResumeFiber item, or a GuestThreadState::Ready slot — the same locations as before — and the guard holds it until it reaches its next store-owned location.

For every SuspendReason arm that hands the fiber to a destination, the destination slot is checked for an existing fiber before the fiber is moved out of the guard. If the check fails, the fiber stays in the guard and is disposed on return. GuestThreadState::check_no_fiber centralises that check for Suspended and Ready. set_thread_running and mark_ready are restructured so all fallible lookups complete before any ownership transfer begins. The worker-fiber take is reordered so worker_item is asserted empty and the take happens in one non-fallible step.

Commit 2 — guard pending work and preserve queues on promotion panic

handle_work_item is converted from async fn to a plain function returning impl Future<Output = Result<()>>. The ResumeFiber arm is handled in the non-async prefix so the guard is established before the future is returned, matching the treatment in resume_fiber. promote is restructured to scan the queues in place rather than draining and rebuilding them; a predicate panic no longer drops the remaining items.

Commit 3 — clarify the oldest-first promotion scan

One-line comment clarification; no behaviour change.

Design notes

The fix does not add scheduler states, new fields to ConcurrentState, or per-suspension allocations. The existing StoreFiber::drop assertion remains the final defence. Two properties worth checking in review: check_no_fiber is the only place a handoff is gated, and every SuspendReason arm calls it before writing to the destination slot; and the ResumingFiber guard together with the FiberFuture inside fiber::resolve_or_release cover the full resume interval including pre-first-poll cancellation.

Testing

cargo test -p wasmtime --lib --features component-model-async --offline
cargo test -p wasmtime-cli --test wast --no-default-features --features component-model-async,cranelift,winch,wat,wast --offline -- component-model/async
cargo test -p wasmtime-cli --test all --features component-model-async --offline -- component_model
cargo test -p wasmtime --lib --features component-model-async --offline --config profile.test.package.wasmtime.debug-assertions=false --config profile.dev.package.wasmtime.debug-assertions=false -- fiber_ownership_tests
cargo clippy -p wasmtime --all-targets --features component-model-async,task-group-hook --offline -- -Dwarnings
cargo check -p wasmtime --no-default-features --features runtime,component-model-async --offline

Results: 221 unit tests (including 27 fiber ownership tests), 128 async WAST trials on Cranelift and Winch, 258 component model integration tests, fmt and clippy clean, non-async and no_std builds pass. The fiber ownership tests also pass with debug assertions disabled.

Five paths are covered by fault-injection tests in concurrent/fiber_ownership_tests.rs, verified against unpatched builds that exit SIGABRT:

thread-wait-resume.wast (carried forward from #14418) checks that a thread blocked in waitable-set.wait still produces cannot resume thread which is not suspended for thread.resume-later and thread.suspend-then-resume.

Reviewing

Three commits, each self-contained:

  1. Protect fiber handoffs without adding thread states. The core change: ResumingFiber, check_no_fiber, and the restructured handoff paths in resume_fiber, mark_ready, and set_thread_running. Start here.
  2. Guard pending work and preserve queues on promotion panic. handle_work_item pre-first-poll guard and the in-place promotion scan. "Hide whitespace" helps on the handle_work_item diff.
  3. Clarify the oldest-first promotion scan. One comment line; safe to skim.

The representation is unchanged from main: GuestThreadState still has Suspended and Ready carrying the fiber directly, WaitMode::Fiber and WorkItem::ResumeFiber still carry the fiber. Developed with assistance from Claude (Anthropic). Per the Bytecode Alliance AI Tool Use Policy, I'm the author and accountable for this change.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 03:00):

Byte-Naut requested wasmtime-core-reviewers for a review on PR #14620.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 03:00):

Byte-Naut requested dicej for a review on PR #14620.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 07:03):

github-actions[bot] added the label wasmtime:api on PR #14620.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 14:35):

:memo: dicej submitted PR review:

Thanks, @Byte-Naut; looks good overall; just a few inline suggestions.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 14:35):

:speech_balloon: dicej created PR review comment:

While we're here, might as well turn this into a bail_bug! instead of panicking.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 14:35):

:speech_balloon: dicej created PR review comment:

Let's make this a bail_bug! while we're here.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 14:35):

:speech_balloon: dicej created PR review comment:

Let's bail_bug! instead of panic!ing here. Also, please update the error message to be something like "entry unexpectedly already exists for {thread}".

view this post on Zulip Wasmtime GitHub notifications bot (Oct 09 2026 at 14:35):

:speech_balloon: dicej created PR review comment:

Nit: looks like these two if statements could be combined into a single match statement.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 10 2026 at 06:27):

Byte-Naut updated PR #14620.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 10 2026 at 06:28):

Byte-Naut edited PR #14620:

Fixes #14241.

This supersedes #14418. Based on @alexcrichton's feedback there, this takes the smaller ownership-repair path: GuestThreadState, WaitMode, and WorkItem keep their existing shapes — WaitMode::Fiber and WorkItem::ResumeFiber still carry the fiber directly, and waiting or scheduled threads continue to appear Running to the thread intrinsics — and the fix concentrates on guarding the intervals where a fiber is outside the store's scheduler state.

Motivation

An unfinished StoreFiber that is dropped directly triggers an assertion in StoreFiber::drop. Several paths in the scheduler moved a live fiber out of one store-owned location and into another with a fallible step in between. When that step fails — a table lookup error, bail_bug!, or a panic — the fiber is dropped mid-transfer, turning an ordinary error into a host panic or process abort.

Changes

This rebases on top of #14624, #14635, #14638, and #14610, which landed between the first push and now.

Commit 1 — protect fiber handoffs without adding thread states

resume_fiber is converted from async fn to a plain function returning impl Future. A ResumingFiber guard is constructed before returning the future, so a caller that drops the future before its first poll still disposes of the fiber through the store. The fiber enters the guard after being taken from a WaitMode::Fiber entry, a WorkItem::ResumeFiber item, or a GuestThreadState::Ready slot — the same locations as before — and the guard holds it until it reaches its next store-owned location.

For every SuspendReason arm that hands the fiber to a destination, the destination slot is checked for an existing fiber before the fiber is moved out of the guard. If the check fails, the fiber stays in the guard and is disposed on return. GuestThreadState::check_no_fiber centralises that check for Suspended and Ready. set_thread_running and mark_ready are restructured so all fallible lookups complete before any ownership transfer begins. The worker-fiber take is reordered so worker_item is asserted empty and the take happens in one non-fallible step.

Commit 2 — guard pending work and preserve queues on promotion panic

handle_work_item is converted from async fn to a plain function returning impl Future<Output = Result<()>>. The ResumeFiber arm is handled in the non-async prefix so the guard is established before the future is returned, matching the treatment in resume_fiber. promote is restructured to scan the queues in place rather than draining and rebuilding them; a predicate panic no longer drops the remaining items.

Commit 3 — clarify the oldest-first promotion scan

One-line comment clarification; no behaviour change.

Design notes

The fix does not add scheduler states, new fields to ConcurrentState, or per-suspension allocations. The existing StoreFiber::drop assertion remains the final defence. Two properties worth checking in review: check_no_fiber is the only place a handoff is gated, and every SuspendReason arm calls it before writing to the destination slot; and the ResumingFiber guard together with the FiberFuture inside fiber::resolve_or_release cover the full resume interval including pre-first-poll cancellation.

Testing

cargo test -p wasmtime --lib --features component-model-async --offline
cargo test -p wasmtime-cli --test wast --no-default-features --features component-model-async,cranelift,winch,wat,wast --offline -- component-model/async
cargo test -p wasmtime-cli --test all --features component-model-async --offline -- component_model
cargo test -p wasmtime --lib --features component-model-async --offline --config profile.test.package.wasmtime.debug-assertions=false --config profile.dev.package.wasmtime.debug-assertions=false -- fiber_ownership_tests
cargo clippy -p wasmtime --all-targets --features component-model-async,task-group-hook --offline -- -Dwarnings
cargo check -p wasmtime --no-default-features --features runtime,component-model-async --offline

Results: 221 unit tests (including 27 fiber ownership tests), 128 async WAST trials on Cranelift and Winch, 258 component model integration tests, fmt and clippy clean, non-async and no_std builds pass. The fiber ownership tests also pass with debug assertions disabled.

Five paths are covered by fault-injection tests in concurrent/fiber_ownership_tests.rs, verified against unpatched builds that exit SIGABRT:

thread-wait-resume.wast (carried forward from #14418) checks that a thread blocked in waitable-set.wait still produces cannot resume thread which is not suspended for thread.resume-later and thread.suspend-then-resume.

Reviewing

Three commits, each self-contained:

  1. Protect fiber handoffs without adding thread states. The core change: ResumingFiber, check_no_fiber, and the restructured handoff paths in resume_fiber, mark_ready, and set_thread_running. Start here.
  2. Guard pending work and preserve queues on promotion panic. handle_work_item pre-first-poll guard and the in-place promotion scan. "Hide whitespace" helps on the handle_work_item diff.
  3. Clarify the oldest-first promotion scan. One comment line; safe to skim.

The representation is unchanged from main: GuestThreadState still has Suspended and Ready carrying the fiber directly, WaitMode::Fiber and WorkItem::ResumeFiber still carry the fiber. Developed with assistance from Claude (Anthropic). Per the Bytecode Alliance AI Tool Use Policy, I'm the author and accountable for this change.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 10 2026 at 06:31):

Byte-Naut edited PR #14620:

Fixes #14241.

This supersedes #14418. Based on @alexcrichton's feedback there, this takes the smaller ownership-repair path: GuestThreadState, WaitMode, and WorkItem keep their existing shapes — WaitMode::Fiber and WorkItem::ResumeFiber still carry the fiber directly, and waiting or scheduled threads continue to appear Running to the thread intrinsics — and the fix concentrates on guarding the intervals where a fiber is outside the store's scheduler state.

Motivation

An unfinished StoreFiber that is dropped directly triggers an assertion in StoreFiber::drop. Several paths in the scheduler moved a live fiber out of one store-owned location and into another with a fallible step in between. When that step fails — a table lookup error, bail_bug!, or a panic — the fiber is dropped mid-transfer, turning an ordinary error into a host panic or process abort.

Changes

This rebases on top of #14624, #14635, #14638, and #14610, which landed between the first push and now.

Commit 1 — protect fiber handoffs without adding thread states

resume_fiber is converted from async fn to a plain function returning impl Future. A ResumingFiber guard is constructed before returning the future, so a caller that drops the future before its first poll still disposes of the fiber through the store. The fiber enters the guard after being taken from a WaitMode::Fiber entry, a WorkItem::ResumeFiber item, or a GuestThreadState::Ready slot — the same locations as before — and the guard holds it until it reaches its next store-owned location.

For every SuspendReason arm that hands the fiber to a destination, the destination slot is checked for an existing fiber before the fiber is moved out of the guard. If the check fails, the fiber stays in the guard and is disposed on return. GuestThreadState::check_no_fiber centralises that check for Suspended and Ready. set_thread_running and mark_ready are restructured so all fallible lookups complete before any ownership transfer begins. The worker-fiber take is reordered so worker_item is asserted empty and the take happens in one non-fallible step.

Commit 2 — guard pending work and preserve queues on promotion panic

handle_work_item is converted from async fn to a plain function returning impl Future<Output = Result<()>>. The ResumeFiber arm is handled in the non-async prefix so the guard is established before the future is returned, matching the treatment in resume_fiber. promote is restructured to scan the queues in place rather than draining and rebuilding them; a predicate panic no longer drops the remaining items.

Commit 3 — clarify the oldest-first promotion scan

One-line comment clarification; no behaviour change.

Design notes

The fix does not add scheduler states, new fields to ConcurrentState, or per-suspension allocations. The existing StoreFiber::drop assertion remains the final defence. Two properties worth checking in review: every SuspendReason arm checks its destination before taking the fiber out of the guard (check_no_fiber for thread slots, the entry API for wait sets, and next_switch_item for subtask yields); and the ResumingFiber guard together with the FiberFuture inside fiber::resolve_or_release cover the full resume interval including pre-first-poll cancellation.

Testing

cargo test -p wasmtime --lib --features component-model-async --offline
cargo test -p wasmtime-cli --test wast --no-default-features --features component-model-async,cranelift,winch,wat,wast --offline -- component-model/async
cargo test -p wasmtime-cli --test all --features component-model-async --offline -- component_model
cargo test -p wasmtime --lib --features component-model-async --offline --config profile.test.package.wasmtime.debug-assertions=false --config profile.dev.package.wasmtime.debug-assertions=false -- fiber_ownership_tests
cargo clippy -p wasmtime --all-targets --features component-model-async,task-group-hook --offline -- -Dwarnings
cargo check -p wasmtime --no-default-features --features runtime,component-model-async --offline

Results: 221 unit tests (including 27 fiber ownership tests), 128 async WAST trials on Cranelift and Winch, 258 component model integration tests, fmt and clippy clean, non-async and no_std builds pass. The fiber ownership tests also pass with debug assertions disabled.

Five paths are covered by fault-injection tests in concurrent/fiber_ownership_tests.rs, verified against unpatched builds that exit SIGABRT:

thread-wait-resume.wast (carried forward from #14418) checks that a thread blocked in waitable-set.wait still produces cannot resume thread which is not suspended for thread.resume-later and thread.suspend-then-resume.

Reviewing

Three commits, each self-contained:

  1. Protect fiber handoffs without adding thread states. The core change: ResumingFiber, check_no_fiber, and the restructured handoff paths in resume_fiber, mark_ready, and set_thread_running. Start here.
  2. Guard pending work and preserve queues on promotion panic. handle_work_item pre-first-poll guard and the in-place promotion scan. "Hide whitespace" helps on the handle_work_item diff.
  3. Clarify the oldest-first promotion scan. One comment line; safe to skim.

The representation is unchanged from main: GuestThreadState still has Suspended and Ready carrying the fiber directly, WaitMode::Fiber and WorkItem::ResumeFiber still carry the fiber. Developed with assistance from Claude (Anthropic). Per the Bytecode Alliance AI Tool Use Policy, I'm the author and accountable for this change.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 10 2026 at 06:32):

Byte-Naut commented on PR #14620:

@dicej Thanks for the review! I've addressed all four suggestions: replaced the assert!s in run_on_worker and wake_waiter with bail_bug!, updated the occupied-entry message to name the thread, and combined the two ifs into one match. The branch also rebases on the changes that landed since the first push.


Last updated: Oct 11 2026 at 04:10 UTC