You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(server): drop Rc; borrow handler from async state
The driver's async fn owns `handler: H` as a local. Per-client
futures borrow `&handler` from the same async state — Rust's
async-fn state machine makes the self-borrow sound (state is
pinned, never moves). When drain completes, the outer async
returns `handler` by move.
Removes the internal `Rc<H>` + `Rc::try_unwrap` path (and the
related panic doc).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/runner-task-ipc/server-design.md
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ The IPC server runs per spawn execution **only when fspy is enabled**, letting t
8
8
9
9
1.**Server doesn't take a cancellation token.** The caller signals "stop accepting" via `StopAccepting::signal()`. The server has no awareness of external cancellation.
10
10
2.**Handler is moved in, returned out.** The caller doesn't keep a reference. The driver owns the handler; on drain completion it returns it by value. No self-reference, no `&H` lifetime.
11
-
3.**`Rc<H>` + `CancellationToken`internally** — both hidden from the public API.
11
+
3.**`CancellationToken`is internal** — hidden from the public API (exposed only via `StopAccepting`).
12
12
4.**Driver is `!Send`**, lifetime bounded by `H`'s lifetime — if `H: 'static`, the driver is `'static`; if `H` borrows, the driver respects that.
13
13
14
14
## Server API
@@ -43,7 +43,7 @@ The driver future, when polled:
43
43
1.**Accept phase** — accepts new clients and pumps per-client futures (`FuturesUnordered`) until `StopAccepting::signal()` fires.
3.**Drain phase** — waits for in-flight per-client futures to complete naturally (each ends on client EOF).
46
-
4.**Returns `H`** — via `Rc::try_unwrap` since all per-client clones have dropped.
46
+
4.**Returns `H`** — the owned handler that was moved in at `serve()`.
47
47
48
48
Dropping the driver before it resolves tears everything down immediately. Handler is dropped without being returned.
49
49
@@ -81,7 +81,7 @@ struct FspyState {
81
81
**Not stored:**
82
82
83
83
-`name` — consumed once to build envs, dropped immediately.
84
-
-`handler` — lives inside `server.driver` via `Rc`; recovered by value when driver resolves.
84
+
-`handler` — lives inside `server.driver`'s async state; recovered by value when the driver resolves.
85
85
86
86
### Driving the server during `pipe_stdio` / `child.wait`
87
87
@@ -121,11 +121,11 @@ The driver future _owns_ the handler (via `Rc<H>` internally). It doesn't need t
121
121
122
122
If the caller wants to store `ServerHandle` in a struct without a lifetime parameter, they can use a `'static` handler (naturally satisfied by handlers that own all their state via `RefCell<...>` + cloned data).
123
123
124
-
### Why `Rc<H>` internally instead of `&H`?
124
+
### Handler is owned by the driver, not shared via `Rc`
125
125
126
-
`&H`→ per-client futures borrow from a single `H`. Storing both the handler and the driver in the same `FspyState` creates a self-reference.
126
+
The driver's async function owns `handler: H`as a local. Per-client futures borrow `&handler`from that same async state; Rust's async-fn state machine makes this self-borrow sound (the state is pinned and never moves). All per-client futures live inside `FuturesUnordered` which is also part of the same state — borrow scopes are contained.
127
127
128
-
`Rc<H>` → per-client futures share ownership. The driver captures `Rc<H>`, clones into each per-client future. When drain completes, `Rc::try_unwrap` succeeds because all per-client clones have dropped, and the driver returns the owned `H`.
128
+
When drain completes and all per-client futures have been dropped, the outer async returns `handler` by move. No `Rc`, no `try_unwrap`, no panic possible.
0 commit comments