Repository navigation
perf(dataplane): fold session FSM into the shared core (v5 S2a, no fast path) - #315
Merged
Merged
Conversation
… path)
S2a is the structural foundation for the fast path: it moves the
session `SessionFsm` out of `SessionLoop` and into the one `std::Mutex`
that already holds the admission counters, so a later slice can read
`open && pending == 0` together with a speculative transition under a
single lock. No fast path and no behavior change yet.
- gate.rs: `GateInner` -> `FsmCore { fsm, pending, open }`. `Gate` is now
the handle to that shared core. Added three pure, synchronous methods
that never expose a `MutexGuard`:
- `state_snapshot() -> SessionState` (locked `Copy` read),
- `apply_slow(event) -> (Result<Effects, TransitionError>, post_state)`
— runs `on_event` under the lock and returns owned effects plus the
post-transition state; the caller executes effects *outside* the lock,
- `seal_closed()` — the terminal fence (idempotent, sticky).
The mutex still guards only pure computation; it is never held across an
`.await`, send, or effect.
- session.rs: `SessionLoop` holds `core: Gate` instead of `fsm`. Every
`self.fsm.state()` becomes `self.core.state_snapshot()`; `apply` routes
through `apply_slow` and judges the handshake deadline on the returned
post-state. The `BackendProbe` arm hoists the state read into a local
*before* `backend_active().await` so the guard never spans the await.
New `seal_and_end` seals the core closed at every loop-terminal path
(FsmClosed / ServerShutdown / SourceExhausted) before the FSM-still-Ready
teardown window.
- session_engine.rs: `run_bound_session_observed` passes the one session
`gate` to the loop, so the loop's core, all three gated channels, and
(a later slice) the engine share it.
- tests: `channels()` returns the shared `Gate` and builds the control
channel from it, mirroring production; all loop constructions thread it.
`effects_executed` stays on `SessionLoop`, counted after each effect
executes; `fast_transitions` and the ordered action-trace ledger are
left to later slices. `S2b` TODOs marked where a read-then-await decision
must re-snapshot once the engine can commit concurrently.
New gate tests: `apply_slow`/`state_snapshot` drive the FSM identically
to a standalone `SessionFsm` over a handshake+command script; `seal_closed`
is sticky and idempotent.
cargo test -p dataplane: all suites green (103 lib / 92 session_engine /
18 session_loop / …); fmt --check clean; clippy --all-targets clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bb7133
marked this pull request as ready for review
October 5, 2026 12:06
bb7133
force-pushed
the
perf/fastpath-s2
branch
from
October 5, 2026 12:06
0374491 to
fb78f14
Compare
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.
Stacked on #314 (base
perf/fastpath-s1); the diff here is the single S2a commit. Will rebase ontomainand retarget once #314 merges.S2a — structural foundation for the fast path, behavior-neutral, no fast path yet. Moves the session
SessionFsmout ofSessionLoopinto the onestd::Mutexthat already holds the admission counters (GateInner→FsmCore { fsm, pending, open }), so a later slice can evaluateopen && pending == 0together with a speculative transition under a single lock.Gategains three pure, synchronous methods that never expose aMutexGuard:state_snapshot(),apply_slow(event) -> (Result<Effects, TransitionError>, post_state)(runson_eventunder the lock, returns owned effects; caller executes them outside the lock), andseal_closed()(terminal fence, sticky + idempotent).SessionLoopholdscore: Gateinstead offsm;applyroutes throughapply_slowand judges the handshake deadline on the returned post-state. TheBackendProbearm hoists the state read into a local beforebackend_active().awaitso the guard never spans the await.seal_and_endseals the core at every loop-terminal path before the FSM-still-Ready teardown window.run_bound_session_observedpasses the one sessiongateto the loop, so loop core + 3 gated channels (+ a later slice's engine) share it. Tests unify on one sharedGate, mirroring production.Per the structural contracts agreed with @CodexM5:
effects_executedstays onSessionLoop(counted after execute);fast_transitions+ ordered action-trace deferred to later slices; S2b TODOs marked where a read-then-await decision must re-snapshot once the engine commits concurrently; the std mutex is never held across an await.New gate tests:
apply_slow/state_snapshotdrive the FSM identically to a standaloneSessionFsmover a handshake+command script;seal_closedsticky + idempotent.Gates:
cargo test -p dataplaneall suites green;fmt --checkclean;clippy --all-targetsclean.🤖 Generated with Claude Code