feat(ha-commit-durability): publish durable floor from recovered WAL tip (task 3.4)

This commit is contained in:
Rongsong Shen 2026-06-18 08:24:46 +00:00
parent e3fc42c11e
commit c1c35ab1b3
4 changed files with 271 additions and 1 deletions

View file

@ -804,6 +804,16 @@ uint64_t persist_durable_lsn(persist_t* p)
return atomic_load_explicit(&p->durable_lsn, memory_order_acquire);
}
uint64_t persist_durable_floor(persist_t* p)
{
/* The durable freshness floor (durable last-log index) IS the durable
* watermark: persist_recover re-derives it from the recovered WAL tip
* (ha-commit-durability task 3.4), and the flusher advances it on every
* successful fdatasync — so a thin wrapper avoids a redundant field. NULL
* guard keeps the accessor safe for the election consumers (task 4.2). */
return p ? atomic_load_explicit(&p->durable_lsn, memory_order_acquire) : 0;
}
void persist_set_durable_cb(persist_t* p, void (*fn)(void* ctx), void* ctx)
{
if (!p)
@ -1626,6 +1636,31 @@ int persist_recover(persist_t* p)
"corrupt (CRC break / mismatch / short read); member marked "
"recovery-truncated (non-promotable until caught up)");
/* --- Step 3b: re-derive the durable floor from the recovered WAL tip
* (ha-commit-durability task 3.4, Req 2.3) ---
*
* Publish the member's durable floor (the durable last-log index consumed
* by the Raft freshness gate, task 4.2) as the highest INTACT replayed LSN.
* replay_wal advanced current_lsn to one past the last intact entry — incl.
* stopping at a corruption boundary on TRUNCATED — so current_lsn - 1 is
* exactly that intact WAL tip, and a snapshot-sentinel load leaves it at the
* sentinel/replayed tip. We store it into durable_lsn because the recovered
* on-disk entries are GENUINELY durable (each was CRC-validated and read
* from stable storage by replay_wal), so the freshness advertisement is now
* backed by durable state rather than a volatile in-memory position.
*
* Crux (Req 2.3 — a truncated member cannot over-advertise): this is an
* ASSIGN to the recovered tip, NOT a max() with whatever durable_lsn held.
* A fresh recover starts from durable_lsn=0 (persist_init), so on a
* truncating restart the floor lands at the LOWER intact tip — never a
* stale higher in-memory value and never the commit position
* (committed_lsn). The recovered tip is, by construction, <= current_lsn-1,
* preserving the durable_lsn <= current_lsn-1 invariant. The post-election
* safe-commit point continues to come from voter replies, not this floor
* (CLAUDE.md). */
uint64_t recovered_tip = (p->current_lsn > 0) ? p->current_lsn - 1 : 0;
atomic_store_explicit(&p->durable_lsn, recovered_tip, memory_order_release);
/* --- Step 4: retention pass (TTL check) — no-op in P0 (no entities) --- */
return 0;