diff --git a/.kiro/specs/ha-config-bootstrap/tasks.md b/.kiro/specs/ha-config-bootstrap/tasks.md index 31dd3c5..a560d8f 100644 --- a/.kiro/specs/ha-config-bootstrap/tasks.md +++ b/.kiro/specs/ha-config-bootstrap/tasks.md @@ -181,7 +181,7 @@ Dependency order: startup validation (D) unblocks vtaskmgr before election (A); - _Requirements: 5.6, 5.7_ - _Boundary: Persistence Layer (persist.c, persist.h)_ -- [ ] 7.6 Wire majority-commit into persist_wal_sync_append() +- [x] 7.6 Wire majority-commit into persist_wal_sync_append() - In `persist_wal_sync_append()`: after local write + fsync for `WAL_JOB_SUBMIT`, `WAL_JOB_START`, `WAL_JOB_FINISH`, `WAL_QUEUE_CHANGE` → call `repl_broadcast(c, OP_walAck, payload, len)` + `repl_wait_majority_ack(c, commit_timeout_ms, OP_walAck)` → on quorum advance `committed_lsn` - `WAL_HOST_STATE`, `WAL_CHECKPOINT_REF` bypass to `persist_wal_async()` (unchanged) - `make test` passes; integration test verifies committed WAL entries survive failover diff --git a/src/controller/persist.c b/src/controller/persist.c index bacedd1..a057bb9 100644 --- a/src/controller/persist.c +++ b/src/controller/persist.c @@ -1005,21 +1005,34 @@ int persist_wal_sync_append(persist_t* p, uint32_t type, return rc; } - /* For sync replication (WAL_JOB_SUBMIT): wait for at least one standby ACK. + /* Majority-commit replication (Req 5.1–5.3). + * After local fsync via persist_wal_async, wait for majority quorum ACK. + * On quorum: advance committed_lsn. On no standbys or timeout: degraded. * We use current_lsn - 1 because persist_wal_async already incremented it. */ uint64_t written_lsn; pthread_mutex_lock(&p->lock); written_lsn = p->current_lsn - 1; pthread_mutex_unlock(&p->lock); - int sync_err = repl_wait_ack(&p->repl, written_lsn, - p->config.sync_timeout_ms); - if (sync_err < 0) - CTRL_METRIC_INC(ctrl_repl_degraded_count); + /* No standbys registered → degraded mode immediately (no quorum to wait for) */ + mylock_lock(&p->repl.lock); + int has_standbys = (p->repl.nstandbys > 0); + mylock_unlock(&p->repl.lock); + + int sync_err; + if (!has_standbys) { + sync_err = -ENODEV; + } else { + sync_err = repl_wait_majority_ack(&p->repl, p->config.sync_timeout_ms, + (uint8_t)OPCode_walAck); + if (sync_err == 0) + atomic_store_explicit(&p->committed_lsn, written_lsn, memory_order_release); + else + CTRL_METRIC_INC(ctrl_repl_degraded_count); + } if (sync_rc_out) *sync_rc_out = sync_err; - /* -ETIMEDOUT = degraded mode (local-only durability); -ENODEV = no standbys. - * Both are acceptable — primary continues operating. */ + /* sync_err < 0 = degraded mode (local-only durability); continue operating. */ return 0; }