feat(ha-term-safety): WAL term seam + recovery read-back + legacy-0 rule (task 1.3)
persist_set_term sets the live election term so appends stamp hdr.term (fixes the G1 term-0 stamping); replay_wal builds an in-memory per-LSN term index (freed in persist_fini, no on-disk change); persist_entry_term reads it back (0=absent). Legacy-0 -> WAL_LEGACY_TERM=1 at read-back only (raw 0 retained so present-legacy-0 is distinguishable from absent). 40-byte wal_entry_hdr_t preserved (value fix, R1.7). 5 new tests (test_term_safety_adapter.c); reviewer APPROVED. Feeds task 2.2. All controller test groups report 0 failures; the meson full-suite timeout is the bug-924 overlayfs /tmp fdatasync artifact, not this task.
This commit is contained in:
parent
a95d57f733
commit
d540ebd183
6 changed files with 420 additions and 1 deletions
|
|
@ -171,6 +171,23 @@ struct persist_s {
|
|||
uint64_t total_wal_bytes;
|
||||
char seg_path[4096];
|
||||
|
||||
/* Per-LSN recovered/live term index (ha-term-safety task 1.3, R1.5).
|
||||
* IN-MEMORY ONLY — no on-disk layout change (R1.7). Records each entry's
|
||||
* hdr.term keyed by LSN so the term can be read back after recovery and
|
||||
* fed to the reconcile/promotion logic (on_get_entry_term, task 2.2).
|
||||
*
|
||||
* term_index[lsn - 1] holds the RAW recorded term for a 1-based LSN (LSN 0
|
||||
* is reserved and never indexed). term_index_count is the highest LSN
|
||||
* recorded (== number of contiguous slots in use), used as the presence
|
||||
* watermark so persist_entry_term distinguishes an ABSENT LSN (returns 0)
|
||||
* from a PRESENT legacy-0 entry (mapped to the lowest-known term). Grown on
|
||||
* demand by record_entry_term during replay_wal and live appends; freed in
|
||||
* persist_fini. Guarded by p->lock (written under the append lock and the
|
||||
* single-threaded recovery path; read by accessors that take p->lock). */
|
||||
uint64_t* term_index; /* term_index[i] = term of LSN (i+1) */
|
||||
uint64_t term_index_count; /* highest recorded LSN (slots in use) */
|
||||
uint64_t term_index_cap; /* allocated slot capacity */
|
||||
|
||||
/* Recovery-integrity verdict (ha-commit-durability task 3.1, Req 2.1).
|
||||
* Set by persist_recover/replay_wal at startup: RECOVER_CLEAN when the
|
||||
* local WAL forms a valid CRC chain to a clean entry boundary (incl. an
|
||||
|
|
@ -271,6 +288,45 @@ typedef struct persist_s persist_t;
|
|||
* Derives wal_path and snap_path as <workdir>/<name>/data/{wal,snap}. */
|
||||
void persist_set_workdir(persist_t* p, const char* workdir, const char* name);
|
||||
|
||||
/* ============================================================
|
||||
* WAL term adapter (ha-term-safety task 1.3, Req 1.1/1.5/1.6/1.7/1.8)
|
||||
*
|
||||
* Today every WAL append stamps hdr.term = p->term, but p->term is never
|
||||
* assigned, so all entries carry term 0 (the G1 bug). The on-disk
|
||||
* wal_entry_hdr_t already has a 40-byte `term` field; this is a VALUE fix, NOT
|
||||
* a layout change (R1.7).
|
||||
* ============================================================ */
|
||||
|
||||
/* Legacy-zero-term rule (R1.8) — REVIEW ISSUE 2, made precise:
|
||||
*
|
||||
* A WAL entry that predates this feature carries hdr.term == 0. For (term,lsn)
|
||||
* matching it must map to a SINGLE, well-defined "lowest-known term" so that
|
||||
* (a) recovery never fails on legacy-0 entries, and
|
||||
* (b) a legacy-0 entry is never spuriously treated as divergent from another
|
||||
* legacy-0 entry at the same LSN.
|
||||
* The rule: legacy-0 -> WAL_LEGACY_TERM (1, the lowest real Raft term — Raft
|
||||
* terms start at 1; term 0 is the pre-election "no term" placeholder). It is
|
||||
* applied UNIFORMLY at read-back (persist_entry_term), so every legacy-0 entry
|
||||
* answers the same term 1 and matches another legacy-0 entry at the same LSN.
|
||||
* The raw 0 is still stored in the index (so "present legacy-0" stays
|
||||
* distinguishable from "absent"); only the read-back value is mapped. */
|
||||
#define WAL_LEGACY_TERM 1u
|
||||
|
||||
/* Set the controller's live election term so subsequent WAL appends stamp it
|
||||
* into hdr.term (R1.1/R1.6). Called from the append callback (on_persist_entry,
|
||||
* task 2.2) and on term adoption, so no append records a zero placeholder while
|
||||
* a valid term is in effect. No on-disk layout change; value fix only (R1.7).
|
||||
* Safe on a NULL p (no-op). */
|
||||
void persist_set_term(persist_t* p, uint64_t term);
|
||||
|
||||
/* Recovered/live per-entry term for a 1-based LSN (lsn == Raft index), feeding
|
||||
* the task-2.2 on_get_entry_term callback and the library's init restore
|
||||
* (R1.5). Returns 0 when the LSN is ABSENT (never recorded). A PRESENT entry
|
||||
* that carries a legacy zero term is mapped to WAL_LEGACY_TERM (R1.8) so it is
|
||||
* never confused with an absent LSN and matches other legacy-0 entries.
|
||||
* Safe on a NULL p (returns 0). */
|
||||
uint64_t persist_entry_term(const persist_t* p, uint64_t lsn);
|
||||
|
||||
/* Lifecycle */
|
||||
int persist_init(persist_t* p, persist_config_t* config, cluster_t* c);
|
||||
int persist_start(persist_t* p);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue