feat(ha-commit-durability): add XCE_TESTING durable-LSN-on-disk verification seam (task 1.2)
This commit is contained in:
parent
1ca2d640cb
commit
f90395282c
6 changed files with 357 additions and 1 deletions
|
|
@ -536,6 +536,140 @@ int persist_flush(persist_t* p)
|
|||
return rc;
|
||||
}
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Durability-verification test seam (ha-commit-durability Req 3.2/3.3)*
|
||||
* *
|
||||
* Re-read + CRC-validate the WAL segments from stable storage and *
|
||||
* return the highest fully-persisted LSN — independent of the *
|
||||
* in-memory current_lsn / committed_lsn watermarks. Mirrors the *
|
||||
* segment-scan + CRC-chain validation of replay_wal(), but is strictly *
|
||||
* read-only: it applies no entries, fires no hooks, and mutates no *
|
||||
* persist_t state, so it cannot change the externally observable *
|
||||
* acknowledgment semantics (Req 3.3). *
|
||||
* *
|
||||
* Stops at the first corruption / short read / pre-allocated zero *
|
||||
* region (exactly like recovery), so only entries genuinely on stable *
|
||||
* storage are counted. Returns the highest such LSN, or 0 when nothing *
|
||||
* is durable. *
|
||||
* ------------------------------------------------------------------ */
|
||||
uint64_t persist_test_durable_lsn_on_disk(persist_t* p)
|
||||
{
|
||||
if (!p)
|
||||
return 0;
|
||||
|
||||
DIR* d = opendir(p->wal_path);
|
||||
if (!d)
|
||||
return 0; /* no WAL dir → nothing durable */
|
||||
|
||||
/* Collect segment start LSNs (same bound as replay_wal). */
|
||||
uint64_t seg_lsns[4096];
|
||||
int nseg = 0;
|
||||
|
||||
struct dirent* e;
|
||||
while ((e = readdir(d)) != NULL &&
|
||||
nseg < (int)(sizeof(seg_lsns) / sizeof(seg_lsns[0]))) {
|
||||
if (strncmp(e->d_name, "seg-", 4) != 0)
|
||||
continue;
|
||||
uint64_t seg_start = 0;
|
||||
if (sscanf(e->d_name, "seg-%" SCNx64 ".wal", &seg_start) != 1)
|
||||
continue;
|
||||
seg_lsns[nseg++] = seg_start;
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
if (nseg == 0)
|
||||
return 0;
|
||||
|
||||
/* Insertion sort by start LSN (segment count is tiny). */
|
||||
for (int i = 1; i < nseg; i++) {
|
||||
uint64_t key = seg_lsns[i];
|
||||
int j = i - 1;
|
||||
while (j >= 0 && seg_lsns[j] > key) {
|
||||
seg_lsns[j + 1] = seg_lsns[j];
|
||||
j--;
|
||||
}
|
||||
seg_lsns[j + 1] = key;
|
||||
}
|
||||
|
||||
uint64_t highest_durable = 0;
|
||||
uint32_t last_crc = WAL_CRC_NONE;
|
||||
|
||||
for (int si = 0; si < nseg; si++) {
|
||||
char seg_path[4096];
|
||||
snprintf(seg_path, sizeof(seg_path),
|
||||
WAL_SEG_FMT, p->wal_path, seg_lsns[si]);
|
||||
|
||||
int fd = open(seg_path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
continue;
|
||||
|
||||
uint32_t seg_off = 0;
|
||||
|
||||
for (;;) {
|
||||
wal_entry_hdr_t hdr;
|
||||
ssize_t hn = pread(fd, &hdr, sizeof(hdr), (off_t)seg_off);
|
||||
if (hn == 0)
|
||||
break; /* clean end of segment */
|
||||
if (hn < (ssize_t)sizeof(hdr))
|
||||
break; /* truncated / pre-allocated zeros */
|
||||
|
||||
/* Pre-allocated zero region: no valid entry is all-zero. */
|
||||
if (hdr.lsn == 0 && hdr.type == 0 && hdr.payload_len == 0)
|
||||
break;
|
||||
|
||||
/* CRC chain validation — identical rule to replay_wal. */
|
||||
if (seg_off == 0) {
|
||||
if (hdr.prev_crc != WAL_CRC_NONE && last_crc != WAL_CRC_NONE &&
|
||||
hdr.prev_crc != last_crc)
|
||||
break;
|
||||
} else {
|
||||
if (hdr.prev_crc != last_crc)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Verify entry CRC over header + payload. */
|
||||
uint32_t expected_crc =
|
||||
crc32(0L, (const Bytef*)&hdr, WAL_HDR_CRC_SIZE);
|
||||
|
||||
uint8_t* payload = NULL;
|
||||
if (hdr.payload_len > 0) {
|
||||
payload = malloc(hdr.payload_len);
|
||||
if (!payload) {
|
||||
close(fd);
|
||||
return highest_durable;
|
||||
}
|
||||
ssize_t pn = pread(fd, payload, hdr.payload_len,
|
||||
(off_t)(seg_off + sizeof(hdr)));
|
||||
if (pn != (ssize_t)hdr.payload_len) {
|
||||
free(payload);
|
||||
break;
|
||||
}
|
||||
expected_crc =
|
||||
crc32(expected_crc, (const Bytef*)payload, hdr.payload_len);
|
||||
}
|
||||
|
||||
if (hdr.crc != expected_crc) {
|
||||
free(payload);
|
||||
break;
|
||||
}
|
||||
free(payload);
|
||||
|
||||
/* Entry is intact and durable on disk. */
|
||||
last_crc = hdr.crc;
|
||||
if (hdr.lsn > highest_durable)
|
||||
highest_durable = hdr.lsn;
|
||||
|
||||
seg_off += (uint32_t)(sizeof(hdr) + hdr.payload_len);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return highest_durable;
|
||||
}
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Checkpoint internals */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue