forked from lei.chen/myxce
fix(raft-drive-async): resolve snapshot chunk-drop (bug-1386) and chunk misordering (bug-1389)
ha_send_snapshot_to_standby ignored the network send's return value in its chunk loop, silently truncating a transfer on a mid-stream error while still logging "complete" (bug-1386). Fixing the silent drop exposed a second, independent bug in the same pipeline: ha_handle_snapshot_chunk appended received chunks in arrival order instead of at their wire-carried chunk_offset, so out-of-order delivery across the vtask pool corrupted the reassembled file even though every chunk's bytes were individually correct (bug-1389). Fixed by checking every fallible call in the send loop and switching the receiver to pwrite-at-offset with a count-based completion tracker. Adds snapverify/snapdiff xce_test commands (offline checkpoint-file structural validator and byte-level comparator) used to diagnose bug-1389 and kept as standing diagnostic tools for future snapshot-transfer triage. g_drive_async_configpush_offload flipped back ON after both fixes, validated via 6-round chaotic + 3/3 force-snapshot soak (raft-drive-async task 3.3). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SDWVNQLKxqTVHh42cH7rV3
This commit is contained in:
parent
02e46f6ba1
commit
5cb36794d2
14 changed files with 1553 additions and 43 deletions
|
|
@ -248,6 +248,113 @@ static void test_snapshot_chunk_roundtrip(void)
|
|||
printf("test_snapshot_chunk_roundtrip: PASS\n");
|
||||
}
|
||||
|
||||
/* bug-1389: a force-snapshot soak observed the RECEIVED chunk's content
|
||||
* diverging from the SENT content starting at an exact SNAPSHOT_CHUNK_SIZE
|
||||
* (65536-byte) boundary, byte-shifted by a nondeterministic amount, while
|
||||
* the total size stayed correct. test_snapshot_chunk_roundtrip above only
|
||||
* exercises a 300-byte payload — far short of the 65536-byte production
|
||||
* chunk size that actually forces xce_snapshot_chunk_encode_raw's
|
||||
* capn_new_list8 call to overflow the root segment and cross into a second
|
||||
* segment via a far pointer (the exact mechanism bug-847 already proved can
|
||||
* corrupt a capnp Data field in this codebase, for a different message
|
||||
* shape). This reproduces the REAL production chunk size in a single
|
||||
* deterministic encode->decode round-trip, no network involved — isolating
|
||||
* whether the corruption is in this library's capnp handling at all, or
|
||||
* only manifests under real transport/timing conditions. */
|
||||
static void test_snapshot_chunk_roundtrip_full_size_payload(void)
|
||||
{
|
||||
/* Arrange: exactly SNAPSHOT_CHUNK_SIZE (config_repl.c) bytes, with a
|
||||
* position-dependent, non-repeating-within-256-bytes pattern so ANY
|
||||
* byte-shift (not just a zeroed/dropped payload) is immediately
|
||||
* detectable by TEST_ASSERT_EQUAL_MEMORY instead of accidentally
|
||||
* matching shifted-but-still-plausible content. */
|
||||
enum { CHUNK_SZ = 65536 };
|
||||
uint8_t *payload = malloc(CHUNK_SZ);
|
||||
TEST_ASSERT_NOT_NULL(payload);
|
||||
for (size_t i = 0; i < CHUNK_SZ; i++)
|
||||
payload[i] = (uint8_t)((i * 2654435761u) >> 24); /* Knuth multiplicative hash */
|
||||
|
||||
snapshot_transfer_t chunk;
|
||||
memset(&chunk, 0, sizeof(chunk));
|
||||
chunk.snapshot_lsn = 117;
|
||||
chunk.chunk_index = 0;
|
||||
chunk.chunk_offset = 0;
|
||||
chunk.total_size = 2 * CHUNK_SZ;
|
||||
chunk.is_last = 0;
|
||||
chunk.checkpoint_term = 4;
|
||||
|
||||
io_packet_t *pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
|
||||
TEST_ASSERT_NOT_NULL(pkt);
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
xce_snapshot_chunk_encode_raw(&chunk, payload, CHUNK_SZ, &test_peer,
|
||||
11, pkt));
|
||||
|
||||
/* Act */
|
||||
xce_request_t req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_decode(pkt, &req));
|
||||
|
||||
/* Assert: envelope + every byte of a full-size chunk survives intact */
|
||||
TEST_ASSERT_EQUAL_INT(OPCode_snapshotTransfer, req.opcode);
|
||||
TEST_ASSERT_EQUAL_INT(Request_body_snapshottransfermsg, req.body_which);
|
||||
snapshot_transfer_t *rx = req.body.snapshottransfermsg;
|
||||
TEST_ASSERT_NOT_NULL(rx);
|
||||
TEST_ASSERT_EQUAL_UINT64(117, rx->snapshot_lsn);
|
||||
TEST_ASSERT_EQUAL_UINT64(4, rx->checkpoint_term);
|
||||
TEST_ASSERT_EQUAL_size_t(CHUNK_SZ, rx->raw_payload_len);
|
||||
TEST_ASSERT_NOT_NULL(rx->raw_payload);
|
||||
TEST_ASSERT_EQUAL_MEMORY(payload, rx->raw_payload, CHUNK_SZ);
|
||||
|
||||
xce_free_request(&req);
|
||||
free(payload);
|
||||
printf("test_snapshot_chunk_roundtrip_full_size_payload: PASS\n");
|
||||
}
|
||||
|
||||
/* bug-1389: same as above but for chunk_index 1 (the SECOND chunk of a
|
||||
* multi-chunk transfer) — the observed force-snapshot corruption started at
|
||||
* a chunk-boundary OTHER than the very first chunk in one of the two
|
||||
* repro runs (offset 131072 == chunk index 2's start), so this covers a
|
||||
* non-zero chunk_index/chunk_offset combination too, not just chunk 0. */
|
||||
static void test_snapshot_chunk_roundtrip_full_size_payload_nonzero_index(void)
|
||||
{
|
||||
enum { CHUNK_SZ = 65536 };
|
||||
uint8_t *payload = malloc(CHUNK_SZ);
|
||||
TEST_ASSERT_NOT_NULL(payload);
|
||||
for (size_t i = 0; i < CHUNK_SZ; i++)
|
||||
payload[i] = (uint8_t)((i * 2654435761u) >> 24);
|
||||
|
||||
snapshot_transfer_t chunk;
|
||||
memset(&chunk, 0, sizeof(chunk));
|
||||
chunk.snapshot_lsn = 118;
|
||||
chunk.chunk_index = 2;
|
||||
chunk.chunk_offset = 2 * CHUNK_SZ;
|
||||
chunk.total_size = 4 * CHUNK_SZ;
|
||||
chunk.is_last = 0;
|
||||
chunk.checkpoint_term = 4;
|
||||
|
||||
io_packet_t *pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
|
||||
TEST_ASSERT_NOT_NULL(pkt);
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
xce_snapshot_chunk_encode_raw(&chunk, payload, CHUNK_SZ, &test_peer,
|
||||
12, pkt));
|
||||
|
||||
xce_request_t req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_decode(pkt, &req));
|
||||
|
||||
snapshot_transfer_t *rx = req.body.snapshottransfermsg;
|
||||
TEST_ASSERT_NOT_NULL(rx);
|
||||
TEST_ASSERT_EQUAL_UINT32(2, rx->chunk_index);
|
||||
TEST_ASSERT_EQUAL_UINT64(2 * CHUNK_SZ, rx->chunk_offset);
|
||||
TEST_ASSERT_EQUAL_size_t(CHUNK_SZ, rx->raw_payload_len);
|
||||
TEST_ASSERT_NOT_NULL(rx->raw_payload);
|
||||
TEST_ASSERT_EQUAL_MEMORY(payload, rx->raw_payload, CHUNK_SZ);
|
||||
|
||||
xce_free_request(&req);
|
||||
free(payload);
|
||||
printf("test_snapshot_chunk_roundtrip_full_size_payload_nonzero_index: PASS\n");
|
||||
}
|
||||
|
||||
static void test_snapshot_chunk_empty_payload(void)
|
||||
{
|
||||
/* Zero-length payload (defensive): envelope round-trips, raw NULL/0 */
|
||||
|
|
@ -286,5 +393,7 @@ void test_wal_wire(void)
|
|||
RUN_TEST(test_wal_batch_entry_without_payload);
|
||||
RUN_TEST(test_wal_batch_encode_rejects_invalid_args);
|
||||
RUN_TEST(test_snapshot_chunk_roundtrip);
|
||||
RUN_TEST(test_snapshot_chunk_roundtrip_full_size_payload);
|
||||
RUN_TEST(test_snapshot_chunk_roundtrip_full_size_payload_nonzero_index);
|
||||
RUN_TEST(test_snapshot_chunk_empty_payload);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue