feat(executor): cooperative single-thread lanes + generic config-driven lane routing#2
Merged
Merged
Conversation
…lock_on `ThreadExecutor::execute_async` drove each async task with `futures::executor::block_on` on the lane's single worker thread, so any `.await` inside a `#[frb(thread = ...)]` handler parked that worker until the future resolved — every other call routed to the same lane queued behind it. This is a native-only regression versus `SimpleExecutor` (which spawns onto a multi-thread runtime) and versus the wasm lane (which `spawn_local`s onto the JS event loop, yielding on `.await`). Drive the lane cooperatively instead: - Add `SingleThreadAsyncLane` (native `thread_pool/io.rs`): one dedicated thread running a `current_thread` runtime + `LocalSet`, fed jobs over a channel and running each inside the LocalSet context. - Native `spawn_local_compat` now `tokio::task::spawn_local`s the task future instead of `block_on`-ing it, so `.await` yields the lane to other queued jobs while preserving single-thread FIFO ownership — matching the wasm lane. WASM is unchanged. Adds `single_worker_lane_pinning_tests`, a regression test that deadlocks under `block_on` and passes under cooperative scheduling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…omain lanes)
`#[frb(thread = ...)]` routing baked modality's domain into FRB: the `Thread`
enum hardcoded `Loro`/`Export` variants, the attribute parser allowlisted those
names, and `validate_frb_threads` hardcoded `session_id`/`drive_id`/... param
names + a "sync or session-handle ⇒ must annotate" rule. FRB should know none of
that — lane identity and policy belong to the embedder.
Make lanes opaque and the policy config-driven:
- `Thread` is now `{ Main, Worker(&'static str) }`. The lane key is an opaque
string the embedder's `ThreadRouter` resolves; FRB assigns it no meaning.
- The attribute parser accepts any ident (no allowlist); codegen emits
`Thread::Main` for `Main`/unset and `Thread::Worker("<ident>")` otherwise.
- New optional `lane_routing` config (`flutter_rust_bridge.yaml`): `lanes` (valid
keys) + `require_annotation_when { sync, param_named }`. `validate_frb_threads`
enforces it against config — erroring on an unknown lane or a function that
matches the predicate but isn't annotated. Absent config ⇒ no-op (any key
accepted, nothing required), so FRB ships with zero domain knowledge.
Runtime/doc comments de-Loro'd. WASM unchanged. Config golden fixtures updated
for the new `lane_routing` field (the unrelated `oxidized` MIR-golden drift that
predates this branch is left untouched).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related changes to the
#[frb(thread = ...)]executor-routing feature.1. Cooperative single-thread lanes (
fix)ThreadExecutor::execute_asyncdrove each async task withfutures::executor::block_onon the lane's single worker, so any.awaitinside a#[frb(thread = ...)]handler parked that worker until the future resolved — every other call on the same lane queued behind it. This is a native-only regression vsSimpleExecutor(spawns onto a multi-thread runtime) and the wasm lane (spawn_locals, yielding).SingleThreadAsyncLane(nativethread_pool/io.rs): one dedicated thread running acurrent_threadruntime +LocalSet, jobs run inside the LocalSet.spawn_local_compatnowtokio::task::spawn_locals the task future instead ofblock_on-ing it, so.awaityields the lane while keeping single-thread FIFO ownership — matching wasm.Regression test
single_worker_lane_pinning_tests: two async tasks on one single-worker lane with a cross-task dependency — deadlocks underblock_on, passes under cooperative scheduling.2. Generic, config-driven lane routing (
feat)The feature baked a consumer's domain into FRB:
ThreadhardcodedLoro/Export, the attribute parser allowlisted those names, andvalidate_frb_threadshardcodedsession_id/drive_id/… param names + a "sync or session-handle ⇒ must annotate" rule. FRB should know none of it.Threadis now{ Main, Worker(&'static str) }— the lane key is opaque; the embedder'sThreadRouterresolves it.Thread::Main/Thread::Worker("<ident>").lane_routingconfig (flutter_rust_bridge.yaml):lanes(valid keys) +require_annotation_when { sync, param_named }.validate_frb_threadsenforces it against config; absent config ⇒ no-op, so FRB ships with zero domain knowledge.Tests
frb_rust37/37;frb_codegenattribute + config-golden tests green. WASM unchanged. (Pre-existingoxidizedMIR-golden drift that predates this branch is left untouched — unrelated to this change.)🤖 Generated with Claude Code