feat(ha-term-safety): drive raft_log_append on primary append path (task 3.1)
This commit is contained in:
parent
3dd8493fff
commit
6a519a928b
5 changed files with 299 additions and 11 deletions
|
|
@ -1866,6 +1866,15 @@ void ha_cb_commit_advanced_for_test(ha_state_t *ha, uint64_t commit_index)
|
|||
{
|
||||
s_real_cbs.on_commit_advanced(ha, commit_index);
|
||||
}
|
||||
|
||||
/* ha-term-safety task 3.1: drive the (static) own-term gate arm — the WAL_NOOP
|
||||
* append now routed through ha_primary_log_append — so a unit test can assert
|
||||
* the promotion no-op records the live term via the library. Exposed only under
|
||||
* XCE_TESTING. */
|
||||
void ha_arm_own_term_gate_for_test(cluster_t *c)
|
||||
{
|
||||
ha_arm_own_term_gate(c);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ha_check_heartbeat_nocluster() / ha_watchdog_thread() / ha_start_watchdog() /
|
||||
|
|
@ -2645,6 +2654,89 @@ static void ha_register_persist_hooks(cluster_t *c)
|
|||
c->ha.hooks_registered = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ha_primary_log_append (ha-term-safety task 3.1, R1.1/R1.2/R2.1): the SINGLE
|
||||
* primary append entry point. Routes a local primary append (job-state entry or
|
||||
* the own-term promotion no-op) through the raft library's raft_log_append, so
|
||||
* the LIBRARY stamps the live election term (s->term) and assigns the LSN
|
||||
* (s->log_len+1), then drives on_persist_entry — which (primary role) performs
|
||||
* the actual WAL write via persist_wal_async. This makes the library index the
|
||||
* authority for both the per-entry term (R1) and the LSN, retiring the
|
||||
* hand-rolled bypass that assigned the LSN directly through persist_wal_async.
|
||||
*
|
||||
* Co-tracking invariant (closes the task 2.1 deferral): because raft_log_append
|
||||
* is the only primary append path, every primary append advances BOTH the
|
||||
* library index (s->log_len) and the persist write cursor (p->current_lsn) by
|
||||
* exactly one, in lockstep — so p->current_lsn == s->log_len + 1 always holds and
|
||||
* the LSN the library assigns equals the LSN on_persist_entry writes. raft_init
|
||||
* (task 2.5) seeds s->log_len from the recovered durable floor, which equals
|
||||
* p->current_lsn - 1 at boot, establishing the invariant before the first append.
|
||||
*
|
||||
* Persist-before-act: raft_log_append calls on_persist_entry BEFORE advancing
|
||||
* s->log_len and propagates a non-zero persist rc without mutating the log (no
|
||||
* phantom entry) — preserving persist-before-ACK and the bug-850 contiguity
|
||||
* guard beneath the WAL write.
|
||||
*
|
||||
* Legacy fallback (non-raft road): when the raft library is not in RAFT_LEADER
|
||||
* state (raft_log_append returns -EPERM) — the init-time non-raft promotion road
|
||||
* (ha_promote_to_primary) and engine-less unit-test contexts — the library is
|
||||
* not tracking the log, so there is no co-tracking hazard. We append locally
|
||||
* through the legacy persist_wal_async path (which still records the live term
|
||||
* via the persist_set_term seam set on the prior promotion) so those roads keep
|
||||
* working unchanged. Once raft drives leadership in production, the -EPERM
|
||||
* branch is never taken and the library is the sole LSN/term source.
|
||||
*
|
||||
* out_lsn (optional) receives the assigned 1-based LSN on success.
|
||||
* Returns 0 on success; propagates a non-zero persist rc on append failure.
|
||||
*/
|
||||
int ha_primary_log_append(cluster_t *c, uint32_t type,
|
||||
const void *payload, uint32_t len, uint64_t *out_lsn)
|
||||
{
|
||||
if (!c)
|
||||
return -EINVAL;
|
||||
|
||||
/* Pack the in-flight adapter envelope [raft_entry_blob_t{type,len}][payload]
|
||||
* — the SAME format on_persist_entry (ha_cb_persist_entry_stub) unpacks, so
|
||||
* the encoder and decoder share the single wal_types.h definition. */
|
||||
size_t blob_len = sizeof(raft_entry_blob_t) + len;
|
||||
uint8_t stackbuf[256];
|
||||
uint8_t *blob = (blob_len <= sizeof(stackbuf)) ? stackbuf : malloc(blob_len);
|
||||
if (!blob)
|
||||
return -ENOMEM;
|
||||
|
||||
raft_entry_blob_t *bh = (raft_entry_blob_t *)blob;
|
||||
bh->type = type;
|
||||
bh->payload_len = len;
|
||||
if (payload && len > 0)
|
||||
memcpy(blob + sizeof(*bh), payload, len);
|
||||
|
||||
uint64_t idx = 0;
|
||||
int rc = raft_log_append(&c->ha.raft, blob, blob_len, &idx);
|
||||
|
||||
if (rc == -EPERM) {
|
||||
/* Non-raft road: the library is not LEADER and is not tracking the log.
|
||||
* Append locally through the legacy path (no library index to co-track).
|
||||
* Stamp the live election term (ha->term) so the entry still records the
|
||||
* leader's term (R1.1) even off the raft road — matching on_persist_entry's
|
||||
* persist_set_term on the raft path. */
|
||||
persist_set_term(&c->persist,
|
||||
atomic_load_explicit(&c->ha.term, memory_order_acquire));
|
||||
rc = persist_wal_async(&c->persist, type, payload, len);
|
||||
if (rc == 0) {
|
||||
pthread_mutex_lock(&c->persist.lock);
|
||||
idx = c->persist.current_lsn - 1; /* the LSN just assigned */
|
||||
pthread_mutex_unlock(&c->persist.lock);
|
||||
}
|
||||
}
|
||||
|
||||
if (blob != stackbuf)
|
||||
free(blob);
|
||||
|
||||
if (rc == 0 && out_lsn)
|
||||
*out_lsn = idx;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* ha_arm_own_term_gate: append the own-term WAL_NOOP and arm the commit
|
||||
* engine's writable_lsn gate (ha-single-leader-role task 2.2, Req 1.4/2.1/2.2).
|
||||
|
|
@ -2670,7 +2762,13 @@ static void ha_register_persist_hooks(cluster_t *c)
|
|||
*/
|
||||
static void ha_arm_own_term_gate(cluster_t *c)
|
||||
{
|
||||
int rc = persist_wal_async(&c->persist, WAL_NOOP, NULL, 0);
|
||||
/* ha-term-safety task 3.1: route the own-term promotion no-op through the
|
||||
* library (ha_primary_log_append → raft_log_append) so the WAL_NOOP records
|
||||
* the newly established term (R1.2) and the library assigns its LSN. On the
|
||||
* non-raft promotion road this falls back to the legacy append; either way
|
||||
* the returned LSN is the no-op's WAL LSN. */
|
||||
uint64_t noop_lsn = 0;
|
||||
int rc = ha_primary_log_append(c, WAL_NOOP, NULL, 0, &noop_lsn);
|
||||
if (rc < 0) {
|
||||
ERROR("HA: own-term WAL_NOOP append failed: %d", rc);
|
||||
WARN("HA: own-term gate left UNSET (degraded-but-safe); first "
|
||||
|
|
@ -2678,13 +2776,6 @@ static void ha_arm_own_term_gate(cluster_t *c)
|
|||
return;
|
||||
}
|
||||
|
||||
/* Derive the no-op's LSN: the append post-incremented current_lsn, so the
|
||||
* entry just written took current_lsn - 1 (read under p->lock). */
|
||||
uint64_t noop_lsn;
|
||||
pthread_mutex_lock(&c->persist.lock);
|
||||
noop_lsn = c->persist.current_lsn - 1;
|
||||
pthread_mutex_unlock(&c->persist.lock);
|
||||
|
||||
/* Observability-only bookkeeping (lock-free, non-blocking, records
|
||||
* nothing per entry — commit state derives from registry watermarks). */
|
||||
(void)commit_submit(&c->commit, noop_lsn, COMMIT_MODE_RETAIN, 0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue