Close out #736: convergence replay reuse/bound (#635) + daemon lock scoping (#633)#770
Conversation
…nce replay (#635) Part of the #736 campaign — the safe subset of the deferred convergence BFS work (no incremental-prefix cache, so zero change to which branch wins). - Reuse: the BFS already fully materializes each completed candidate path at its terminal probe, but canonicalize_openmls_batch re-materialized every path a second time. Retain the terminal OpenMlsMaterializedCandidate on the completed path and, on the stored-message hot path, feed it straight into a new private canonicalize_openmls_batch_with_materialized core instead of replaying again. Reuse is CONDITIONAL: the BFS probe folds pending proposals but NOT applications, so with pending app messages the reused candidate would miss the ApplicationProcessed observations app_messages_by_id consumes. Reuse therefore fires only when the pass has no pending application messages (byte-identical by construction); otherwise the existing fresh re-materialize runs. The public canonicalize_openmls_batch keeps its exact prior behavior (used directly by conformance vectors). - Bound: a per-pass ReplayBudget (derived from commit count and max_rewind_commits) caps total OpenMLS replay round-trips and fails closed (ReplayBudgetExceeded) rather than returning a partial result, bounding attacker-driven same-epoch branching. Tests: engine_reuses_bfs_materialized_candidates_when_no_pending_app_messages drives an app-free multi-commit chain through the reuse branch; inline replay_budget_tests cover the budget arithmetic (incl. saturation) and fail-closed exhaustion. Full cgka-engine + conformance suites green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Part of the #736 campaign — the highest-blast-radius deferred item (CLI daemon concurrency model). The Execute and StreamWatch arms held the shared `workers` async mutex across the entire command, including relay network I/O and run_cli_local, so one slow relay-backed command stalled every other command and the subscription reconcile. Now the lock is held only for the host-mutating reconcile (mirroring the subscription handlers) and the relay work runs off the lock: - Add reconcile_and_clone_runtime: lock, reconcile, clone the runtime handle, release. - Execute-path handlers (account setup, hosted command dispatch, refresh) reconcile under the lock, then perform relay I/O against the cloned runtime handle unlocked. - run_cli_local (opens its own account/session; touches no shared state) runs fully off the lock — the core head-of-line fix. - StreamWatch reconciles under the lock, then spawns the watch + opens the broker connection unlocked (the stream-watch registry is already interior-mutable). - Split handle_app_runtime_command_request into a locked Execute entry and a host-based entry for the compose path (which already holds the lock), sharing one unlocked dispatch_hosted_runtime_command — no re-entrant lock / deadlock. - Stream-compose (mutates the &mut session map + host together) keeps the lock for its low-traffic QUIC-preview path. Test: daemon_execute_local_command_runs_without_holding_worker_lock holds the workers lock for the whole Execute and asserts a relay-less local command still completes. Full darkmatter-cli suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughThis PR bounds OpenMLS convergence replay with a fail-closed budget, reuses BFS-materialized candidates during canonicalization, and refactors daemon Execute/StreamWatch handling to reduce workers-lock hold time across relay I/O. ChangesBounded Replay Budget and BFS Candidate Reuse
Estimated code review effort: 4 (Complex) | ~60 minutes Daemon Lock Scoping for Execute and StreamWatch
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
Ready to review this PR? Stage has broken it down into 3 individual chapters for you:
Chapters generated by Stage for commit ea11b5e on Jul 2, 2026 1:19am UTC. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/cli/src/daemon/mod.rs`:
- Around line 407-420: The `Command::Stream` branch in `mod.rs` is still
acquiring the `workers` mutex for all stream commands, which can block
non-compose hosted stream paths unnecessarily. Update the `if
matches!(cli.command, ...)` gate to match only the compose subcommands before
locking `workers`, and keep the lock acquisition inside that narrower
compose-only branch around `handle_stream_compose_request`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8e8119f0-9b2b-4eea-9e09-e445eab1d439
📒 Files selected for processing (6)
crates/cgka-engine/src/openmls_projection.rscrates/cgka-engine/tests/distributed_convergence.rscrates/cli/src/daemon/mod.rscrates/cli/src/daemon/runtime_host.rscrates/cli/src/daemon/stream_workers.rscrates/cli/src/daemon/tests.rs
#770) The Execute arm took the workers lock for every `Command::Stream`, so non-compose hosted stream commands (start/finish/watch/send) briefly contended on an unrelated busy workers mutex before `handle_stream_compose_request` returned None and they fell through to the off-lock hosted path. Narrow the gate to the compose subcommands (ComposeOpen/Append/Finish/Cancel) so only they acquire the lock; behavior is otherwise unchanged (the compose request already returned None for non-compose stream commands). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes out the remaining open children of tracking issue marmot-protocol/mdk#380. The bulk of marmot-protocol/mdk#380 landed in the merged #768 (five boundary contracts); the only children left open were the two deliberately-deferred items (#635, marmot-protocol/mdk#438) plus one stale bug (#692). This PR resolves all three.
marmot-protocol/mdk#437 — convergence BFS replay: reuse + bound (safe subset)
The deferred full incremental-prefix rework stays deferred (branch-selection risk). This is the safe subset only — zero change to which branch wins.
canonicalize_openmls_batchre-materialized every path a second time. The terminalOpenMlsMaterializedCandidateis now retained on the completed path and, on the stored-message hot path, fed straight into a new privatecanonicalize_openmls_batch_with_materializedcore instead of replaying again.ApplicationProcessedobservationsapp_messages_by_idconsumes. Reuse therefore fires only when the pass has no pending application messages (byte-identical by construction); otherwise the existing fresh re-materialize runs. The publiccanonicalize_openmls_batchkeeps its exact prior behavior (used directly by conformance vectors).ReplayBudget(derived from commit count ×max_rewind_commits) caps total OpenMLS replay round-trips and fails closed (ReplayBudgetExceeded) rather than returning a partial result, bounding attacker-driven same-epoch branching. Generous enough that the default (max_rewind_commits = 5) never trips.Tests:
engine_reuses_bfs_materialized_candidates_when_no_pending_app_messagesdrives an app-free multi-commit chain through the reuse branch; inlinereplay_budget_testscover the budget arithmetic (incl. saturation) and fail-closed exhaustion. Fullcgka-engine(39 convergence) +cgka-conformance-simulator(73) suites green.marmot-protocol/mdk#438 — daemon
workerslock held across relay I/O (head-of-line blocking)The Execute/StreamWatch arms held the shared
workersasync mutex across the entire command (including relay I/O andrun_cli_local), so one slow relay-backed command stalled every other command and the subscription reconcile. Now the lock is held only for the host-mutating reconcile (mirroring the already-correct subscription handlers), and the relay work runs off the lock:reconcile_and_clone_runtime: lock → reconcile → clone the runtime handle → release.run_cli_local(opens its own account/session; touches no shared state) runs fully off the lock — the core head-of-line fix.handle_app_runtime_command_requestsplit into a locked Execute entry and a host-based entry for the compose path (which already holds the lock), sharing one unlockeddispatch_hosted_runtime_command— no re-entrant lock / deadlock.&mutsession map + host together) keeps the lock for its low-traffic QUIC-preview path.Test:
daemon_execute_local_command_runs_without_holding_worker_lockholds the workers lock for the whole Execute and asserts a relay-less local command still completes. Fulldarkmatter-clisuite (133) green.Honest caveat: a true slow-relay end-to-end demonstration needs a stalling relay; the unit test proves the local path never touches the lock, backed by the existing
tests/cli.rsdaemon integration suite.marmot-apphas no counting mockTransportAdapter, so the off-lock reconcile granularity relies on by-construction correctness + the passing integration suites (same limitation noted in #768).marmot-protocol/mdk#416 — closed as stale (no code)
The filed O(group-history) rebuild-on-re-delivery no longer reproduces on
master— the!app_event_exists_txgate is gone; a re-delivered kind-9 CHAT takes the incremental upsert path. Closed with revalidation evidence.Scope
Closes marmot-protocol/mdk#437
Closes marmot-protocol/mdk#438
After merge, marmot-protocol/mdk#380 closes on its full in-scope set (#768 + this PR); marmot-protocol/mdk#416 is already closed as stale.
Verification
just fast-cigreen (fmt + check + clippy, incl. OTLP feature builds).cargo test -p cgka-engine -p darkmatter-cli -p cgka-conformance-simulatorall green.🤖 Generated with Claude Code
Summary by CodeRabbit
ReplayBudgetExceedederror.