Skip to content

runtime: convert CallInterceptor to async for chain-subscriber back-pressure#27

Merged
colinrozzi merged 1 commit into
mainfrom
async-call-interceptor
Jun 7, 2026
Merged

runtime: convert CallInterceptor to async for chain-subscriber back-pressure#27
colinrozzi merged 1 commit into
mainfrom
async-call-interceptor

Conversation

@colinrozzi

Copy link
Copy Markdown
Owner

Summary

Theater's chain-subscription redesign (theater #105) makes the chain the source of truth for event delivery. To get real producer back-pressure — a slow subscriber gating the wasm-side host-call rate — StateChain::add_typed_event needs to .await each subscriber's Sender::send(...) instead of try_send'ing and dropping on overflow.

That requires the calling context (CallInterceptor's methods, which theater hooks add_typed_event into) to be async. This PR converts the trait.

Trait change (breaking → 0.7.0)

CallInterceptor methods are now async fn, declared via #[async_trait]:

#[async_trait]
pub trait CallInterceptor: Send + Sync {
    async fn before_import(&self, interface: &str, function: &str, input: &Value) -> Option<Value>;
    async fn after_import(&self, interface: &str, function: &str, input: &Value, output: &Value);
    async fn before_export(&self, function: &str, input: &Value) -> Option<Value>;
    async fn after_export(&self, function: &str, input: &Value, output: &Value);
}

Bridge updates

  • Async host bridges (func_async, func_async_result, AsyncInstance::call_with_value_async) .await the interceptor directly — this is the path theater uses and the one that gets the back-pressure win.
  • Sync host bridges (func_typed, func_typed_result) drive the future via a new block_on_interceptor helper using tokio::task::block_in_place + Handle::current().block_on(...). When an interceptor is installed on the sync paths, a tokio multi-thread runtime is required — documented on the trait.

New deps

  • async-trait = "0.1" (regular dep)
  • tokio with rt-multi-thread (was dev-only, now also a regular dep — needed for the sync-bridge block-on path)

Regression test

tests/interceptor_backpressure.rs — an interceptor whose after_import .awaits on a bounded mpsc::Sender<Value> of capacity 1. A wasm export calls a host import twice. After 200ms the test asserts:

  • Both after_import futures started
  • Only the first one finished (second is parked on send)
  • The wasm task hasn't returned

Then the test drains the channel and verifies the wasm task completes with the expected result. This is the back-pressure property theater's PR #105 depends on.

Migration for downstream impls

Both known impls (theater's RecordingInterceptor and ReplayRecordingInterceptor) need: add #[async_trait] attr, mark methods async, .await any internal calls that became async. Theater's patch is already prototyped and queued to land the moment 0.7.0 is on crates.io.

Release expectations

Trait change is breaking → bump to 0.7.0 on merge (via nix run .#release -- minor).

Test plan

  • cargo test --workspace (all green, including new back-pressure test)
  • cargo test -p packr-abi --features derive
  • cargo clippy --workspace -- -D warnings
  • cargo fmt --all -- --check
  • After release: theater bumps its packr dep to 0.7.0 and re-runs the wedge-repro burst.

…ressure

Theater's chain-subscription redesign (theater PR #105) needs the
chain-emission path to apply real back-pressure on slow subscribers —
StateChain::add_typed_event must .await the per-subscriber Sender::send
instead of try_send. That requires the calling context (CallInterceptor's
methods) to be async.

Trait change:
- CallInterceptor methods are now async, declared via #[async_trait].
- This is a breaking change → packr should go out as 0.7.0.

Call-site updates:
- Async host bridges (func_async, func_async_result, AsyncInstance
  call_with_value_async) .await the interceptor directly.
- Sync host bridges (func_typed, func_typed_result) drive the future via
  a new block_on_interceptor helper: tokio::task::block_in_place +
  Handle::current().block_on(...). When an interceptor is installed on
  the sync paths, a tokio multi-thread runtime is required (documented
  on the trait).

New deps:
- async-trait 0.1
- tokio 1 with rt-multi-thread (promoted from dev-dep)

Regression test:
- tests/interceptor_backpressure.rs spins up an interceptor whose
  after_import .awaits on an mpsc cap=1. A wasm export calls a host
  import twice; verify the second after_import (and the second wasm-side
  return) block until the channel drains.
@colinrozzi colinrozzi merged commit 86048e3 into main Jun 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant