Skip to content

Distributed hardening: soak harness + coordinator recovery + backpressure (M5 E2/E3)#9

Merged
jayhere1 merged 6 commits into
mainfrom
feat/m5-soak-harness
Jul 11, 2026
Merged

Distributed hardening: soak harness + coordinator recovery + backpressure (M5 E2/E3)#9
jayhere1 merged 6 commits into
mainfrom
feat/m5-soak-harness

Conversation

@jayhere1

Copy link
Copy Markdown
Owner

Distributed-runtime hardening — M5 E2 and E3 from docs/PRD_USER_READINESS.md. The soak harness drove out three real reliability gaps in the coordinator; each is fixed and covered. All green: 82 distributed tests, clippy --all-targets -D warnings clean, fmt clean.

E2 — soak / chaos harness (+ a bug it found)

conduit-distributed/tests/soak.rs drives the real coordinator under continuous load while crashing and replacing workers mid-flight, asserting the §8.2 SLOs — exactly-once completion, drain-to-zero, bounded state — and reporting throughput + p50/p99. Fast fixed burst by default (a real CI regression, ~1s); CONDUIT_SOAK_SECS=<n> runs a continuous soak (nightly can set hours). Verified exactly-once over 4,600+ tasks and 76 crash/replace cycles.

Bug it surfaced: a task dispatched to a worker in the window between its disconnect and handle_worker_disconnect computing the orphan list was stranded in inflight forever. Fixed with a reconciliation sweep in the periodic health-check that requeues any in-flight task whose worker is no longer connected. Covered by a focused unit test + the soak itself.

E3 — coordinator crash recovery

A restarted coordinator previously orphaned every running task. Now it survives a restart:

  • AssignmentStore trait, two backends: InMemoryAssignmentStore (default, non-durable — original behaviour) and RocksAssignmentStore (RocksDB). One shared conformance suite + a reopen-survival test.
  • Coordinator records on dispatch, removes on terminal result; with_store + async recover() reloads persisted assignments and re-queues them (old workers are gone; they re-register and pick the work up); an is_recovering() gate queues new submissions during recovery so recovered work keeps its place.
  • Exposed in production via DistributedExecutor::with_persistence({state_dir}/coordinator_assignments); new() stays in-memory and unchanged.
  • Tests: a full restart scenario (3 in-flight tasks survive a drop+reopen and re-dispatch to a fresh worker) + a completed-tasks-are-not-recovered durability check.

E3 — real backpressure (was drop-on-full)

submit_task used to log an error and silently drop a task when the queue was full — lost work the caller thought succeeded. It now returns SubmitOutcome::{Dispatched,Queued,Rejected}: a full queue yields Rejected without accepting the task, so the caller applies backpressure. New high_water_mark config + is_under_backpressure() let producers throttle before the queue fills. Two unit tests.

Everything composes: the soak still holds exactly-once with persistence recording active on every dispatch.

Remaining M5 (not in this PR): D7/D8 — SQLGlot differential oracle + lineage resolver hardening (conduit-lineage).

🤖 Generated with Claude Code

jayhere1 added 6 commits July 10, 2026 09:23
…y (PRD E2)

Adds an in-process soak harness (conduit-distributed/tests/soak.rs) that
drives the real Coordinator under continuous load while crashing and
replacing workers mid-flight, asserting the §8.2 SLOs: exactly-once
completion, drain-to-zero, and bounded state, and reporting throughput +
p50/p99 latency. Fast fixed burst by default (a real CI regression,
~1s); CONDUIT_SOAK_SECS=<n> runs a continuous soak (nightly can set
hours).

The harness surfaced a real coordinator gap: a task dispatched to a
worker in the window between its disconnect and handle_worker_disconnect
computing the orphan list is stranded in `inflight` for a removed
worker with no recovery path. Fixed with a reconciliation sweep in the
periodic health-check that requeues any in-flight task whose worker is
no longer connected (idempotent, safe every tick). Covered by a focused
unit test plus the soak harness itself; verified exactly-once holds over
4600+ tasks and 76 crash/replace cycles with zero duplicates or losses.
…store (PRD E3)

Persists the coordinator's authoritative in-flight state so a restarted
coordinator reconstructs it instead of silently orphaning every running
task.

- New AssignmentStore trait with two backends in conduit-distributed:
  InMemoryAssignmentStore (default — original non-durable behaviour) and
  RocksAssignmentStore (RocksDB, keyed by assignment_id). Both pass one
  conformance suite; the Rocks backend also has a reopen-survival test.
- Coordinator records each dispatched assignment (both the immediate and
  drained-from-queue paths) and removes it on terminal result. New
  Coordinator::with_store + async recover(): recover() loads persisted
  assignments and re-queues them as pending (the old workers are gone;
  they re-register and pick the work up). An is_recovering() gate queues
  new submissions during recovery so recovered work keeps its place.
- DistributedExecutor::with_persistence(config, path) opens the durable
  store at {state_dir}/coordinator_assignments and recovers on startup;
  new() stays in-memory and unchanged.
- Tests: a full restart scenario (3 in-flight tasks survive a coordinator
  drop+reopen and re-dispatch to a fresh worker) and a
  completed-tasks-are-not-recovered durability check.

80 distributed tests pass; the soak harness still holds exactly-once
with persistence recording active on every dispatch.
submit_task previously logged an error and silently DROPPED a task when
the queue was full — lost work the caller thought succeeded. It now
returns a SubmitOutcome (Dispatched | Queued | Rejected): a full queue
yields Rejected without accepting the task, so the caller applies
backpressure instead of losing it. New CoordinatorConfig.high_water_mark
(default 80% of max_queue_size) + Coordinator::is_under_backpressure()
let producers throttle proactively before the queue fills.
DistributedExecutor::dispatch surfaces the outcome (logging Rejected)
and exposes is_under_backpressure(). Two unit tests cover full-queue
rejection + the dispatched/backpressure signals. 82 distributed tests
pass; soak still holds exactly-once.
@jayhere1
jayhere1 merged commit c452e1b into main Jul 11, 2026
7 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