feat(ha-term-safety): retire term-0 repl_broadcast live send; carry leader term on replication (task 3.4)

Closes bug-990: the live replicator repl_broadcast wire-encoded term=0
(entry.term was never set), so standbys persisted hdr.term=0 and took the
legacy direct-apply path instead of the library (term,lsn) intake. Now the
live broadcast and catch-up streamer carry the leader's real term + AE prev
tuple; ha_apply_wal_catchup_entry routes every term-carrying entry through
raft_on_append_entries_request -> on_persist_entry (leader term stamped),
retiring the term-0 direct-apply. Standbys now persist the leader's real term
and reconcile by (term,lsn); the live commit chain is end-to-end.

848/849/850 + persist-before-ACK + commit-barrier + bug-960 preserved.
Proven at integration level; the definitive live STRICT_TERM failover run is
env-blocked here (bug-924 overlayfs) and recommended on a real fs.
This commit is contained in:
Rongsong Shen 2026-06-22 15:45:14 +00:00
parent 87242aa8e9
commit efe621c124
11 changed files with 307 additions and 45 deletions

View file

@ -2055,6 +2055,16 @@ int ha_stream_wal_to_standby(endpoint_t *ep, const char *wal_path,
int batch_n = 0;
size_t batch_bytes = 0;
/* ha-term-safety task 3.4: track the previous streamed entry's
* (lsn, term) so each catch-up entry carries the AppendEntries prev tuple
* (prev_log_index, prev_log_term) + its own term. The standby intake routes
* every term-carrying sub-entry through raft_on_append_entries_request so it
* persists the LEADER's term (not a local substitute). Entries are streamed
* in strict ascending LSN order, so the prev tuple of entry N is exactly the
* (lsn, term) of the entry emitted just before it (0/0 before the first). */
uint64_t prev_streamed_lsn = 0;
uint64_t prev_streamed_term = 0;
for (int si = 0; si < nseg; si++) {
/* Skip segments whose entire range is at or below standby_lsn */
if (si + 1 < nseg && seg_lsns[si + 1] <= standby_lsn)
@ -2091,6 +2101,10 @@ int ha_stream_wal_to_standby(endpoint_t *ep, const char *wal_path,
batch_n = 0;
batch_bytes = 0;
}
/* Advance the prev tracker even on the OOM-skip so a later
* entry's prev tuple stays correct. */
prev_streamed_lsn = hdr.lsn;
prev_streamed_term = hdr.term;
off += (uint32_t)(sizeof(hdr) + hdr.payload_len);
continue;
}
@ -2099,6 +2113,15 @@ int ha_stream_wal_to_standby(endpoint_t *ep, const char *wal_path,
entry->n_entries = 0;
entry->entries = NULL;
entry->payload = NULL;
/* ha-term-safety task 3.4: carry the leader's term + prev tuple
* so the standby intake routes this through the library and
* persists the LEADER's term (R1.3/R1.4). prev_streamed_* holds
* the (lsn,term) of the immediately preceding WAL entry (0/0
* before the first), which IS the predecessor in contiguous LSN
* order. */
entry->term = hdr.term;
entry->prev_log_index = prev_streamed_lsn;
entry->prev_log_term = prev_streamed_term;
if (hdr.payload_len > 0) {
uint8_t *pbuf = malloc(hdr.payload_len);
@ -2130,6 +2153,13 @@ int ha_stream_wal_to_standby(endpoint_t *ep, const char *wal_path,
batch_bytes = 0;
}
}
/* Advance the prev tracker for EVERY scanned entry — including those
* at/below standby_lsn that are not sent — so the first streamed
* entry's prev tuple references the entry the standby already holds
* (prev_log_index == standby_lsn). (Skips on the OOM path above set
* it inline before continuing.) */
prev_streamed_lsn = hdr.lsn;
prev_streamed_term = hdr.term;
off += (uint32_t)(sizeof(hdr) + hdr.payload_len);
}
close(fd);