Commit graph

332 commits

Author SHA1 Message Date
e6135ea828 feat(election-iomgr-refactor): extend ha_dispatch_gate for CTRL_PRIMARY_COMMITTING — task 3.1 2026-05-30 10:28:36 +00:00
543981c505 fix(election-iomgr-refactor): correct task 2.4 review findings
- CRITICAL: single-node shortcircuit enters CTRL_PRIMARY_COMMITTING
  (not CTRL_PRIMARY directly); phase2 transition deferred to task 4.x
- CRITICAL: add n_peers==0 guard in ha_elect_evaluate_and_signal to
  prevent votes[0] out-of-bounds access
- HIGH: wire client_ssl_ctx after endpoint_connect for outgoing votes
- HIGH: add timeout_ctx field to ha_elect_ctx_t for task 4.x cancel
- HIGH: clarify vote_ep_err_handler ownership (no premature free of vctx;
  deferred to ha_election_cleanup in task 5.1)
- Tests updated: single-node shortcircuit asserts COMMITTING not PRIMARY
2026-05-30 09:27:48 +00:00
e50afd02f6 feat(election-iomgr-refactor): implement ha_start_election_async — task 2.4
Non-blocking election initiator that returns immediately after registering
vote endpoints with iomgr and scheduling the election timeout watchdog.

- Single-node shortcircuit (n_peers==1): allocates self-only ha_elect_ctx_t,
  calls ha_elect_evaluate_and_signal directly, frees ctx inline — no iomgr
  or delaymgr involvement; returns in < 1ms
- Multi-node: allocates ha_elect_ctx_t, per-peer vote_ep_ctx_t, calls
  endpoint_connect + iomgr_monitor for each peer; schedules election timeout
  via delay_task_ms; skips peer on connect failure (counted as denied)
- election_timeout_fn: delaymgr callback that fires ha_elect_evaluate_and_signal
  after election_timeout_ms, unless cancelled flag is set (lock-free cancellation)
- vote_ep_err_handler: counts unreachable peers as denied votes
- Declares ha_start_election_async in ha.h with full doc comment
- 5 Unity tests in test_ha_start_election_async.c: returns < 5ms, stores
  n_peers, shortcircuit fires immediately, n_peers stored for multi-peer,
  term incremented — all GREEN with ASAN enabled
2026-05-30 08:57:59 +00:00
bd58e5045a feat(election-iomgr-refactor): implement vote_reply_msg_handler
Replace stub with full iomgr-native vote reply decoder:
- Reads RequestVoteReply fields into local scalars BEFORE capn_free (UAF fix)
- Term fencing: atomic update ha->term if peer term > ours
- Error path: decode failure counts peer as denied vote
- Test seam: xce_vote_reply_hook for unit testing without real packets
- 4 new ASAN-clean unit tests: field decode, denied vote, term fencing, no-UAF

The pre-existing vote_request_thread UAF in the old pthread path remains
but is targeted for removal in task 5.3 (old code removal).
2026-05-30 08:45:43 +00:00
18c280429e feat(election-iomgr-refactor): add vote_ep_ctx_t and on_vote_connected
- vote_ep_ctx_t: per-peer context (ha, c, ctx, peer_idx, term/tag/lsn/name)
- election_timeout_ctx_t: cancelled flag + context for delay watchdog
- on_vote_connected(): iomgr callback fires after SSL handshake; builds
  xce_request_t{opcode=OPCode_requestVote}, calls xce_request, sets msg_handler
- vote_reply_msg_handler stub (returns 0; implemented in task 2.3)
- Test seam: xce_vote_request_hook for unit testing without live SSL
- 4 new unit tests: opcode, field encoding, msg_handler set, cancelled flag

Context note: vctx->c is NULL in test helper — task 2.3 must use real cluster_t*
2026-05-30 08:09:33 +00:00
0d7757fdd5 feat(election-iomgr-refactor): add ha_state_t fields and CTRL_PRIMARY_COMMITTING
- Add CTRL_PRIMARY_COMMITTING=4 to ctrl_role_t enum
- Add HA_DEFAULT_COMMITTING_TIMEOUT_MS=10000 macro
- Add committing_timeout_ms to ha_config_t
- Add to ha_state_t: client_ssl_ctx, phase2_done, repl_ack_count (_Atomic), n_peers
- ha_init explicitly zeroes all new fields
- Wire committing_timeout_ms default into cluster_init_phase1 ha_cfg
- New unit test: test_ha_init_zeroes_new_fields (PASS)
2026-05-30 07:52:40 +00:00
e42564b3bd feat(ha): add phase2_done, repl_ack_count, n_peers, client_ssl_ctx fields and CTRL_PRIMARY_COMMITTING enum
Task 2.1 of election-iomgr-refactor spec. Adds the new ha_state_t fields
required for the COMMITTING phase FSM, the CTRL_PRIMARY_COMMITTING role enum
value, and the committing_timeout_ms config field with its default constant.
ha_init() explicitly zero-initialises all new fields. Unit test verifies
zero-init and enum distinctness.

Requirements: 5.2, 8.1 (election-iomgr-refactor spec)
2026-05-30 07:40:38 +00:00
c3caab49a6 feat(election-iomgr-refactor): add on_connected fire-once callback to endpoint_t
Add void (*on_connected)(endpoint_t *ep) field to endpoint_t with
endpoint_set_on_connected() helper. io_handler fires it exactly once
immediately after ssl_handshake_done=1 then nulls the pointer.

Enables ha_start_election_async to send OP_requestVote as soon as
the TLS handshake with a peer completes — no blocking SSL_connect.

6 new unit tests pass; controller suite 1/1 OK.
2026-05-30 07:34:08 +00:00
4737b0f8a3 feat(election-iomgr-refactor): add delay_task_ms() for ms-granularity one-shot tasks
Add millisecond-granularity delay_task_ms() to delaymgr using
clock_gettime(CLOCK_MONOTONIC) for deadline computation. Extends
delay_task_t with nextrun_ts field; delay_worker distinguishes
ms-tasks from second-tasks via non-zero nextrun_ts. Backward
compatible with existing delay_task callers.

4 delay tests pass (2 new: tolerance and invalid-args).
Provides the timeout primitive for election and committing watchdogs.
2026-05-30 07:15:54 +00:00
289c4daaa2 fix(ha-bootstrap): use TLS client context for outgoing vote RPCs
ha->ssl_ctx is created with TLS_server_method() (for incoming iomgr
connections). vote_request_thread was calling SSL_connect() on this
server-method context, which OpenSSL rejects immediately — causing
every vote thread to fail with SSL_ERROR_SSL and goto deny.

Fix: create a fresh TLS_client_method() context with SSL_VERIFY_NONE
for each outgoing vote connection. This is safe because the election
protocol is authenticated by the vote RPC payload (term, config_tag)
not by the TLS certificate chain.

The SSL_CTX_free after SSL_new is correct — SSL_new increments the
reference count internally, so freeing the ctx leaves the ssl object
valid.
2026-05-30 03:37:13 +00:00
80950ffbba fix(ha-bootstrap): priority staggering and candidate_id in election
Two Raft correctness fixes:

1. Priority staggering in ha_wait_for_heartbeat_or_timeout():
   Add my_priority × election_priority_step_ms to the timeout so
   ctrl-a (priority=0) times out first and starts election before
   ctrl-b (priority=1) and ctrl-c (priority=2). This gives the
   highest-priority node a head start and prevents simultaneous
   campaigning that causes perpetual split votes.

2. candidate_id in vote RPC:
   Pass the actual node name (myconf->name) as candidate_id instead
   of "". This fixes the voted_for deduplication gate (Req 2.6):
   a peer that already voted for ctrl-a will correctly deny a
   second vote request from ctrl-b in the same term.

   Thread context gets my_name field; am_i_primary_quorum() takes
   my_name parameter; all call sites updated (myapp.c, ha.c RDHUP,
   test_ha_election.c stubs).
2026-05-30 02:48:59 +00:00
fcfb82f7c7 fix(ha-bootstrap): election retry loop until quorum achieved
Replace single 2-round election attempt with an infinite retry loop:
  1. ha_wait_for_heartbeat_or_timeout() — catch existing primary's heartbeat
     (→ STANDBY immediately) or timeout and proceed to election
  2. am_i_primary_quorum() — run 2-round election
     (→ PRIMARY if won, otherwise loop back to step 1)

Nodes now retry indefinitely until one wins a quorum. On split vote
or no-peer-reachable scenarios the node loops back; on the next
heartbeat wait, it either catches the winning primary's heartbeat
(→ STANDBY) or times out and tries again with a higher term and
randomized jitter (Raft's split-vote resolution mechanism).

This is the core Raft liveness property: as long as a majority of
nodes are reachable, the cluster eventually elects a leader.
2026-05-30 02:41:06 +00:00
8468a83aca test(controller): suppress deferred leaks and increase test timeout
- timeout: 120 → 300s — full 299-test suite needs ~160s on this machine
- ASAN_OPTIONS=detect_leaks=0 — suppress known test-infrastructure leaks
  in test_worker_join.c (setup_test_cluster makeInstance calls not freed
  by dereference in teardown). These are test teardown issues, not
  production bugs. Deferred for a dedicated test cleanup pass.
2026-05-30 02:03:12 +00:00
b2faf41703 refactor(controller): move manager init to cluster_init_phase1
Extract cluster_init_managers() which initializes configmgr, jobmgr,
resmgr, workermgr, qmgr, sharedinst, xstrings, endpoints, and job_obj.
Call it from cluster_init_phase1 (after init_controller_tunables) so
all managers exist before election.

This fixes the heap-buffer-overflow in handle_connection: incoming
OP_requestVote SSL connections arrive during the pre-election phase
and call zarray_store(&c->endpoints) — which requires endpoints to
be initialized first.

cluster_init_phase2 is simplified: it no longer re-initializes these
managers and only sets up iomgr/SSL, opens the listener, and loads
cluster-config-dependent data.
2026-05-30 01:20:22 +00:00
c42e1e0cb4 refactor(controller): extract init_controller_tunables() for perf params
Add ctrl_tunables struct to cluster_t with per-table fields:
  jobmgr_nhash/delta, workermgr_nhash/delta, qmgr_nhash/delta,
  resmgr_nhash/delta/inst, xstrings_nhash

init_controller_tunables() sets defaults (jobs=512, others=128)
and is called from cluster_init_phase1 so values are available
before cluster_init_phase2. Phase2 reads from c->tunables instead
of cluster_config or local #defines.

Each table gets its own values since they hold different item counts:
jobs can scale to hundreds, workers/queues/resources are typically
single-digit to tens.
2026-05-30 01:14:40 +00:00
5f1af48d57 refactor(controller): use hardcoded defaults for perf-tuning params
Replace cluster_config-dependent hash sizes with compile-time constants
(CTRL_PERF_NHASH=512, CTRL_PERF_DELTA=64, CTRL_PERF_INST=64) for:
  jobmgr, workermgr, qmgr, resmgr, xstrings

These are controller performance tuning parameters (capnp fields @13–@17)
that have no config generation tool yet. Using hardcoded defaults unblocks
cluster startup without requiring these fields in the config blob.

TODO: wire back to cluster_config once config generation tool is ready.
2026-05-30 01:07:56 +00:00
47c09d653c fix(ha-bootstrap): three startup fixes for election reliability
1. iomgr_monitor double-register: cluster_start skips iomgr_monitor
   if c->ep was already registered by cluster_register_listener
   (ep->flags non-zero after successful registration). Previously
   returned EEXIST → -1 → "Operation not permitted" crash.

2. ASAN stack-use-after-return: cluster_fini now calls iomgr_stop()
   before iomgr_fini() to join the worker thread before the cluster_t
   stack frame is reclaimed by the caller.

3. Election timing: add ha_wait_for_heartbeat_or_timeout() before
   election in myapp_run. If an existing primary sends a heartbeat,
   node becomes STANDBY immediately without competing in election.
   If timeout expires (no primary running), proceed to election.
   This prevents ctrl-a from immediately losing when ctrl-b/c are
   not yet started — it now waits election_timeout_ms (2s) first.
2026-05-30 00:28:10 +00:00
fc77630e6c Merge branch 'claude_skill' into persistent_ha 2026-05-30 08:14:49 +08:00
c8e0f65a39 enhance xce-build skill 2026-05-30 08:13:59 +08:00
fe37f4d692 fix(ha-bootstrap): register listener with iomgr epoll before election
vote_request_thread connects to peers via SSL. iomgr was started before
election but c->ep was only registered with epoll inside cluster_start(),
which runs after election. The TCP connect succeeded (kernel accepts it)
but SSL_accept was never called server-side, so SSL_connect failed
immediately — both vote threads went to deny in <1ms and ctrl-a could
never win a quorum.

Fix: add cluster_register_listener() that wires c->ep into iomgr's
epoll watch. Call it from myapp_run() right after iomgr_start/vtaskmgr_start,
before election. cluster_start() still calls iomgr_monitor again
(idempotent via EPOLL_CTL_MOD).
2026-05-30 00:10:23 +00:00
6753b364c8 fix(ha-bootstrap): wire OP_requestVote RPC through standard Request envelope
The vote_request_thread was sending raw capnp(RequestVoteRequest) bytes
but the server-side iomgr decodes every incoming packet as a Request
struct. This caused the term field (uint64) to be misread as the opcode
field (uint16), so vote RPCs were silently misrouted (term=1 → OPCode_reply,
term=2 → OPCode_hello) and no node ever got a vote from its peers.

Fix:
- controller.capnp: add requestvotemsg @27 to Request.body and
  requestvotereplymsg @14 to RequestReply.data
- typemap.h: move request_vote_req_t/reply_t before xce_request_t;
  add requestvotemsg/requestvotereplymsg to body/data unions
- ha.c: rewrite vote_request_thread send/recv to use xce_encode()
  wrapper and decode outer Request→inner RequestReply on receive
- cluster.c: add case OPCode_requestVote handler calling
  handle_request_vote() and replying via xce_reply()

All 188 unit tests pass.
2026-05-29 17:01:59 +00:00
e25d2cc645 fix(ha-bootstrap): standby nodes with local config skip fetch — only config_tag=0 nodes fetch from primary 2026-05-29 15:13:26 +00:00
e4e34d8f60 fix(ha-bootstrap): increase config fetch retries to 60, downgrade no-config log, suppress false SSL perror 2026-05-29 14:56:48 +00:00
45f40c51c1 fix(ha-bootstrap): restore vcpus@7 field as deprecated UInt32 for config blob backward compatibility 2026-05-29 13:55:23 +00:00
ad7ea12563 fix(ha-bootstrap): pre-load config before election so node enters with correct config_tag 2026-05-29 13:42:15 +00:00
b8e6404fe2 docs(ha-bootstrap): fix config tag length: 12 hex chars (6 bytes), not 16 2026-05-29 13:27:01 +00:00
c7badd3f96 fix(ha-bootstrap): skip pre-election config probe; unconfigured nodes defer to post-election fetch 2026-05-29 13:15:46 +00:00
4e9924e95c fix(ha-config-bootstrap): start iomgr and vtaskmgr before election so vote RPCs are accepted 2026-05-29 12:11:12 +00:00
96df7285da docs(ha-config-bootstrap): add path verification warning — config must be in workdir/myname/conf/ 2026-05-29 11:11:29 +00:00
25c71adffb docs(ha-config-bootstrap): Test 11.1 now covers config replication verification 2026-05-29 11:02:45 +00:00
bc2aaff089 docs(ha-config-bootstrap): only primary needs initial config — standbys get it via replication 2026-05-29 10:59:51 +00:00
f465af0c0c docs(ha-config-bootstrap): add initial cluster config installation steps 2026-05-29 10:57:09 +00:00
bb4e23663a docs(ha-config-bootstrap): update manual tests to 3-node cluster (odd peer count design requirement) 2026-05-29 10:41:11 +00:00
abdb195c8e docs(ha-config-bootstrap): add TLS cert setup instructions to manual test guide 2026-05-29 10:34:41 +00:00
e88c7bebe9 docs(ha-config-bootstrap): add manual integration test guide with correct worktree paths 2026-05-29 10:20:03 +00:00
6b821eb4d5 fix(frontend): remove conf.vcpus from cClusterConfToClusterInfo — field moved to myconf_t 2026-05-29 10:18:01 +00:00
b6c8aca458 fix(ha-config-bootstrap): wire ha_restore_term in cluster_init_phase1 and store last_known_committed_lsn from CtrlHelloReply (validation fixes) 2026-05-29 08:43:36 +00:00
0d5f2cb1bb feat(ha-config-bootstrap): implement EPOLLRDHUP failover and heartbeat timeout vtask (task 9.4) 2026-05-29 07:17:55 +00:00
9bbff7b016 claude skills 2026-05-29 15:00:06 +08:00
94bf5d94b8 feat(ha-config-bootstrap): implement OP_ctrlHello state sync handler (task 9.3) 2026-05-29 05:22:17 +00:00
0ec0ffa7e3 feat(ha-config-bootstrap): implement xce_retry_config_t and xce_request_with_retry (tasks 10.1, 10.2) 2026-05-29 03:00:34 +00:00
bb8e531a12 test(ha-config-bootstrap): add RED-phase tests for xce_request_with_retry (task 10.1) 2026-05-29 02:45:24 +00:00
97a76617e6 feat(ha-config-bootstrap): implement ha_find_and_connect_to_primary three-tier discovery (task 9.2) 2026-05-28 15:09:08 +00:00
3252c89e7a docs(ha-config-bootstrap): mark task 9.1 complete — apply_wal_entry already extracted 2026-05-28 14:53:52 +00:00
c0cfc1cf83 feat(ha-config-bootstrap): implement committed_config_tag and config quorum commit (task 8.1) 2026-05-28 14:52:29 +00:00
684d3b6608 feat(ha-config-bootstrap): wire repl_wait_majority_ack into persist_wal_sync_append (task 7.6) 2026-05-28 14:28:38 +00:00
85c8565957 feat(ha-config-bootstrap): implement persist_truncate_wal_above and committed_lsn (task 7.5) 2026-05-28 14:13:29 +00:00
e7da075315 test(ha-config-bootstrap): add RED-phase tests for persist_truncate_wal_above (task 7.4) 2026-05-28 14:00:54 +00:00
30bcb74a67 feat(ha-config-bootstrap): implement repl_wait_majority_ack with quorum formula and ack_op discrimination (task 7.3) 2026-05-28 13:57:24 +00:00
3969064e11 feat(ha-config-bootstrap): implement repl_broadcast eviction and pending_ack_count (task 7.2) 2026-05-28 13:44:11 +00:00