Skip to content

Commit bc3a66e

Browse files
wan9chiclaude
andcommitted
refactor(server): drop 'static bound on handler
The driver future owns the handler via `Rc<H>` and only needs `H` to outlive the future. Parameterize `ServerHandle` and `serve` over a lifetime so callers with non-`'static` handlers are supported. For `'static` handlers (the expected common case), nothing changes at the call site — inference picks `'static`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3ce03bf commit bc3a66e

2 files changed

Lines changed: 11 additions & 11 deletions

File tree

crates/vite_task_server/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ pub trait Handler {
3131
///
3232
/// Dropping `driver` before it resolves tears everything down immediately —
3333
/// listener closed, per-client tasks cancelled, handler discarded.
34-
pub struct ServerHandle<H> {
35-
pub driver: LocalBoxFuture<'static, H>,
34+
pub struct ServerHandle<'h, H> {
35+
pub driver: LocalBoxFuture<'h, H>,
3636
pub stop_accepting: StopAccepting,
3737
}
3838

@@ -65,7 +65,7 @@ impl StopAccepting {
6565
/// The driver future panics if, after drain, the handler still has other
6666
/// `Rc` references — which would indicate a logic bug in the server
6767
/// (per-client futures should all have dropped their clones by then).
68-
pub fn serve<H: Handler + 'static>(handler: H) -> io::Result<(OsString, ServerHandle<H>)> {
68+
pub fn serve<'h, H: Handler + 'h>(handler: H) -> io::Result<(OsString, ServerHandle<'h, H>)> {
6969
let handler_rc = Rc::new(handler);
7070
let stop_token = CancellationToken::new();
7171
let (name, bound) = bind_listener()?;

docs/runner-task-ipc/server-design.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The IPC server runs per spawn execution **only when fspy is enabled**, letting t
99
1. **Server doesn't take a cancellation token.** The caller signals "stop accepting" via `StopAccepting::signal()`. The server has no awareness of external cancellation.
1010
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.
1111
3. **`Rc<H>` + `CancellationToken` internally** — both hidden from the public API.
12-
4. **Driver is `!Send`, `'static`** — matches the existing execute pipeline shape (`LocalBoxFuture<'static, ...>` throughout).
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.
1313

1414
## Server API
1515

@@ -21,12 +21,12 @@ pub trait Handler {
2121
fn get_env(&self, name: &str, tracked: bool) -> Option<Arc<OsStr>>;
2222
}
2323

24-
pub fn serve<H: Handler + 'static>(
24+
pub fn serve<'h, H: Handler + 'h>(
2525
handler: H,
26-
) -> io::Result<(OsString, ServerHandle<H>)>;
26+
) -> io::Result<(OsString, ServerHandle<'h, H>)>;
2727

28-
pub struct ServerHandle<H> {
29-
pub driver: LocalBoxFuture<'static, H>,
28+
pub struct ServerHandle<'h, H> {
29+
pub driver: LocalBoxFuture<'h, H>,
3030
pub stop_accepting: StopAccepting,
3131
}
3232

@@ -115,11 +115,11 @@ if !fast_fail_token.is_cancelled() && !interrupt_token.is_cancelled() {
115115

116116
## Design-decision log
117117

118-
### Why `H: 'static`?
118+
### Why no `'static` bound on `H`?
119119

120-
Storing the driver in `FspyState` without a lifetime parameter requires `LocalBoxFuture<'static, H>`, which requires `H: 'static`. The concrete `IpcRecorder` is naturally `'static` — it owns all its fields (RefCells, cloned env config).
120+
The driver future _owns_ the handler (via `Rc<H>` internally). It doesn't need to outlive `H` — it just needs `H` to outlive the future. So the signature is `serve<'h, H: Handler + 'h>` and the returned `ServerHandle<'h, H>` carries the lifetime. If the caller's `H` is `'static`, the driver is `'static`; if `H` borrows, the driver respects that.
121121

122-
An earlier direction ("don't require `'static` on Handler") was specifically to avoid `spawn_local`, which requires `'static` tasks. Requiring `'static` on the trait type is acceptable when the concrete handler naturally has no borrow.
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).
123123

124124
### Why `Rc<H>` internally instead of `&H`?
125125

0 commit comments

Comments
 (0)