feat(serve): land the spawn-and-return plumbing + exit/status mechanics (w2-spawn-return)
- src/serve.rs (new): the ADR-006 decision-4 mechanics in code — spawn_serving_path (the one choke point; nothing awaited inline) and report_exit_status_then_close (exit-status before close via the cloned Session::handle()), #[doc(hidden)] contract-test re-exports; a unit row pins spawn-returns-while-pending. - src/handler.rs: the four session-row seam stubs became spawned serving paths (failure-then-close unchanged; stubs are plain fns — an async stub holding &Session across the spawn is non-Send); open- level declines and the tunnel seam stub stay inline by class, per the new module-doc section. - tests/spawn_return.rs (new): two end-to-end wire rows — the client observes exit-status(7) strictly before close and after the handler returned; the handler-returns-fast row routes follow-up traffic while the serving path is still gated (POC-3 deadlock inversion). - tasks/implementation/w2-spawn-return.md — completed with notes. Verification (all green): cargo test; per-feature dispatch matrices (--features git/session/tunnel/sftp, --all-features); cargo clippy --all-targets/-all-features with -D warnings; cargo fmt --check; cargo doc --no-deps (no warnings); taskgraph validate.
This commit is contained in:
1 parent
68d3723f6a
commit
2e5a7c9cee
5 files changed
+640
-36
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: w2-spawn-return
|
||||
name: Wave 2 — spawn-and-return plumbing + exit/status reporting mechanics
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [w2-dispatch-decline-core]
|
||||
scope: narrow
|
||||
risk: medium
|
||||
@@ -42,16 +42,16 @@ mechanically from the stubs this task leaves behind.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Every serving path (the dispatch stubs) runs in a
|
||||
- [x] Every serving path (the dispatch stubs) runs in a
|
||||
`tokio::spawn`; no handler hook awaits a serving path inline
|
||||
- [ ] Exit/status reporting from spawned tasks rides the cloned
|
||||
- [x] Exit/status reporting from spawned tasks rides the cloned
|
||||
`Session::handle()`; `exit-status` is sent before channel close
|
||||
- [ ] A harness test proves the mechanics end to end: the client
|
||||
- [x] A harness test proves the mechanics end to end: the client
|
||||
observes `exit-status(N)` from a spawned stub that completed
|
||||
after the handler returned
|
||||
- [ ] A handler-returns-fast assertion pins the structural constraint
|
||||
- [x] A handler-returns-fast assertion pins the structural constraint
|
||||
as a test
|
||||
- [ ] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
|
||||
- [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
|
||||
`cargo fmt --check` pass
|
||||
|
||||
## References
|
||||
@@ -67,8 +67,101 @@ mechanically from the stubs this task leaves behind.
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
- `src/serve.rs` (new): the spawn-and-return plumbing as *mechanics*,
|
||||
not convention — `spawn_serving_path(serving future)` (the one
|
||||
choke point for the ADR-006 decision-4 spawn; nothing it takes is
|
||||
ever awaited inline) and `report_exit_status_then_close(&Handle,
|
||||
channel, N)` (the cloned-handle report with the `mapping.md`
|
||||
`exit_status` row's ordering baked in: exit-status **before**
|
||||
close). Both re-exported `#[doc(hidden)]` from the crate root — the
|
||||
`SessionHandler` §Test-surface pattern — as the contract-test
|
||||
surface the mechanics rows compose over the real wire. A unit row
|
||||
(`spawn_returns_while_the_serving_path_is_still_pending` —
|
||||
latch-gated future, spawn call returns first) pins spawn-then-return
|
||||
directly. The derivation contract's *rules* (which exit code → which
|
||||
SSH exit shape) stay wave-3 `mapping.md` material; no rule logic
|
||||
landed here.
|
||||
- `src/handler.rs`: the four session-row seam stubs (`git_row_stub`,
|
||||
`pipe_row_stub`, `pty_row_stub`, `shell_row_stub`) are now spawned
|
||||
serving paths — `spawn_seam_stub` clones `session.handle()`,
|
||||
`seam_stub_decline(handle, channel)` runs the failure-then-close
|
||||
decline on the spawned task, and the hooks return without awaiting
|
||||
anything. A compile-level fact coded around: the row stubs are
|
||||
plain `fn`s (not `async`) — an `async fn` holding `&Session` across
|
||||
the spawn makes the handler future non-`Send` (russh requires `+
|
||||
Send` on handler futures; `Session` is not `Sync`), so the clone is
|
||||
taken synchronously and only the handle moves into the spawned
|
||||
future. Wire shapes are unchanged (all `w2-dispatch-decline-core`
|
||||
rows pass unmodified with the spawned decline); the tunnel seam stub
|
||||
and every open-level/capability decline stay inline by class — they
|
||||
answer the open request *before* any channel is owned, so nothing
|
||||
spawns yet (documented in the new handler module-doc section §The
|
||||
spawn-and-return structural constraint; wave 3's
|
||||
accept-only-after-fabric-open flow is where the tunnel row gains
|
||||
its spawn).
|
||||
- `tests/spawn_return.rs` (new): the end-to-end mechanics proof over
|
||||
the real wire — the russh server stack (duplex transport, harness
|
||||
client/harness transport posture) mounted over a minimal proof
|
||||
handler that is *not* the crate's `SessionHandler` (the auth and
|
||||
dispatch hooks have their own suites; the proof subject is the
|
||||
mechanics) and that serves execs by composing the exported
|
||||
plumbing: `spawn_serving_path` of a release-gated serving future
|
||||
reporting `exit-status(7)` + close through the cloned handle. Two
|
||||
rows, each with a `Contract stamp:`:
|
||||
- `handler_returns_fast_while_the_spawned_serving_path_is_pending` —
|
||||
the structural constraint as a wire test: after the exec hook has
|
||||
provably returned (a bounded wait on the handler-side timestamp;
|
||||
the client's exec returns when the request is on the wire, not
|
||||
when the hook ran) and with the serving path still gated, the
|
||||
client's *follow-up traffic* (a second session channel + second
|
||||
exec) is routed and dispatched normally — POC-3's follow-up-bytes
|
||||
deadlock shape inverted — and the still-gated no-report state is
|
||||
asserted before the release.
|
||||
- `spawned_stub_reports_exit_status_before_close_after_the_handler_returned`
|
||||
— the client observes `ExitStatus{7}` strictly before `Close`
|
||||
(message-by-message, no draining that could hide ordering), and
|
||||
the recorded timestamps prove the exit-status left *after* the
|
||||
handler had already returned (the literal criterion).
|
||||
- **The proof-handler design note:** the gate is `tokio::sync::watch`-
|
||||
based (no lost-wakeup race — a late subscriber sees the release,
|
||||
which a `Notify`-with-`notify_waiters` shape could miss against
|
||||
multiple spawned paths); the handler-side timestamps ride
|
||||
`parking_lot::Mutex`.
|
||||
- Feature independence: the mechanics are feature-independent (the
|
||||
helpers exist in every build and the proof rows run in every build
|
||||
they were verified under — default, `git`, `session`, `tunnel`,
|
||||
`sftp`, `--all-features`); no feature-gated code was added, so no
|
||||
new decline-path coverage obligations arise.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Landed the spawn-and-return plumbing + exit/status reporting
|
||||
mechanics (`w2-spawn-return`) in three files:
|
||||
|
||||
- **`src/serve.rs`** — `cargo test`-pinned mechanics: the
|
||||
`spawn_serving_path` choke point (the ADR-006 decision-4 rule, in
|
||||
code, never awaited inline) and `report_exit_status_then_close`
|
||||
(exit-status before close through the cloned `Session::handle()`),
|
||||
both `#[doc(hidden)]` contract-test re-exports; a unit row pins
|
||||
spawn-returns-while-pending directly.
|
||||
- **`src/handler.rs`** — the four session-row seam stubs became
|
||||
spawned serving paths (handle clone taken synchronously — the
|
||||
non-`Send` shape that forcing `async fn` stubs would produce is
|
||||
coded around and recorded); failure-then-close wire shapes unchanged
|
||||
(all dispatch-core rows pass unmodified); open-level declines and
|
||||
the tunnel seam stub stay inline by class, documented; a new
|
||||
module-doc section carries the constraint and the wave-3
|
||||
inherit-the-pattern note.
|
||||
- **`tests/spawn_return.rs`** — the end-to-end mechanics proof: two
|
||||
`Contract stamp:` rows over the real wire (handler-returns-fast
|
||||
under follow-up traffic with the spawn still pending; exit-status
|
||||
before close, timestamps proving the report outlived the handler's
|
||||
return), driven by a proof handler composing the exported plumbing.
|
||||
|
||||
Verification (all green): `cargo test` · `cargo test
|
||||
--features {git,session,tunnel,sftp} --test dispatch_core` ·
|
||||
`cargo test --all-features` · `cargo clippy --all-targets -- -D
|
||||
warnings` · `cargo clippy --all-features --all-targets -- -D
|
||||
warnings` · `cargo fmt --check` · `cargo doc --no-deps` (no
|
||||
warnings). 93 lib/unit rows + the per-build dispatch matrices + the
|
||||
two mechanics rows across 7 test binaries.
|
||||
Reference in new issue
Block a user