Skip to content

perf(dataplane): fold session FSM into the shared core (v5 S2a, no fast path) - #315

Merged
bb7133 merged 1 commit into
mainfrom
perf/fastpath-s2
Oct 5, 2026
Merged

bb7133 merged 1 commit into
mainfrom
perf/fastpath-s2

Conversation

@bb7133

@bb7133 bb7133 commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Stacked on #314 (base perf/fastpath-s1); the diff here is the single S2a commit. Will rebase onto main and retarget once #314 merges.

S2a — structural foundation for the fast path, behavior-neutral, no fast path yet. Moves the session SessionFsm out of SessionLoop into the one std::Mutex that already holds the admission counters (GateInner → FsmCore { fsm, pending, open }), so a later slice can evaluate open && pending == 0 together with a speculative transition under a single lock.

  • Gate gains three pure, synchronous methods that never expose a MutexGuard: state_snapshot(), apply_slow(event) -> (Result<Effects, TransitionError>, post_state) (runs on_event under the lock, returns owned effects; caller executes them outside the lock), and seal_closed() (terminal fence, sticky + idempotent).
  • SessionLoop holds core: Gate instead of fsm; 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. seal_and_end seals the core at every loop-terminal path before the FSM-still-Ready teardown window.
  • run_bound_session_observed passes the one session gate to the loop, so loop core + 3 gated channels (+ a later slice's engine) share it. Tests unify on one shared Gate, mirroring production.

Per the structural contracts agreed with @CodexM5: effects_executed stays on SessionLoop (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_snapshot drive the FSM identically to a standalone SessionFsm over a handshake+command script; seal_closed sticky + idempotent.

Gates: cargo test -p dataplane all suites green; fmt --check clean; clippy --all-targets clean.

🤖 Generated with Claude Code

… 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
bb7133 marked this pull request as ready for review October 5, 2026 12:06
@bb7133
bb7133 changed the base branch from perf/fastpath-s1 to main October 5, 2026 12:06
@bb7133 bb7133 closed this Oct 5, 2026
@bb7133 bb7133 reopened this Oct 5, 2026
@bb7133
bb7133 merged commit fa02952 into main Oct 5, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant