test(config-upgrade-snapshot): prove WAL-trim safety + add fault-injection seam for marker trigger

Confirmed task 5's existing 6 tests already satisfy "exactly once",
"safe under forced retry", and "does not re-home entities"; confirmed
test_snapshot_recv_old_format_version_rejected_fail_loud (task 1)
already covers format-mismatch rejection by proxy through
load_snapshot's two shared callers (network-install, boot replay).

Adds the two genuine gaps: (1)
test_marker_triggered_checkpoint_trim_preserves_standby_mid_catchup_segments
proves trim_wal_segments/repl_trim_lsn's standby-cursor retention
guard (bug-1147) still holds when reached via the new marker-triggered
checkpoint path, not just the old periodic/direct-call path -- guard
itself untouched. (2) persist_marker_checkpoint_fault_hook
(#ifdef XCE_TESTING, NULL default, true no-op in production) gates
whether persist_checkpoint_marker_triggered runs, for task 8.7's later
crash-before-checkpoint-durable injection; minimally proven here.
Task 8.1 of 19.

Requirements: 1.1, 1.5, 1.8, 5.2, 5.3, 5.6, 5.7, 6.3, 7.3, 7.4
This commit is contained in:
Rongsong Shen 2026-07-25 06:33:11 +00:00
parent 41af05402d
commit 4aeae6e282
3 changed files with 291 additions and 2 deletions

View file

@ -3369,6 +3369,33 @@ int apply_wal_entry(persist_t* p, uint32_t type, uint64_t lsn,
* SYNCHRONOUSLY on this exact call stack, from this exact function. */
static _Thread_local bool t_apply_seam_notifying = false;
#ifdef XCE_TESTING
/* config-upgrade-snapshot task 8.1 (Req 6.3): deterministic fault-injection
* seam at the marker-apply-to-checkpoint-durable boundary, for later use by
* a crash/restart regression (task 8.7): "force a controller crash/restart
* between a configuration-change marker's commit and that member's own
* snapshot write becoming durable, and assert materialization resumes
* correctly from the marker on restart." When non-NULL AND it returns
* nonzero for the marker's own (p, lsn), the WAL_CONFIG_MARKER case below
* skips its call into persist_checkpoint_marker_triggered() entirely --
* simulating "the process crashes here, before the checkpoint write becomes
* durable" (a real crash simply stops the process; the checkpoint call
* never runs either way). The skip is deliberately routed through the SAME
* rc!=0 handling an actual checkpoint failure already takes below (not a
* new branch): last_applied_lsn is not advanced past this marker, so the
* next kick/retry -- or, after a genuine restart, boot replay's independent
* call into this same case (already proven idempotent by
* test_marker_apply_retry_is_idempotent) -- resumes materialization from
* the marker fresh, exactly matching Requirement 5.2's crash-safety
* contract. NULL in production -- no behaviour change. Mirrors
* ha_stream_body_read_fault_hook's shape (config_repl.c) and this same
* spec's own xce_upgrade_marker_committed_hook placement convention
* (defined here at file scope, declared extern in whichever test file
* needs it -- never in a header). */
int (*persist_marker_checkpoint_fault_hook)(persist_t *p,
uint64_t marker_lsn) = NULL;
#endif
int apply_committed_entry(persist_t* p, uint32_t wal_type, uint64_t lsn,
const void* payload, uint32_t len)
{
@ -3780,7 +3807,20 @@ int apply_committed_entry(persist_t* p, uint32_t wal_type, uint64_t lsn,
* checkpoint. `lsn` (this function's own parameter) is
* already the marker's exact position, so it is passed
* through explicitly instead of re-derived. */
int rc = persist_checkpoint_marker_triggered(p, lsn);
int rc;
#ifdef XCE_TESTING
if (persist_marker_checkpoint_fault_hook != NULL &&
persist_marker_checkpoint_fault_hook(p, lsn)) {
/* Simulated crash (task 8.1, Req 6.3): skip the checkpoint call
* entirely and fall into the SAME genuine-failure handling below
* a real checkpoint I/O error already takes -- see the fault
* hook's own doc comment above for why that is the correct,
* not merely convenient, simulation of "the process never got
* to checkpoint". */
rc = -ECANCELED;
} else
#endif
rc = persist_checkpoint_marker_triggered(p, lsn);
if (rc == -EBUSY) {
/* Expected, self-resolving (Req 4.4/5.3): a concurrent
* WAL-threshold-triggered or earlier marker-triggered