Commit graph

385 commits

Author SHA1 Message Date
e56b2064de test(ha-heartbeat-liveness): SIGSTOP/SIGCONT tolerance integration test (task 4.5)
Add src/tools/test-heartbeat-tolerance.sh: automated bash script that
deploys a 3-node cluster and runs two SIGSTOP/SIGCONT scenarios:

  Scenario A (1 × heartbeat_interval_ms pause): verifies no RDHUP fires on
  standbys — liveness_poll_fn must not trigger failover for a short silence
  within the tolerance window (Req 2.2).

  Scenario B (heartbeat_timeout_ms + 500ms pause): verifies exactly one RDHUP
  fires on each standby — the activity-based poll fires after sustained silence
  and does not loop (Req 2.3, Req 5.4).

Add .kiro/specs/ha-heartbeat-liveness/manual-tests.md: step-by-step manual
procedure, expected log patterns, and results template for both scenarios.

Mark task 4.5 complete in tasks.md.
2026-06-03 04:18:56 +00:00
529552c127 fix(ha-heartbeat-liveness): start primary heartbeat in COMMITTING phase (task 4.3)
Call ha_start_primary_heartbeat in raft_cb_become_leader when entering
CTRL_PRIMARY_COMMITTING so standbys receive NOPs during the committing
window. Fixes 60s stability: 0 false RDHUP events on standbys.
2026-06-01 15:56:36 +00:00
aceadf0357 chore(ha-heartbeat-liveness): mark unit test tasks 4.1-4.2 complete
All required unit test cases already implemented in prior tasks.
2026-06-01 15:43:29 +00:00
12216f1165 feat(ha-heartbeat-liveness): verify ctrlhello integration and RDHUP cancel (task 3.2)
Verification task: confirmed config_repl.c handle_ctrlhello calls both
ha_on_heartbeat_received and ha_arm_heartbeat_timeout_vtask (delaymgr poll).
Confirmed ha_on_primary_ctrl_fd_rdhup leads with ha_disarm (Req 5.4).
No pthread_create in heartbeat arm path (Req 1.6). Add thread-count test.
2026-06-01 15:37:35 +00:00
d937c1c1bb feat(ha-heartbeat-liveness): role-based liveness reset in opcode dispatch (task 3.1)
Replace fd-based guard in OPCode_nop with role==CTRL_STANDBY check.
Add ha_on_heartbeat_received call to OPCode_hello ctrlhello path when standby.
Satisfies Req 1.3 (any message resets deadline) and Req 4.1 (ctrlhello counts).
3 new dispatch guard tests added to test_ha_liveness.c.
2026-06-01 15:31:15 +00:00
622302a55c feat(ha-heartbeat-liveness): idle-triggered primary NOP per standby (task 2.2)
Extend primary_heartbeat_fn to read last_push_time per standby and send NOP
only when elapsed >= heartbeat_interval_s. on_standby_connected writes
last_push_time before ctrlhello send. Remove workaround xce_nop_req at end
of on_standby_connected. Add ha_nop_req_hook XCE_TESTING seam. 3 new tests.
2026-06-01 15:05:41 +00:00
738859fb4e feat(ha-heartbeat-liveness): replace hb_timeout_thread with liveness_poll_fn (task 2.1)
Remove hb_vtask_t, hb_timeout_thread, s_hb_vtask from ha.c. Replace with
liveness_poll_ctx_t + delaymgr liveness_poll_fn: checks cancelled flag and
role==CTRL_STANDBY before acting; re-arms at heartbeat_interval_ms/2.
ha_on_primary_ctrl_fd_rdhup calls ha_disarm_heartbeat_timeout_vtask first
to prevent duplicate failover (Req 5.4). ha_on_heartbeat_received writes
last_activity via clock_gettime. Add test_ha_liveness.c with 7 unit tests.
2026-06-01 14:34:14 +00:00
9f9a6b3c0f feat(ha-heartbeat-liveness): add validate_heartbeat_config startup check (task 1.2)
Enforce heartbeat_timeout_ms >= 2 * heartbeat_interval_ms at startup.
validate_heartbeat_config() in startup_validation.c returns -EINVAL with
error message on violation; wired in cluster_init_phase1 before ha_init().
4 unit tests added to test_ha_startup.c.
2026-06-01 12:26:29 +00:00
bcb45e59d2 feat(ha-heartbeat-liveness): add last_activity and last_push_time state fields (task 1.1)
Add time_t last_activity to ha_state_t for standby liveness timestamp tracking.
Add time_t last_push_time to standby_ep_ctx_t for primary idle NOP suppression.
Zero-initialize last_activity in ha_init(); last_push_time zeroed by calloc.
Add test_ha_init_last_activity_is_zero to test_ha_startup.c.
2026-06-01 11:59:16 +00:00
e85100d761 test(ha-peer-connection-reuse): complete tasks 7.1, 7.2, 7.3 — all validation done
Task 7.1 (unit tests) was already complete with 8 tests in
test_ha_connection_reuse.c. Added two new tests:

Task 7.2 — test_follower_passive_no_outbound_connect (Req 2.1, 2.4):
  Verifies raft_cb_become_follower sets role=CTRL_STANDBY and nulls all
  votes[i].ep. The old fetch_config_from_primary path is gone; standbys
  never initiate outbound connects after election loss.

Task 7.3 — test_promoted_ep_repl_ack_promotes_to_primary (Req 5.2, 5.4):
  Injects two OPCode_replCatchUpDone packets via peer_ctrl_msg_handler in
  CTRL_PRIMARY_COMMITTING with n_peers=4 (5-node cluster, quorum=2 ACKs).
  Verifies repl_ack_count reaches 2 and role transitions to CTRL_PRIMARY.
  Added vote_ep_ctx_set_cluster_for_test() seam so vctx->c is non-NULL
  when ha_repl_ack_received fires ha_promote_to_primary.

All 62 controller tests pass; ASAN clean.
spec: .kiro/specs/ha-peer-connection-reuse/tasks.md all tasks [x]
2026-06-01 07:53:02 +00:00
78b7942bbe docs(ha): use generated cleanup.sh in clean restart; clarify what it preserves
cleanup.sh is generated by deploy-ha.sh and already does the right thing:
keeps ctrl-1/conf/* and all certs, wipes ctrl-{2..N}/conf, all data/, and logs.
Update skill and CLAUDE.md to use it as the primary clean-restart command
instead of manual rm loops.
2026-06-01 06:03:34 +00:00
484b110009 docs(ha): add xce-deploy-ha skill and update controller CLAUDE.md
- .claude/skills/xce-deploy-ha/SKILL.md: new skill for deploying a
  local 3-node XCE HA controller cluster. Covers build prerequisites,
  deploy via src/tools/deploy-ha.sh, node startup with -vvv raft trace,
  health verification checks, clean restart procedure, and a
  troubleshooting table for common failure modes.
- src/controller/CLAUDE.md: add HA Cluster Deployment section with
  quick-deploy commands, health check one-liners, and reference to the
  xce-deploy-ha skill. Add src/tools/deploy-ha.sh to key files.
2026-06-01 06:01:13 +00:00
3d076ccda6 fix(ha): fix config replication to standbys; add replConfigPush handler
- config_repl.c: add handle_repl_config_push — standby handler for
  OPCode_replConfigPush. Verifies SHA-256, saves blob to disk via
  conf_save_config, reloads via load_primary_config, ACKs primary.
- config_repl.h: declare handle_repl_config_push.
- cluster.c: dispatch OPCode_replConfigPush to handle_repl_config_push;
  populate c->myhost_info.name from myconf->name (ctrl-1/ctrl-2/ctrl-3)
  instead of the static "controller" placeholder.
- ha.c: fix raft_cb_become_leader push_config_to_standbys call to pass
  real config bytes and tag read from disk (was NULL/0 — standbys got
  empty push and never saved config).
- typemap.h: add raw_sha256/raw_bytes/raw_bytes_len fields to
  repl_config_push_req_t (mirrors upgrade_config_req_t pattern).
- request.c: pre-extract List(UInt8) sha256/bytes from OPCode_replConfigPush
  before capn_free() in xce_decode(); free them in xce_free_request().
- helper.c: add all HA/replication opcode names to xce_request_op_name
  so "unknown opcode 30" becomes "replConfigPush" in logs.
- cluster_def.h: add embedded myhost_info field to cluster_t struct.
- ha.h: declare ha_start_primary_heartbeat; remove dead
  ha_find_and_connect_to_primary declaration.
2026-06-01 05:56:16 +00:00
d00d3bb051 fix(ha): wire raft trace to DEBUG3; drop NOP on promoted ep; fix --vcpus in deploy script
- ha.c: add raft_cb_trace callback wired to s_real_cbs.on_trace; emits
  RAFT[pN] term=T event=E at DEBUG3 level, visible with -vvv flag.
- ha.c: repl_ack_decode_and_dispatch now silently drops OPCode_nop and
  OPCode_reply instead of WARNING — heartbeat NOP replies from standbys
  arrive on promoted endpoints and are expected.
- deploy-ha.sh: fix --vcpu -> --vcpus; add \$VERBOSE passthrough so
  start.sh respects VERBOSE=-vvv from the environment.
2026-06-01 05:20:13 +00:00
7b39650229 refactor(ha): passive standby model — primary connects outbound to all peers
Remove fetch_config_from_primary, ha_find_and_connect_to_primary, and the
tier-1/2/3 discovery path. Standbys never initiate outbound connections to
the primary; the primary owns all inter-controller channels.

Key changes:
- ha.c: add primary heartbeat loop (ha_start_primary_heartbeat) that sends
  OPCode_nop to connected standbys every heartbeat_interval_ms; loop
  self-cancels on demotion via role check before re-arm.
- ha.c: add outbound reconnect in primary_heartbeat_fn — for peers absent
  from standby_endpoints, open fresh outbound TLS connection (on_standby_connected
  sends ctrlhello; standby_ep_ctx_t mirrors vote_ep_ctx_t layout).
- ha.c: remove ha_find_and_connect_to_primary + helpers (ha_raw_tcp_connect,
  ha_tcp_connect_hook); remove election_timeout_fn (superseded by
  raft_election_timeout_fn); remove fetch_config_from_primary call from
  ha_elect_evaluate_and_signal STANDBY branch.
- cluster.c: OPCode_requestVote denial no longer reuses inbound connection;
  late joiners are bootstrapped via primary heartbeat outbound connect.
- config_repl.c: remove connect_ssl_raw, do_one_config_fetch,
  fetch_config_from_primary (~250 LOC).
- myapp.c: remove my_tag > 0 fetch branch; all standbys wait passively.
- ha.h: remove ha_find_and_connect_to_primary declaration + macros +
  ha_tcp_connect_hook seam; add ha_start_primary_heartbeat declaration.
- meson.build: remove test_ha_connect.c (tests deleted function).
- controller_test.c: remove test_ha_connect registration.
- CLAUDE.md, src/controller/CLAUDE.md: document passive standby model,
  inbound-vs-outbound connection ownership, async I/O constraint,
  heartbeat model.
- request.h: add xce_request declaration (removes scattered local externs).
2026-06-01 04:17:05 +00:00
842b9a838e fix(election): no-config standbys wait passively for primary push instead of polling 2026-05-31 11:37:52 +00:00
1c1fe462cc fix(election): increase election timeout to 5000ms to handle TLS handshake latency 2026-05-31 11:33:14 +00:00
0f766776ba fix(ha-term): move ha-term.bin to {workdir}/{name}/data/ alongside WAL/snapshot 2026-05-31 11:04:35 +00:00
04b31afb06 fix(election): no-config nodes set CTRL_STANDBY to trigger config fetch; voter-only path 2026-05-31 10:39:33 +00:00
a75fb2581b fix(election): no-config nodes skip own election; delay_ms=0 fires election immediately 2026-05-31 10:27:05 +00:00
937605ec60 feat(raft-library): tasks 6.4 + 7 complete — all ASAN-clean, hooks verified, spec closed 2026-05-31 09:25:20 +00:00
becf6ee765 feat(raft-library): reject even n_peers in raft_init; add test_raft_init_even_peers_returns_einval 2026-05-31 08:48:30 +00:00
fd9933c9d0 feat(raft-library): task 6.3 — delegate election dispatch to raft_* API in ha.c + cluster.c 2026-05-31 08:43:01 +00:00
8f0d2d55c3 feat(raft-library): task 6.2 — real raft callbacks in ha.c; fix double-free + dead stubs 2026-05-31 07:53:00 +00:00
b77525deb0 feat(raft-library): task 6.1 — raft_state_t field in ha_state_t, raft_init with no-op stubs 2026-05-31 07:20:15 +00:00
6bdf7edacf feat(raft-library): task 5.4 — Groups F+G, 6 test cases (RDHUP failover + split-brain) 2026-05-31 07:05:45 +00:00
5c677bc52d feat(raft-library): task 5.3 — Groups D+E 7 test cases; fix Req 6.3 on_cancel_timeout in decided path 2026-05-31 06:58:02 +00:00
0aa4c84bfe feat(raft-library): task 5.2 — protocol Groups C+H, 11 test cases (eligible policy + persist) 2026-05-31 06:34:33 +00:00
389047194f feat(raft-library): task 5.1 — protocol Groups A+B, 10 test cases (election + voted_for) 2026-05-31 06:26:52 +00:00
c1b07392df feat(raft-library): task 4.2 — sim deliver/partition/timeout/start-all; 4 tests 2026-05-31 06:20:35 +00:00
d2e2c1ef8a feat(raft-library): task 4.1 — sim data structures, raft_sim_init, dump_log; 2 tests 2026-05-31 06:11:34 +00:00
85a7e325a8 feat(raft-library): task 3.5 — election timeout, leader-lost, check-and-start; 6 tests 2026-05-31 06:02:29 +00:00
f81054930f feat(raft-library): task 3.4 — raft_on_vote_reply quorum tracking; 6 tests + review fixes 2026-05-31 05:56:34 +00:00
e0bcb35554 feat(raft-library): task 3.3 — raft_on_vote_request 6-step decision; 10 tests 2026-05-31 05:44:34 +00:00
4955fa3805 feat(raft-library): task 3.2 — raft_start_election with single-node shortcircuit; 4 tests 2026-05-31 05:30:00 +00:00
1aa7db4583 feat(raft-library): task 3.1 — raft_init validation, state init, raft_snapshot; 12 tests 2026-05-31 05:18:42 +00:00
d109155a21 feat(raft-library): task 2.2 — raft_callbacks_t, raft_config_t, public API signatures 2026-05-31 05:03:04 +00:00
66c9810823 feat(raft-library): task 2.1 — raft_role_t, raft_state_t, raft_snapshot_t in raft.h 2026-05-31 04:56:49 +00:00
7f7de013b9 feat(raft-library): task 1.1 — build scaffold (raft/ files + meson wiring) 2026-05-31 04:51:42 +00:00
dbb750ebd3 fix(election-iomgr-refactor): Raft election, config decode, and RDHUP chain
- ha.c: self-vote in ha_start_election_async (Raft invariant)
- ha.c: heartbeat_timeout_ms for hb_timeout_thread (not election_timeout_ms)
- ha.c: clear voted_for before RDHUP jitter (stale vote from dead primary)
- ha.c: same-term voted_for check in RDHUP jitter
- config_repl.c/h: use xce_decode for upgradeConfig (capnp extraction before capn_free)
- cluster.c: fix upgradeConfig handler direction (standbys apply, primaries skip)
- cluster.c: remove raw_pkt_copy hack; use req directly
- join.c: push config to newly-joined standbys
- typemap.h/request.h/request.c: raw_sha256/raw_bytes fields for decoded upgradeConfig
- test_ha_failover.c: update heartbeat timeout test to use g_fast_cfg
2026-05-31 03:54:30 +00:00
1ded1022fb fix(election-iomgr-refactor): RDHUP failover fixes — heartbeat watchdog + jitter
- ha.c: arm heartbeat timeout watchdog after ha_find_and_connect_to_primary (all 3 tiers)
- ha.c: guard ha_arm_heartbeat_timeout_vtask with c!=NULL (prevents ASAN stack-use-after-return in unit tests)
- ha.c: add priority-based jitter to ha_on_primary_ctrl_fd_rdhup to avoid split-brain
- ha.c: clear voted_for before RDHUP jitter so check reflects new votes, not stale startup votes
2026-05-31 02:24:52 +00:00
fd93997f78 fix(election-iomgr-refactor): config replication and RDHUP failover chain
- cluster.c: fix upgradeConfig handler (standbys apply, not primaries)
- cluster.c: fix raw_pkt_copy opcode peek (read scalar before capn_free)
- config_repl.h: enable CONF_ENABLE_UPGRADE_CONFIG=1
- config_repl.c: use client_ssl_ctx for outgoing config fetch connections
- config_repl.c: after config fetch, run cluster_init_phase2 + find_primary
- join.c: push config to newly-joined standbys on OPCode_join
- myapp.c: schedule fetch_config_from_primary after skipping own election
- ha.c: arm heartbeat timeout watchdog after find_and_connect_to_primary
2026-05-31 02:08:48 +00:00
2f5a738b36 fix(election-iomgr-refactor): startup election stability fixes
- delay.c: fix task_cmp 3-arg signature to match ordq_cmp_fn_t
- netio.c: skip SSL_new in endpoint_connect if ep->ssl pre-created
- ha.c: pre-create client SSL before endpoint_connect
- ha.c: fix double-free (let endpoint_fini own ep->data/vctx)
- ha.c: add endpoint_set_peer + AF_INET/SOCK_STREAM args to endpoint_init
- ha.c: fix voted_for concurrent-access with ha->lock in handle_request_vote
- ha.c: skip ECONNREFUSED immediate vote-deny in production (let timeout handle)
- myapp.c: priority-based startup stagger (priority_step_ms * priority)
- myapp.c: skip own election if already voted during startup delay
2026-05-30 15:36:10 +00:00
81ea5e8eb8 fix(election-iomgr-refactor): integration bug fixes from 6.3 cluster test
- netio.c: skip SSL_new in endpoint_connect if ep->ssl already set (pre-created client ctx)
- ha.c: pre-create client SSL before endpoint_connect to avoid server-ctx handshake failure
- ha.c: fix double-free of vctx in endpoint_connect error path (let endpoint_fini own ep->data)
- ha.c: add endpoint_set_peer() call after endpoint_init for vote endpoints
- ha.c: fix endpoint_init args to AF_INET/SOCK_STREAM for outgoing vote sockets
- ha.c: wrap handle_request_vote voted_for check in ha->lock to prevent split-brain
- ha.c: skip immediate vote-deny for ECONNREFUSED in production; let election timeout handle
- delay.c: fix task_cmp signature to match ordq_cmp_fn_t (3 args: ctx, arg1, arg2)
2026-05-30 14:59:54 +00:00
e875cae7ad chore(election-iomgr-refactor): mark tasks 6.1 and 6.2 complete in tasks.md 2026-05-30 13:46:17 +00:00
052c891ac0 test(election-iomgr-refactor): add ha_repl_ack_received quorum/promotion unit tests — task 6.2 2026-05-30 13:42:41 +00:00
0fdcd11e5f test(election-iomgr-refactor): add committing_timeout_fn watchdog unit tests — task 6.1 2026-05-30 13:19:31 +00:00
27ea14cd9a feat(election-iomgr-refactor): remove old blocking election code (am_i_primary_quorum, vote_request_thread, bootstrap_config_if_needed) — task 5.3 2026-05-30 13:04:19 +00:00
31a54043d5 feat(election-iomgr-refactor): wire RDHUP failover to ha_start_election_async — task 5.2 2026-05-30 12:43:02 +00:00
2e5681387a feat(election-iomgr-refactor): restructure myapp_run to fully non-blocking async startup — task 5.1 2026-05-30 12:26:56 +00:00