forked from lei.chen/myxce
feat(config-upgrade-snapshot): materialize upgrade snapshot on config-marker apply
Adds an explicit case WAL_CONFIG_MARKER to apply_committed_entry, replacing its no-op treatment, that forces a checkpoint at exactly the marker's own (term, lsn) via two new helpers: persist_checkpoint_write_locked(p, checkpoint_lsn) -- the checkpoint core (pin lsn, scan config ref, write_snapshot, trim, rotate) taking an explicit LSN and never acquiring apply_pump_lock itself persist_checkpoint_marker_triggered(p, lsn) -- shares the existing checkpoint_in_progress CAS guard, calls the above with the marker's own lsn This fixes two hazards design.md's "call persist_checkpoint() directly" text didn't account for: (1) apply_committed_entry's live-pump caller (persist_apply_pump_run, used by both primary and standby online-apply) already holds apply_pump_lock across the dispatch call -- a direct persist_checkpoint() call would self-deadlock on that mutex; (2) last_applied_lsn/replay position only advance AFTER apply_committed_entry returns at both call sites, so pinning checkpoint_lsn from last_applied_lsn inside the marker case would label the checkpoint with the previous entry's LSN, not the marker's own -- fixed by threading the marker's own lsn parameter through explicitly. A -EBUSY outcome (a concurrent periodic checkpoint mid-flight) is logged at TRACE and treated as expected/self-resolving, never retried. The new case has no effect on entity interpretation/re-homing (Req 1.8). New test_upgrade_snapshot_apply.c drives WAL_CONFIG_MARKER through the REAL production call paths (persist_apply_pump_run and boot replay via persist_recover), not a bare apply_committed_entry() call, so the fix is actually proven against the lock-holding context rather than assumed. Also fixes a genuine regression in test_config_repl.c surfaced by WAL_CONFIG_MARKER apply having a real side effect for the first time (bug-1466): an fsync-worker-thread race (t42_wait_applied polling helper, mirrors test_apply_pump.c's tap_wait_applied) and one test call site trusting an unreliable raft-derived LSN. Surfaces (but does not fix, out of this spec's boundary) a pre-existing ha-term-safety/raft-log-replication gap: persist_write_checkpoint_marker bypasses ha_primary_log_append, breaking its documented current_lsn==log_len+1 invariant once a checkpoint interleaves with primary appends -- log_len feeds real RequestVote freshness comparisons, not advisory-only as first assumed; severity corrected in bug-1466. Task 5 of 19. Requirements: 1.1, 1.5, 1.8, 4.4, 5.2, 5.3
This commit is contained in:
parent
00cd26a927
commit
1da26b043e
7 changed files with 938 additions and 23 deletions
|
|
@ -628,6 +628,36 @@ static void t42_teardown(cluster_t *c)
|
|||
free(c);
|
||||
}
|
||||
|
||||
/* config-upgrade-snapshot task 5: committing a WAL_CONFIG_MARKER now triggers
|
||||
* an automatic forced checkpoint the instant the marker is APPLIED (not just
|
||||
* committed) via the live apply pump -- persist_apply_pump_kick's dispatch is
|
||||
* a CAS race between whichever thread reaches it first, which can be either
|
||||
* this test's own thread (inside commit_submit's synchronous
|
||||
* commit_resolve_pending call) or persist_start's independent fsync_worker
|
||||
* thread (via commit_on_durable_advance) once durable_lsn advances. When the
|
||||
* fsync thread wins that race, commit_submit/commit_engine's own WAIT mode
|
||||
* returns having waited only for COMMIT, not for this now-real APPLY+
|
||||
* CHECKPOINT side effect to finish on that other thread -- a fixture's own
|
||||
* subsequent LSN bookkeeping or explicit persist_checkpoint() call can then
|
||||
* genuinely race the still-in-flight background checkpoint (observed as a
|
||||
* flaky -EBUSY from persist_checkpoint() or an unexpectedly-advanced
|
||||
* committed_lsn, depending on which check loses the race). This was always a
|
||||
* latent, unsynchronized race in this fixture (the same one
|
||||
* test_apply_pump.c's tap_wait_applied already documents and exists to
|
||||
* close) -- applying a marker used to be a no-op, so racing on it was
|
||||
* invisible; task 5 gives it a real, observable side effect for the first
|
||||
* time. Poll persist_last_applied_lsn() up to a bounded deadline (mirrors
|
||||
* tap_wait_applied exactly) before any LSN-sensitive step that follows a
|
||||
* marker commit, closing the race rather than leaving it flaky. */
|
||||
static void t42_wait_applied(persist_t *p, uint64_t lsn, uint32_t deadline_ms)
|
||||
{
|
||||
for (uint32_t waited = 0; waited < deadline_ms; waited += 5) {
|
||||
if (persist_last_applied_lsn(p) >= lsn)
|
||||
return;
|
||||
usleep(5000);
|
||||
}
|
||||
}
|
||||
|
||||
/* Bring the node up as the raft LEADER at `term` (so raft_log_append is enabled
|
||||
* — it returns -EPERM off the leader; see make_leader in the adapter suite). */
|
||||
static void t42_make_leader(cluster_t *c, uint64_t term, int n_peers)
|
||||
|
|
@ -925,6 +955,14 @@ static void test_committed_marker_survives_checkpoint_trim(void)
|
|||
atomic_load_explicit(&c->persist.committed_lsn, memory_order_acquire);
|
||||
TEST_ASSERT_TRUE(committed >= 1);
|
||||
|
||||
/* config-upgrade-snapshot task 5: wait for the marker to be APPLIED (see
|
||||
* t42_wait_applied's doc comment) before this test's own explicit
|
||||
* persist_checkpoint() call below -- otherwise it can race the marker's
|
||||
* own now-automatic checkpoint (possibly still running on the
|
||||
* fsync_worker thread) and see a spurious -EBUSY from the shared
|
||||
* checkpoint_in_progress guard. */
|
||||
t42_wait_applied(&c->persist, /*marker LSN*/1, 2000);
|
||||
|
||||
/* Sanity: before any trim, the live-WAL scan finds the marker. */
|
||||
uint64_t got_tag = 0;
|
||||
uint8_t got_sha[32];
|
||||
|
|
@ -1025,7 +1063,10 @@ static void t43_free_myconf(cluster_t *c)
|
|||
}
|
||||
|
||||
/* Commit a WAL_CONFIG_MARKER for `sha`/`tag` through the real commit engine so
|
||||
* config_committed_marker returns it (the committed-prefix authority 4.3 reads). */
|
||||
* config_committed_marker returns it (the committed-prefix authority 4.3 reads).
|
||||
* Also waits for the marker to be APPLIED (see t42_wait_applied's doc comment)
|
||||
* so callers' own subsequent LSN bookkeeping never races this marker's own
|
||||
* task-5 automatic checkpoint, wherever it happens to run. */
|
||||
static void t43_commit_marker(cluster_t *c, uint64_t tag, const uint8_t sha[32])
|
||||
{
|
||||
uint64_t mlsn = 0;
|
||||
|
|
@ -1035,6 +1076,7 @@ static void t43_commit_marker(cluster_t *c, uint64_t tag, const uint8_t sha[32])
|
|||
commit_submit(&c->commit, mlsn, COMMIT_MODE_WAIT, 1000));
|
||||
TEST_ASSERT_TRUE(
|
||||
atomic_load_explicit(&c->persist.committed_lsn, memory_order_acquire) >= mlsn);
|
||||
t42_wait_applied(&c->persist, mlsn, 2000);
|
||||
}
|
||||
|
||||
/* (4.3-a) MATCH activates: a committed marker whose locally-held bytes' sha256
|
||||
|
|
@ -1423,7 +1465,29 @@ static uint64_t t43_append_marker_uncommitted(cluster_t *c, uint64_t tag,
|
|||
uint64_t mlsn = 0;
|
||||
TEST_ASSERT_EQUAL_INT(0, config_append_marker(c, tag, sha, &mlsn));
|
||||
TEST_ASSERT_EQUAL_INT(0, persist_flush(&c->persist));
|
||||
return mlsn;
|
||||
|
||||
/* config-upgrade-snapshot task 5 finding: config_append_marker's returned
|
||||
* lsn (via ha_primary_log_append -> raft_log_append's own s->log_len + 1)
|
||||
* is only reliable when EVERY prior WAL entry on this node went through
|
||||
* that same raft-tracked append path. A marker-triggered checkpoint (this
|
||||
* spec) writes its own local-only, never-replicated WAL_CHECKPOINT entry
|
||||
* directly (persist_write_checkpoint_marker), which advances
|
||||
* persist_t's current_lsn WITHOUT a corresponding raft_log_append call --
|
||||
* this was already possible before this spec (the periodic WAL-threshold
|
||||
* checkpoint trigger does the same), but this spec's marker-triggered
|
||||
* checkpoint makes it the COMMON case for any second marker appended
|
||||
* after an earlier one's checkpoint has already run, exactly this
|
||||
* fixture's own scenario. The returned mlsn can then under-count the
|
||||
* entry's TRUE physical position. This does not corrupt commit/durability
|
||||
* tracking (commit_advance_locked's majority_candidate_locked is driven
|
||||
* entirely by persist_t's own durable_lsn/current_lsn, never by
|
||||
* s->log_len -- see commit.c) but it DOES make the returned value
|
||||
* unreliable for this test's own strict numeric-ordering comparisons.
|
||||
* Read the TRUE physical lsn directly instead, exactly as production
|
||||
* code that needs precise positioning (e.g. persist.c's apply seam,
|
||||
* which receives lsn from the WAL entry's own on-disk header, never from
|
||||
* this return value) already effectively does. */
|
||||
return c->persist.current_lsn - 1;
|
||||
}
|
||||
|
||||
/* (4.3-D / R7.9) COMMIT-ADVANCE RETRY clears a push-before-commit fence: the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue