Commit graph

718 commits

Author SHA1 Message Date
8974f2cc97 research(raft-ha-core-safety): Phase-4/5 stall root cause — RDHUP-flap spurious failover
Post-Phase-2 chaotic wedge root-caused from frozen soak logs: primary stable
at term 6, but recovered standby ctrl-2 (divergent uncommitted tail 12-15)
cannot reconverge; its primary control channel RDHUP-flaps ~15s, each flap
spawns an unwinnable PreVote failover (peers see primary alive, no grant) ->
timeout -> reconnect -> repeat, so the cluster never reaches stable 3-of-3.
Fix spans Phase 4 (truncate divergent tail + catch up like ctrl-3 does) and
Phase 5 (don't launch unwinnable failover storm on transient RDHUP while a
current-term primary is reachable; reconnect+catch-up first). RED direction
recorded. (Dispatched investigator hung; reconstructed by parent.)
2026-06-28 03:12:45 +00:00
d89f7e41bc research(raft-ha-core-safety): Phase-2 interim live validation — payload-loss eliminated
Rebuilt release at HEAD (2.2+2.4), chaotic soak on real ext4. Universally-
held committed prefix (1..11) content-identical across all members; the
present-but-empty committed divergence (bug-1.3 signature) is ABSENT. The
fix eliminates the cross-term payload-loss divergence. Remaining issues:
chaotic soak wedges on election stall (Phase 5) before its verdict; tail
MISSING = catch-up lag (Phase 4). Clean soak verdict gated on Phase 5/4,
not Phase 3 (snapshot, off the critical path).
2026-06-27 15:56:50 +00:00
63b9c5a81a fix(raft-ha-core-safety): streamer fail-loud bounded-retry, never ship empty (task 2.4)
Root-cause source fix: ha_stream_wal_to_standby reads the entry body via
stream_read_entry_body (bounded retry max 4, no sleep); on persistent short
pread/malloc-fail it fails loud + ABORTS the stream (flush valid prefix,
drop unreadable entry, -EIO) instead of shipping PLEN=0. Incomplete stream
re-driven by ACK-driven catch-up. With the 2.2 guard this closes the live
cross-term payload-loss divergence. 2.3 CORE RED -> GREEN (fail-loud: no
empty persisted; + retry-recover test ships full 64). Term-blind structural
retirement deferred to 6.2 (empty-over-full safety enforced by 2.2 guard +
pinned). 12/12 core suite; siblings green; no regression introduced.
Phase 2 complete.
2026-06-27 15:42:37 +00:00
ae8291ed56 test(raft-ha-core-safety): cross-term streamer payload-integrity RED (task 2.3)
Minimal XCE_TESTING fault-injection seam in ha_stream_wal_to_standby
(NULL/production-inert) forces a short body read on a cross-term entry.
CORE RED proves the streamer ships PLEN=0 for a full (source_len=64)
cross-term entry: instrumentation source_len=64 SEND_len=0 RECV_len=0 ->
loss is SEND-side (answers task 1.3's open send-vs-transitive question).
2 honest pins (term-blind route guarded by 2.2; below-tip fence). 11
tests/1 RED; production xce_controller links clean without the seam.
2026-06-27 11:24:16 +00:00
056675c8b3 fix(raft-ha-core-safety): (term,lsn,payload-crc)-aware persist guard (task 2.2)
Payload-aware repair: identical payload = idempotent no-op; non-empty
differing payload at the TIP = repair (truncate-to-lsn-1 + re-append,
committed_lsn snapshot/restored under lock); empty-over-stored = keep
(never degrade); BELOW-tip conflict = loud non-promotable fence
(recovery_unrecoverable), never truncate the tail or lower committed_lsn.
read_entry_meta_locked computes payload-crc identically to walcompare.
Turns 2.1 case (a) green; new below-tip regression test pins the
committed-boundary safety. Review round 1 caught + fixed a committed-data-
loss bug (tail-truncate on below-tip conflict). 8/8 target suite, sibling
persist/repl/catchup/durability suites green; persist-before-ACK intact.
2026-06-27 10:57:37 +00:00
3b5eda1429 test(raft-ha-core-safety): payload-aware persist-guard RED + pins (task 2.1)
Case (a) empty-then-full = genuine RED (payload-blind guard at persist.c:822
no-ops the corrective write, Expected 64 Was 0). Cases (b) full-then-empty
and (c) identical-rewrite = baseline-passing pins that constrain the 2.2
fix asymmetry (empty must never overwrite stored full; identical re-stream
stays idempotent). 7 tests/1 RED/no ASAN.
2026-06-27 10:20:02 +00:00
07f08e58f5 test(raft-ha-core-safety): client-probe soak primary-detection (task 1.5)
Release build block-buffers --logfile (flush on signal only), so log-grep
primary/convergence detection false-aborted. Replace all log-grep detection
with client probes (xce_test ctrlstatus): probe_primary/node_probe/
cluster_committed_converged. Both modes use shared helpers; 1.2 verdict
self-test 17/17 intact; bug-854 isolation preserved. Live release-binary
runs reach the verdict (chaotic re-confirms committed_divergence=3 / job
loss 0; sequential PASS). No production code touched. Phase 1 complete.
2026-06-27 10:12:50 +00:00
dc50924833 test(raft-ha-core-safety): harness infrastructure + payload-loss smoke-RED (task 1.4)
New test_ha_core_safety.c with deterministic gate/probe seams (no sleeps,
drain-before-fini per bug-1224): payload-controllable WAL writer, on-disk
(term,pcrc,len) readback probe, walcompare observer, apply-gap + kill/
election injection for phases 2.3/4.1/5.1. Smoke-RED (Expected 64 Was 0)
proves the payload-blind (term,lsn)-only guard at persist.c:822 drops a
corrective full-payload write at a committed LSN. 5 tests/1 RED/4 seam
PASS, no ASAN errors.
2026-06-27 09:49:51 +00:00
9e55f09274 docs(raft-ha-core-safety): retarget Phase 2 to cross-term payload loss (post-1.3)
Task 1.3 refuted the term-blind hypothesis; real defect = cross-term
catch-up payload loss (streamer ships PLEN=0 on short pread/malloc-fail;
(term,lsn)-only guard is payload-blind). Retarget:
- design: new Catch-up Payload Integrity component (config_repl.c core fix);
  Persist guard -> (term,lsn,payload-crc); term-blind demoted to defensive;
  flows/traceability/testing/hypothesis sections updated.
- tasks: Phase 2 retargeted (streamer fail-loud + payload-aware guard);
  new 1.5 (release-log-buffer soak robustness); 7.2 _Depends 1.5_.
- research: old root-cause + synthesis D1/D3 marked SUPERSEDED.
Independent sanity review PASS after adding the 1.5 dependency edge.
2026-06-27 09:38:06 +00:00
21ce4c4f4a research(raft-ha-core-safety): live divergence bisect REFUTES term-blind hypothesis (task 1.3)
Reproduced committed divergence at LSN 9 (real-fs release soak): all 3
members term=3, minority ctrl-1 pcrc=0/PLEN=0 vs majority PLEN=3560 =
PAYLOAD loss, term correct. Bisect: arrived via the reconcile seam under
cross-term catch-up (stored term 3, leader term 4 / bug-1067 path), NOT
the term-blind bypass. Real defect = cross-term catch-up payload loss in
ha_stream_wal_to_standby (silent empty ship on short pread/malloc-fail).
Independently verified against frozen WALs. Secondary: release-build log
buffering breaks soak primary-detection.
2026-06-27 09:18:26 +00:00
02c0fcb6aa test(raft-ha-core-safety): chaotic soak mode + isolated verdict (task 1.2)
Opt-in overlapping-kill mode (kills inside election windows). Verdict
counts committed walcompare divergence + committed-job loss; bug-854
(client discovery) and bug-924 (overlayfs fdatasync) counted separately
and excluded from PASS/FAIL. 17/17 verdict self-test GREEN. Live smoke
corroborated chaotic regime + live bug-854 isolation.
2026-06-27 05:15:53 +00:00
b028d25dbd test(raft-ha-core-safety): machine-checkable walcompare report (task 1.1)
Per-member (lsn,term,pcrc,present) report + committed-vs-uncommitted
divergence classification via walcompare_report()/--report. Soak signal
(OK/exit-code) preserved. 4 new unit tests, all GREEN.
2026-06-27 04:53:32 +00:00
227f592250 docs(raft-ha-core-safety): finalize spec (requirements, design, research, tasks)
Reframed as residual-defect + convergence/liveness + live-verification spec
(machinery present at baseline 21e26a1). Root cause: term-blind catch-up
bypass of the single reconcile seam. 8 requirements, 7-phase task plan,
all review gates passed.
2026-06-27 04:37:07 +00:00
3609f468c5 chore(raft-ha-core-safety): init spec scaffold (HA-core failover-safety gate)
New spec for the pre-existing HA-core consistency/convergence failures the
xce-test-ha soak reproduces under chaotic failover — committed-prefix
divergence (bug-851, bug-795/828), standby-rejoin/reconverge failure
(bug-1042), and round-N election stall — all baseline-confirmed at commit
21e26a1 (independent of raft-drive-async). This spec is the gate that must
close before raft-drive-async flips its dispatch flags ON in production.

Scaffold only (spec.json + requirements.md project description); requirements
generation is the next phase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6fDCtXkkEgupu7rUah1y2
2026-06-27 03:16:17 +00:00
294b9ce8eb chore(raft-drive-async): task 4.2 regression sweep — no new regression (baseline-confirmed)
Verdict: raft-drive-async introduces NO new regression.

- Unit: controller_unit_test EXIT=0, xcelib_test 282/0 (all HA suites green;
  dispatch/snapshot/configpush flags default OFF, inert).
- Live kill-based failover soak on a real fs (~/tmp), release + ASAN builds:
  zero committed-JOB loss every completed round (jobs 3->12 monotonic).
- The 6-round CHAOTIC soak does not run clean to round 6 (fails ~round 4), but a
  baseline comparison against pre-Phase-3 binaries (commit 21e26a1) fails round 4
  IDENTICALLY (election stall / reconverge). The round-4 committed-prefix
  divergence at LSN 9 is the documented pre-existing bug-851/795 family
  (chaotic-failover silent divergence), present before Phase 3 and out of scope
  for this spec. Logged as bug-1225 (baseline-confirmed).

Also: make the xce-test-ha soak driver's controller/xce_test paths
env-overridable (XCE_HA_BIN / XCE_HA_XT, backward-compatible defaults) — used to
run the baseline binaries through the same driver for the regression comparison.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6fDCtXkkEgupu7rUah1y2
2026-06-27 03:04:23 +00:00
d2830ce514 test(raft-drive-async): receiver-side dispatch liveness integration tests (task 3.4b)
With the dispatch flags flipped ON in-test, prove the three receiver-side
liveness properties (production defaults stay OFF):

- P1: while a snapshot (and config-push) transfer is parked in flight on the
  slow lane, another endpoint's peer-control message is still processed
  promptly on the vtask pool (different thread), with the transfer still
  parked — the offloaded transfer does not block the drive path.
- P2: a peer-control ack (classified VTASK_LIVENESS) runs promptly even while
  the pool is saturated to the LivenessAdmission reservation boundary with
  REQ_BLOCK tasks (one more REQ_BLOCK fast-rejects with -E_XCE_NOT_READY,
  proving real saturation). Narrowed to the liveness-dispatch-under-saturation
  assertion; commit-advance itself is covered by the commit-engine tests.
- P3: while one endpoint's peer-control body is parked (holding a worker + that
  ep busy), another endpoint's body runs concurrently on a different worker —
  per-endpoint FIFO does not serialize across endpoints.

All deterministic (cond-var gates, bounded polls). Fixed a flaky teardown-race
hang (bug-1224): the tests released a parked vtask body then immediately called
the cancel-based vtaskmgr_fini while the just-released body was still unwinding
(P2's REQ_BLOCK bodies racing blk_lock teardown the worst). Fix adds end-of-body
completion counters polled to the full expected count before fini, so every
pool body has fully returned (worker idle) before the cancel. slowlane_fini is
graceful (join) so the lane side was already safe.

Verified: 5/5 deterministic isolated runs; full controller suite EXIT=0;
ASan-clean. Completes task 3.4 (3.4a UAF fix + 3.4b tests); the production
flag-flip remains deferred to after the 4.1 partition soak + 4.2 regression
sweep.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6fDCtXkkEgupu7rUah1y2
2026-06-27 00:36:22 +00:00
6d510d8cf4 fix(raft-drive-async): endpoint-quiescence for bug-1197 vote-ep teardown (task 3.4a)
When the (still-default-OFF) peer-control dispatch flag is ON,
raft_cb_become_follower runs as a vtask pool worker and frees every peer
vote-ep while other pool workers may still hold those eps' data — a UAF
(bug-1197). This makes that path quiescence-safe so the flag can later be
flipped ON (deferred to the 4.x prod-flip gate; production stays OFF here).

Mechanism (no libxce-wide endpoint refcount; reuses the existing per-ep FIFO +
busy serialization):

- Deferred teardown-as-vtask: the loser loop, when dispatch is ON and the pool
  is wired, does per vote-ep endpoint_mark_tearing_down -> iomgr_unmonitor ->
  enqueue a VTASK_IO/VTASK_LIVENESS teardown vtask bound to that ep. Per-ep busy
  FIFO makes it run strictly after any in-flight/queued same-ep peer-control
  vtask; the body NULLs iod->ep before endpoint_fini+free(ep) to disarm
  run_task's post-body endpoint_unblock_tasks (which would touch the freed
  ep->lock). OFF / pool-unwired path is byte-for-byte the legacy endpoint_fini.

- RCU-like drain closes the dispatch/teardown TOCTOU window: new _Atomic int
  endpoint_t.dispatch_inflight (+ tearing_down:1 bit). The ON dispatch brackets
  its flag-check+enqueue with fetch_add/fetch_sub (seq_cst);
  endpoint_mark_tearing_down sets the flag under ep->lock, releases, then spins
  until dispatch_inflight drains to 0. So every dispatch that read the flag
  false finishes its enqueue before mark returns (FIFO-before the teardown
  vtask); any later dispatch reads the flag set and drops the packet. ep->lock
  is deliberately NOT held across the enqueue (it inverts with the taskq lock in
  vtask_eligible); the counter is the lock-free handshake. Drain is bounded: the
  held window is just a calloc + non-blocking enqueue on the single io_worker.

Still open before the production flag flip (4.x): the borrowed-ep race in
snapshot_lane_fn/configpush_lane_fn is distinct (slowlane thread vs io_worker
RDHUP teardown, not the vtask-pool FIFO) and needs its own per-standby-ep
discipline.

Tests: test_ha_vep_teardown.c — 5 ASan tests (ordering, late-event drop, run_task
unblock disarm, OFF-path unchanged, deterministic mark-drains-inflight-dispatch).
xcelib_test 282/0; controller_unit_test EXIT=0; ASan-clean. Adversarial
cpp-review: no CRITICAL/HIGH; handshake formally sound, flag-ON path race-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6fDCtXkkEgupu7rUah1y2
2026-06-26 17:37:26 +00:00
bd02064ebc feat(raft-drive-async): offload requester-less config-push to slow lane (task 3.3)
push_config_transport_only spawns one thread per standby then joins them with a
CONF_PUSH_TIMEOUT_MS bounded wait — blocking the caller. On the requester-less
drive-thread paths (promotion + heartbeat convergence) that wait stalls the
io_worker. Route those through ha_config_push_offloadable, gated by
g_drive_async_configpush_offload (default OFF → inline, byte-for-byte legacy;
ON → slow lane, non-blocking).

Flag ON deep-copies the config bytes + sha into a lane ctx (caller keeps owning
and freeing its own buffer); a single cluster-wide single-flight guard
(ha_state_t.configpush_inflight scalar CAS + lane key=1) bounds it to one
config-push fan-out in flight; the in-flight flag is cleared on every terminal
path (two alloc failures, -EEXIST, hard submit error, lane completion). Send
body factored into ha_config_push_do_send.

Three requester-less drive-thread call sites converted: raft_cb_become_leader
(promotion), the HA_REPL_PUSH_CONFIG heartbeat case, and the direct-CTRL_PRIMARY
FSM promotion branch. The admin-upgrade barrier (push_config_to_standbys, has a
waiting requester) and join.c are untouched.

Lands DORMANT like tasks 3.1/3.2: the lane reads c->ha.standby_endpoints and
writes those borrowed standby endpoints, racing io_worker RDHUP teardown if run
off-thread (bug-1197 family). Flag OFF keeps push+teardown serialized on the
io_worker. Task 3.4 flips this ON together with the snapshot and peerctrl flags
after establishing endpoint-lifetime discipline.

Tests (controller suite EXIT=0): test_configpush_offload_runs_on_lane,
test_configpush_inline_when_flag_off, test_configpush_offload_dedups.
Adversarial cpp-review: no CRITICAL/HIGH/MEDIUM findings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6fDCtXkkEgupu7rUah1y2
2026-06-26 16:05:54 +00:00
6c0523b69b feat(raft-drive-async): snapshot SlowLane offload + WAL-trim cursor guard (task 3.2)
Part B (LIVE, default-ON): trim_wal_segments now bounds WAL-segment deletion
by repl_trim_lsn(&p->repl, active_seg_lsn) = min(active segment LSN, every
active standby acked_lsn, every valid disconnected-slot last_disconnected_lsn),
after repl_expire_disconnect_slots ages out stale slot guards. floor <=
active_seg_lsn structurally, so the guard can only preserve more segments,
never delete one a live standby still needs — closes the bug-795/828
trim-vs-cursor race for connected/recently-disconnected standbys (bug-1147
stopgap; empty/cursor=0 join still needs entity-bearing snapshots).

Part A (DORMANT, g_drive_async_snapshot_offload default OFF): ha_snapshot_send_
dispatch routes HA_REPL_SEND_SNAPSHOT inline (flag OFF — byte-for-byte legacy,
on io_worker) or to slowlane_submit+snapshot_lane_fn (flag ON). Send body
factored into ha_snapshot_do_send. Per-peer single-flight via
ha_state_t.snapshot_inflight[] CAS + slowlane key; in-flight flag cleared on
all four terminal paths, mirroring the reconnect offload (task 2.1).

Lands dormant like task 3.1: snapshot_lane_fn borrows a standby endpoint it
does not own, so flag-ON would race io_worker RDHUP teardown (cross-thread UAF,
bug-1197 family). Flag OFF keeps send+teardown serialized on the io_worker.
Task 3.4 is the single gate that flips both this and the peerctrl flag ON after
establishing endpoint-lifetime discipline; bug-1198 stays open by design until
then. The offload flag was initially authored default-ON (would ship the UAF)
and corrected to OFF in ha.c and the ha.h accessor doc (bug-1210).

Tests (controller 196/0, 0 new leaks): test_snapshot_offload_runs_on_lane,
test_snapshot_inline_when_flag_off, test_snapshot_offload_dedups_per_peer,
test_trim_guard_preserves_standby_segments,
test_trim_without_standbys_proceeds_normally. Adversarial cpp-review: GO.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6fDCtXkkEgupu7rUah1y2
2026-06-26 15:43:24 +00:00
e17a6a613c feat(raft-drive-async): peer-control vtask dispatch shim, dormant (task 3.1)
Lands the PeerCtrlDispatch machinery behind a DEFAULT-OFF flag
(g_drive_async_peerctrl_dispatch_enabled); production behavior is
UNCHANGED. peer_ctrl_msg_handler becomes a thin shim that takes
ownership of ep->fsm_in.pkt and either runs the routing body inline
(flag OFF, byte-for-byte the prior path) or dispatches it to the vtask
pool via vtask_enq_classified (flag ON). The 3 routers now take pkt as
an explicit param (no cross-thread ep->fsm_in.pkt re-read). Per-endpoint
arrival order is enforced by bind_vtask's endpoint_block_tasks.

Packet ownership: shim clears ep->fsm_in.pkt at dispatch (sole-owner
transfer); peer_ctrl_vtask_entry NULLs iod->pkt before the body frees
it once. New vtask_t.owns_io_pkt (default 0) lets taskq_item_killer free
the carried packet for an un-executed queued task at vtaskmgr_fini WITHOUT
double-freeing the libxce endpoint-owned path (bug-1199).

Adversarial review found two FLAG-ON hazards, documented with loud
comments (flag is OFF so production is unaffected), to be resolved before
task 3.4 flips the flag ON:
  - bug-1197 (CRITICAL): raft_cb_become_follower endpoint_fini()s peer
    vote-eps from a pool worker, racing the io_worker's read of ep->data;
    needs iomgr_unmonitor before endpoint_fini in every pool-reachable
    teardown.
  - bug-1198 (HIGH): peer_ctrl_classify tags all work VTASK_LIVENESS, but
    the CTRL_PRIMARY repl-ack path can run a blocking snapshot checkpoint
    inline; resolved by task 3.2 (offload snapshot to SlowLane).

Per spec, 3.1 is not 'done' until the task 3.4 deadlock gate is green.
Tests: 3 new peerctrl tests; controller 196/196, lib 282/0; 0 net leaks.
2026-06-26 11:12:04 +00:00
21e26a17b6 test(raft-drive-async): sustained reconnect-offload integration test (task 2.3)
Escalates the single-tick unit tests (15-17) to sustained liveness: with
one peer unreachable (reconnect parked in-flight on the lane for the whole
window), drives 3 consecutive heartbeat ticks against a 4-node cluster
(two survivors) and asserts every survivor keeps receiving heartbeats on
every tick (g_nop_calls == 2*3), each tick returns promptly (delaymgr
never stalled), the reconnect stays in-flight throughout, and is deduped
across all ticks (connect hook submitted exactly once — Req 2.5).

RED arm (offload flag OFF) proves the inline connect blocks the first
tick, so survivors stop getting heartbeats (g_nop_calls < 6). The flag
toggle is the RED<->GREEN switch; production is unchanged (test-only task).

test_ha_liveness suite 23 -> 25 tests; full controller suite 196/196 pass.
2026-06-26 10:04:57 +00:00
e03ab9bd72 feat(raft-drive-async): resolve inbound/outbound channel ownership on reconnect (task 2.2)
When a reconnect registers a new outbound control channel for peer X,
on_standby_connected now evicts any pre-existing standby_endpoints entry
for the same peer_idx (a promoted vote-ep or a stale prior channel),
keeping exactly one live outbound channel per peer (Req 2.6/4.4). The
stale ep is torn down OUTSIDE ha->lock via standby_ep_err_handler_primary
(which re-takes the lock) and reclaimed; resolution is strictly per-peer.

Also fixes endpoint leaks on the endpoint_get() calloc paths: every
endpoint_fini() error path now free(ep) (fini reclaims inner members
only), closing the task-2.1 GREEN-test leak masked by detect_leaks=0.

Tests 18-19 in test_ha_liveness.c assert single-survivor resolution and
per-peer isolation. 196/196 controller unit tests pass.
2026-06-26 09:54:10 +00:00
e07667ff9b feat(raft-drive-async): offload delaymgr peer reconnect to slow lane (task 2.1)
Heartbeat timer detects a disconnected peer, CAS-acquires a per-peer
single-flight flag, slowlane_submits the bounded connect, and returns — the
delaymgr never blocks on connect. Lane worker (reconnect_lane_fn) does the
bounded endpoint_connect + lock-guarded iomgr_monitor; flag cleared on all
terminal paths (incl. -EEXIST race fix). Default-ON kill-switch retains the
inline path for TDD-RED/fallback. Never self-terminates on failure. 2.2 adds
the inbound/outbound race + failure hardening (documented seam). 196/0 green.
2026-06-26 08:08:31 +00:00
a36234a103 feat(raft-drive-async): LivenessAdmission pool reservation + commit fold (tasks 1.6/1.7)
vtaskmgr gains vtask_class_t (LIVENESS/LIVE_BLOCK/REQ_BLOCK) + a pool-level
blocking reservation: REQ_BLOCK fast-rejects -XCE_ERR_NOT_READY past
n_vcpus-R; LIVENESS never gated (deadlock-freedom). commit_submit folds its
inflight_waits onto the pool via commit_set_pool; production wired in
cluster.c phase2 (vtaskmgr_set_liveness_reserved(R) + commit_set_pool).
Lock order p->lock->ce->lock->blk_lock. Dormant until 3.1. Commit suites
38/30/22/38 green; lib 282/0, controller 196/0, ASAN clean. Phase 1 done.
2026-06-26 07:43:19 +00:00
0781c6c027 feat(raft-drive-async): bound peer connect (O_NONBLOCK+poll), close tasks 1.3/1.4/1.5
socket_connect sets O_NONBLOCK before connect and bounds the attempt with
poll(timeout); new connect_timeout_ms param (0=DEFAULT_CONNECT_TIMEOUT_MS);
all 5 callers updated (ha.c x3, client.c, xcectx.c). Revalidation Trigger:
shared netio helper signature changed. Broad build links all components;
xcelib_test 278/0, controller_unit_test 194/0. 1.4/1.5 unit tests satisfied
by the TDD output of 1.2/1.3 (test_slowlane.c, test_netio_bounded_connect.c).
2026-06-26 07:10:53 +00:00
5650acd375 feat(raft-drive-async): SlowLane bounded executor for long blocking I/O (task 1.2)
Dedicated-pthread pool isolated from the vtask pool; non-blocking slowlane_submit
(0 enqueued / -EEXIST deduped / -EAGAIN cap-full-deferred), admission cap on
running tasks, per-key in-flight dedup, run-exactly-once, drain+join fini. Wired
into controller lifecycle (dormant; consumers land in 2.x/3.x). 7 unit tests,
mutation-verified cap+dedup; full suite 194/0 green, 15x ASAN.
2026-06-26 05:24:00 +00:00
729055a37f feat(raft-drive-async): add drive-async config constants + liveness-reservation helper (task 1.1)
drive_async_liveness_reserved = max(1, min(n_peers-1, n_vcpus-1)) — single
source of truth for VTASK_LIVENESS_RESERVED (deadlock-freedom floor applied
last). New DEFAULT_CONNECT_TIMEOUT_MS / DEFAULT_SLOWLANE_* constants. Resolved
reservation logged at init; under-provisioned (n_vcpus<n_peers) clamp WARNs
loudly, never self-terminates. 8 unit cases, mutation-verified.
2026-06-26 04:58:07 +00:00
295769d8ae add wolf state 2026-06-25 14:30:46 +08:00
6563adf8c6 docs(raft-sim-conformance): cosmetic cleanups (LOW review nits)
- sim_tla_action_name: drop brittle per-case 'raft.tla:NNN' line-number comments
  (some had drifted a few lines off; the return string IS the stable raft.tla
  Next-disjunct name, and the function header already cites raft.tla:454).
- test_sim_5_1_partition_heal: the per-follower convergence loop was vacuous —
  it bounded by commit_index, which stays 0 for followers under the leader-only
  commit model, so the loop never ran. Bound by log_count instead (followers DO
  advance the replicated log); Log Matching still guarantees the shared-prefix
  terms agree. Now non-vacuous; still passes.

Test/comment-only; no behavior change. Lib suite Ok:1 Fail:0.
2026-06-25 04:38:43 +00:00
0e8b4a12bf test(demotion-convergence): de-flake demotion_replay test (bug-1128) — two causes
test_demotion_replay_yields_committed_only flaked (~16% on overlayfs, but
fs-independently too). Two root causes, both test-harness artifacts (NOT product
bugs; production self-restart is execv on a real fs and never overlaps persist
contexts or runs an auto-committer during demotion):
 1. single-node commit race: the fixture's live commit engine on_durable_advance
    auto-commits on n_peers=1, advancing committed_lsn past the test's boundary so
    the 'uncommitted' suffix became committed. Fix: detach on_durable_advance +
    re-pin committed_lsn to the test's own committed_boundary, so the suffix stays
    genuinely uncommitted (the test's premise).
 2. overlayfs fd-incoherence: a fresh persist_t recovering over the same files
    reads a stale post-ftruncate size on overlayfs. Fix: hybrid statfs gate —
    real fs keeps the full fresh boot-recovery assertion; overlayfs asserts the
    same property via the coherent live-context current_lsn.
Test-only; production truncate (ftruncate+fdatasync) untouched. 0/25 runs fail
(was 4/25 with the prior persist_stop-only attempt).
2026-06-25 04:28:48 +00:00
46bd207b0f test(ha-wal-replication): seed current_lsn in 7 stale wal-catchup tests for bug-850 contiguity guard
The bug-850/851 forward-gap contiguity guard (persist.c:839, added in 20e59d0b)
correctly rejects a catch-up entry whose lsn is ahead of the WAL tail (-EAGAIN).
7 test_wal_catchup_* tests in test_config_repl.c predate the guard and applied
non-contiguous LSNs (42, 10-12, 1000+) to a fresh persist (current_lsn=1),
failing deterministically. Seed current_lsn to each test's first applied LSN so
the in-order stream is contiguous (mirroring the real catch-up streamer); the
apply->advance path and all asserted semantics are unchanged. Test-only; the
production contiguity guard is untouched. 179 controller tests, 0 failures (x3).
2026-06-25 03:03:56 +00:00
13c43c23a4 test(ha-term-safety): controller Election-Safety regression for bug-1095/1096 + absorb into spec
Adds two controller-level regression tests driving the production raft_on_vote_reply
via ha->raft (test_ha_election.c): a duplicated vote-grant from one peer must not
double-count toward quorum (bug-1095) and a stale-term reply must be dropped
(bug-1096) — both asserted on role/promotion (no two leaders per term). Formally
absorbs bug-1095/1096 into the ha-term-safety spec (Implementation Note) mapping to
its Requirement 1 / Objective; the raft.c fix (b5b7e01) is owned there with
protocol-suite regression at both the libxce (test_vote_reply_*) and controller
(test_ha_election) levels. No production source changed.
2026-06-25 02:44:29 +00:00
95629797f8 feat(raft-sim-conformance): optional TLC-consumable trace export, default-off (task 8.1) 2026-06-24 16:49:36 +00:00
ffeb7c2cf5 docs(raft-sim-conformance): reflect runtime fault-injection flag (vs compile-time macro) in design 2026-06-24 16:36:54 +00:00
90197f8595 test(raft-sim-conformance): negative test proving oracle teeth, both directions (task 7.2) 2026-06-24 16:30:20 +00:00
62523892d9 feat(raft-sim-conformance): runtime fault-injection switch (G1 term-stamp, G3 truncate) (task 7.1) 2026-06-24 16:15:13 +00:00
7087dc19d9 docs(raft-sim-conformance): mark task 6.2 done — conformance batch ASAN/UBSAN-clean 2026-06-24 16:04:29 +00:00
47f68032b7 test(raft-sim-conformance): randomized M-seed sweep + all scenarios, zero violations (task 6.1) 2026-06-24 15:59:22 +00:00
a2933c274d feat(raft-sim-conformance): stale-rejoin scenario, bug-851 G5 term adoption via AE (task 5.5) 2026-06-24 15:50:13 +00:00
b1f289d2cb feat(raft-sim-conformance): Figure-8 commit-safety scenario + restore dropped 5.3 impl (task 5.4)
Adds raft_sim_scenario_figure8: a previous-term entry on a quorum under a
new-term leader is NOT committed until a current-term entry commits (library-
gated by raft_advance_commit; verified by a mutation test). Also restores the
raft_sim_scenario_drop_dup_reorder implementation that commit e445404 (task 5.3)
silently dropped from raft_sim.c (a stray git checkout reverted the uncommitted
impl), which had left the lib build broken with an undefined reference.

Build green: test_sim_5_3_drop_dup_reorder + test_sim_5_4_figure8 both PASS.
2026-06-24 15:38:01 +00:00
e445404439 feat(raft-sim-conformance): drop/duplicate/reorder adversarial scenario (task 5.3) 2026-06-24 15:09:31 +00:00
6976392d77 feat(raft-sim-conformance): restart-churn adversarial scenario (task 5.2) 2026-06-24 14:47:25 +00:00
7d0964dd16 feat(raft-sim-conformance): partition/heal adversarial scenario (task 5.1) 2026-06-24 14:34:22 +00:00
b844e75f3b feat(raft-sim-conformance): raft_sim_run + deterministic replay (task 4.4) 2026-06-24 14:14:15 +00:00
5f67be4fa3 test(raft-sim-conformance): conformance Restart persistence test (task 4.3) 2026-06-24 14:05:19 +00:00
b5b7e01b92 fix(raft): vote-reply per-peer dedup + stale-term drop (bug-1095/1096)
raft_on_vote_reply double-counted duplicated vote-responses (counter ignored
from_idx) and failed to drop stale-term replies, allowing two leaders at the
same term (bug-851 split-brain family). Mirror the existing pre-vote per-peer
dedup pattern (new vote_replies[] set) and add DropStaleResponse (term <
currentTerm). Surfaced by the raft-sim-conformance oracle (task 4.2).

47 raft / 264 overall lib tests pass.
2026-06-24 13:56:24 +00:00
e15bbf8190 feat(raft-sim-conformance): seeded scheduler raft_sim_step + per-step invariant check (task 4.2)
Harness surfaced a real upstream raft.c split-brain bug (bug-1095/1096,
bug-851 family) in raft_on_vote_reply: counter-based vote tally double-counts
a duplicated vote-response and no stale-term drop -> two leaders same term.
Out of this spec's boundary (raft.c protocol); recorded as Implementation Note.
2026-06-24 13:37:03 +00:00
90e7e9c999 feat(raft-sim-conformance): action trace recorder + raft_sim_dump_trace (task 4.1) 2026-06-24 13:10:31 +00:00
f0be46fb32 feat(raft-sim-conformance): raft_sim_check_all first-failure composition (task 3.5) 2026-06-24 13:00:10 +00:00
a38d9a7271 feat(raft-sim-conformance): State Machine Safety invariant checker (task 3.4) 2026-06-24 12:51:58 +00:00