fix(ha): close permanent catch-up wedge from duplicated replication-target formula (bug-1478)
Two independent call sites duplicated target = max(committed_lsn, last_written): ha.c's heartbeat/stateReport dispatch (repl_ack_decode_and_ dispatch) and config_repl.c's catchup_derive_target (used by the ACK-driven catch-up continuation path). Once config-upgrade-snapshot's marker-triggered checkpoint advances last_written one LSN past an already-committed committed_lsn (the checkpoint's own consumed, never-replicated LSN slot), the unconditional max() permanently wedges every standby's catch-up target at an unreachable value -- the primary re-streams an empty range every heartbeat forever. Extracted the shared, pure ha_compute_repl_target_lsn(committed_lsn, last_written) (ha.h/ha.c), which only falls back to last_written on genuine committed_lsn==0 bootstrap, and pointed both call sites at it so they can never disagree again. Fixing the second call site exposed three pre-existing tests in test_convergence_verification.c that hardcoded the old buggy formula's output as their expected target; updated their fixture values (committed_lsn 4->9, matching appended_lsn) to decouple them from the now-fixed edge case, which already has dedicated pure-function coverage in test_ha_role.c. GREEN: full controller_unit_test suite = 1040 tests, 0 failures, clean across multiple runs. Live-verified via run-ha-soak.sh --config-upgrade-wal-trim (18 jobs, zero job loss, ctrl-1 reconverged 3-of-3 using the preserved floor segment) -- unblocks config-upgrade-snapshot task 8.9.
This commit is contained in:
parent
71223ec712
commit
102e263cce
9 changed files with 850 additions and 20 deletions
|
|
@ -276,17 +276,22 @@ static int cv_standby_in_catch_up(repl_ep_t ep)
|
|||
|
||||
/* ── Tests ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* 5.1: a walAck with acked_lsn < target = max(committed_lsn, last_written)
|
||||
* schedules EXACTLY ONE continuation that streams [acked_lsn+1, target] without
|
||||
* any intervening heartbeat/stateReport dispatch.
|
||||
/* 5.1: a walAck with acked_lsn < target = ha_compute_repl_target_lsn(
|
||||
* committed_lsn, last_written) schedules EXACTLY ONE continuation that
|
||||
* streams [acked_lsn+1, target] without any intervening heartbeat/
|
||||
* stateReport dispatch.
|
||||
* RED: the walAck continuation scaffold schedules nothing → no task, the stream
|
||||
* hook is never called. */
|
||||
static void test_walack_behind_target_schedules_one_continuation(void)
|
||||
{
|
||||
/* appended_lsn=9 → last_written=9; committed_lsn=4 → target=max(4,9)=9. */
|
||||
/* appended_lsn=9 → last_written=9; committed_lsn=9 → target=9. Kept equal
|
||||
* to last_written (rather than a lower value like the historical 4) so
|
||||
* this test exercises catch-up continuation scheduling only — the
|
||||
* committed_lsn-vs-last_written edge case itself (bug-1478) has its own
|
||||
* dedicated pure-function coverage in test_ha_role.c. */
|
||||
cv_setup("behind", /*n_peers=*/3, /*n_standbys=*/1,
|
||||
/*appended_lsn=*/9, /*n_vcpus=*/8);
|
||||
atomic_store(&g_cv.persist.committed_lsn, 4);
|
||||
atomic_store(&g_cv.persist.committed_lsn, 9);
|
||||
|
||||
const int peer_idx = 0;
|
||||
const uint64_t acked_lsn = 3; /* behind target 9 */
|
||||
|
|
@ -325,8 +330,8 @@ static void test_walack_behind_target_schedules_one_continuation(void)
|
|||
"[acked_lsn+1, target]) (Req 5.1)");
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(target,
|
||||
atomic_load(&g_stream_last_target_lsn),
|
||||
"the continuation must stream up to target = max(committed_lsn, "
|
||||
"last_written) (Req 5.1)");
|
||||
"the continuation must stream up to target = "
|
||||
"ha_compute_repl_target_lsn(committed_lsn, last_written) (Req 5.1)");
|
||||
|
||||
cv_teardown();
|
||||
printf("test_walack_behind_target_schedules_one_continuation: checked\n");
|
||||
|
|
@ -338,9 +343,11 @@ static void test_walack_behind_target_schedules_one_continuation(void)
|
|||
* "exactly one continuation across two ACKs" assertion fails (0 != 1). */
|
||||
static void test_walack_single_flight_no_duplicate_continuation(void)
|
||||
{
|
||||
/* committed_lsn == appended_lsn (target=9 either way) — see the "behind"
|
||||
* test's own note on why these don't use a lower committed_lsn. */
|
||||
cv_setup("single", /*n_peers=*/3, /*n_standbys=*/1,
|
||||
/*appended_lsn=*/9, /*n_vcpus=*/8);
|
||||
atomic_store(&g_cv.persist.committed_lsn, 4);
|
||||
atomic_store(&g_cv.persist.committed_lsn, 9);
|
||||
|
||||
const int peer_idx = 0;
|
||||
|
||||
|
|
@ -382,9 +389,11 @@ static void test_walack_single_flight_no_duplicate_continuation(void)
|
|||
* RED: the scaffold never schedules/clears, so neither continuation appears. */
|
||||
static void test_walack_reschedules_after_continuation_completes(void)
|
||||
{
|
||||
/* committed_lsn == appended_lsn (target=9 either way) — see the "behind"
|
||||
* test's own note on why these don't use a lower committed_lsn. */
|
||||
cv_setup("respawn", /*n_peers=*/3, /*n_standbys=*/1,
|
||||
/*appended_lsn=*/9, /*n_vcpus=*/8);
|
||||
atomic_store(&g_cv.persist.committed_lsn, 4);
|
||||
atomic_store(&g_cv.persist.committed_lsn, 9);
|
||||
|
||||
const int peer_idx = 0;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue