|
| 1 | +# Cross-isolate message API — design & feasibility (spec §20.1-B / §20.2-3) |
| 2 | + |
| 3 | +Status: **not implemented — blocked on foundational runtime work** (the spec gates |
| 4 | +it on "the isolate model maturing first"). This note records the investigation and |
| 5 | +the smallest sound implementation plan so the work can be picked up directly. |
| 6 | + |
| 7 | +## Goal (from the spec) |
| 8 | + |
| 9 | +Typed send/receive channels **between isolates**: payloads are owned/Copy data or |
| 10 | +values moved with `take`; **managed handles never cross**; single ownership is |
| 11 | +enforced statically (not by runtime convention); `take`-based move is the |
| 12 | +no-shared-alias transfer path (zero-copy when representation permits). |
| 13 | + |
| 14 | +## Why it isn't a small change today |
| 15 | + |
| 16 | +The runtime is **strictly single-isolate**. Concurrency is cooperative tasks on |
| 17 | +one thread sharing **one heap**: |
| 18 | + |
| 19 | +- `crates/runtime/src/channel.rs` is an explicit "single-isolate MPSC" using |
| 20 | + `Rc<RefCell<ChannelState<T>>>` (no `Send`/`Sync`, no `Arc<Mutex>`). |
| 21 | +- The reg-VM scheduler (`reg_vm/mod.rs`: `tasks`, `ready_queue`, `Wait`, |
| 22 | + `satisfy_waiters`) drives many tasks, but they share the same `Rc` pool — |
| 23 | + `task_group` children are isolate-**local**. |
| 24 | +- There is no construct that creates a second execution context with its own heap; |
| 25 | + a "second isolate" would still alias the same managed `Rc`s. |
| 26 | + |
| 27 | +So "managed handles never cross isolates" has no boundary to be enforced *at* |
| 28 | +today — there is only one heap. |
| 29 | + |
| 30 | +## Smallest sound slice (recommended, ~1–2 weeks; holds VM↔compiled parity) |
| 31 | + |
| 32 | +Reuse the cooperative scheduler; make the isolate boundary a **static + runtime |
| 33 | +value-transfer contract** rather than a separate OS thread/heap. Soundness comes |
| 34 | +from restricting payloads so a value transfer is a genuine deep copy with no |
| 35 | +shared `Rc`. |
| 36 | + |
| 37 | +1. **Type classification — `is_cross_isolate_safe(T)`** (`checks/local.rs`). |
| 38 | + Allowed: Copy scalars, and owned `struct`/`sum` values whose fields are all |
| 39 | + cross-isolate-safe. Denied: `List`/`Map`/`Set`, closures, any managed handle, |
| 40 | + resources, views, generic `T`. This is the reviewable essence: no `Rc` can |
| 41 | + cross, so a clone/move is alias-free. |
| 42 | + |
| 43 | +2. **Surface** — a distinct mailbox API (e.g. `Mailbox<T>` + `Mailbox.pair()`, |
| 44 | + `Outbox.send(value: take T)`, `Inbox.recv()`), feature-gated, with |
| 45 | + `send` requiring `take` of a cross-isolate-safe `T`. Kept separate from the |
| 46 | + in-isolate `Channel<T>` so existing channel semantics are unchanged. |
| 47 | + |
| 48 | +3. **Static enforcement** — at `Outbox.send`, infer the payload type and reject |
| 49 | + non-cross-isolate-safe `T` with a stable diagnostic (mirrors `take` |
| 50 | + validation). This is the primary defense and is fully testable without |
| 51 | + multi-threading. |
| 52 | + |
| 53 | +4. **Execution** — run the two endpoints as cooperative tasks; transfer reuses the |
| 54 | + existing channel value-move (VM clones `VmValue`, Rust moves `T`). Because the |
| 55 | + payload has no `Rc`, this is alias-free on the shared heap — sound for the |
| 56 | + "single ownership / no shared managed handle" guarantee. Add a defensive |
| 57 | + runtime scan rejecting any `Rc`-bearing value (safety net for edge cases). |
| 58 | + |
| 59 | +5. **Parity + tests** — `tests/vm_eval_parity/async_concurrency.rs`: Copy payloads |
| 60 | + cross; managed payloads rejected at check time; ordering preserved; mixing with |
| 61 | + in-isolate channels. |
| 62 | + |
| 63 | +## Larger, deferred option |
| 64 | + |
| 65 | +True multi-thread / multi-heap isolates (`Arc<Mutex>`, `Send + Sync`, a |
| 66 | +thread-multiplexing scheduler) is a multi-quarter refactor that intentionally |
| 67 | +breaks the single-thread-simplicity design. Out of scope; the slice above delivers |
| 68 | +the spec's *static* guarantee without it. |
| 69 | + |
| 70 | +## Decision |
| 71 | + |
| 72 | +Recorded, not built. The static contract (step 1+3) is the high-value, sound, |
| 73 | +bounded core; steps 2/4/5 add the executable surface. Pick up when the mailbox |
| 74 | +surface is prioritized; until then the single-isolate model stands and managed |
| 75 | +handles simply cannot be sent anywhere. |
0 commit comments