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
132
src/controller/testsuite/test_snapdiff.c
Normal file
132
src/controller/testsuite/test_snapdiff.c
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "snapdiff.h"
|
||||
|
||||
static char g_dir[256];
|
||||
|
||||
static void fresh_dir(void)
|
||||
{
|
||||
strcpy(g_dir, "/tmp/xce_snapdiff_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(g_dir));
|
||||
}
|
||||
|
||||
static void cleanup_dir(void)
|
||||
{
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", g_dir);
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
static void write_file(const char* path, size_t size, uint8_t fill,
|
||||
size_t flip_at, uint8_t flip_to)
|
||||
{
|
||||
uint8_t* buf = malloc(size);
|
||||
memset(buf, fill, size);
|
||||
if (flip_at < size)
|
||||
buf[flip_at] = flip_to;
|
||||
FILE* fp = fopen(path, "wb");
|
||||
TEST_ASSERT_NOT_NULL(fp);
|
||||
fwrite(buf, 1, size, fp);
|
||||
fclose(fp);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void test_identical_files(void)
|
||||
{
|
||||
fresh_dir();
|
||||
char p1[512], p2[512];
|
||||
snprintf(p1, sizeof(p1), "%s/a.bin", g_dir);
|
||||
snprintf(p2, sizeof(p2), "%s/b.bin", g_dir);
|
||||
write_file(p1, 1000, 0x42, 2000 /* no flip */, 0);
|
||||
write_file(p2, 1000, 0x42, 2000, 0);
|
||||
|
||||
snapdiff_report_t rep;
|
||||
int rc = snapdiff_paths(p1, p2, stdout, &rep);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, rep.identical);
|
||||
TEST_ASSERT_EQUAL_INT(-1, (int)rep.diff_offset);
|
||||
cleanup_dir();
|
||||
}
|
||||
|
||||
static void test_size_mismatch(void)
|
||||
{
|
||||
fresh_dir();
|
||||
char p1[512], p2[512];
|
||||
snprintf(p1, sizeof(p1), "%s/a.bin", g_dir);
|
||||
snprintf(p2, sizeof(p2), "%s/b.bin", g_dir);
|
||||
write_file(p1, 1000, 0x42, 2000, 0);
|
||||
write_file(p2, 999, 0x42, 2000, 0);
|
||||
|
||||
snapdiff_report_t rep;
|
||||
int rc = snapdiff_paths(p1, p2, stdout, &rep);
|
||||
TEST_ASSERT_EQUAL_INT(1, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, rep.size_mismatch);
|
||||
TEST_ASSERT_EQUAL_INT(0, rep.identical);
|
||||
cleanup_dir();
|
||||
}
|
||||
|
||||
/* bug-1389 regression shape: two SNAPDIFF_CHUNK_SIZE-sized chunks, content
|
||||
* diverges starting at the exact first byte of chunk index 1 — the OPCode_
|
||||
* snapshotTransfer chunk-boundary corruption signature. */
|
||||
static void test_diff_at_chunk_boundary_is_flagged(void)
|
||||
{
|
||||
fresh_dir();
|
||||
char p1[512], p2[512];
|
||||
snprintf(p1, sizeof(p1), "%s/a.bin", g_dir);
|
||||
snprintf(p2, sizeof(p2), "%s/b.bin", g_dir);
|
||||
size_t size = SNAPDIFF_CHUNK_SIZE * 2;
|
||||
write_file(p1, size, 0x11, size, 0);
|
||||
write_file(p2, size, 0x11, SNAPDIFF_CHUNK_SIZE /* flip first byte of chunk 1 */,
|
||||
0x22);
|
||||
|
||||
snapdiff_report_t rep;
|
||||
int rc = snapdiff_paths(p1, p2, stdout, &rep);
|
||||
TEST_ASSERT_EQUAL_INT(1, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, rep.identical);
|
||||
TEST_ASSERT_EQUAL_INT(SNAPDIFF_CHUNK_SIZE, (int)rep.diff_offset);
|
||||
TEST_ASSERT_EQUAL_INT(1, rep.at_chunk_boundary);
|
||||
TEST_ASSERT_EQUAL_INT(1, (int)rep.chunk_index);
|
||||
cleanup_dir();
|
||||
}
|
||||
|
||||
static void test_diff_mid_chunk_is_not_flagged_as_boundary(void)
|
||||
{
|
||||
fresh_dir();
|
||||
char p1[512], p2[512];
|
||||
snprintf(p1, sizeof(p1), "%s/a.bin", g_dir);
|
||||
snprintf(p2, sizeof(p2), "%s/b.bin", g_dir);
|
||||
write_file(p1, 1000, 0x11, 2000, 0);
|
||||
write_file(p2, 1000, 0x11, 500 /* mid-file, not a chunk boundary */, 0x22);
|
||||
|
||||
snapdiff_report_t rep;
|
||||
int rc = snapdiff_paths(p1, p2, stdout, &rep);
|
||||
TEST_ASSERT_EQUAL_INT(1, rc);
|
||||
TEST_ASSERT_EQUAL_INT(500, (int)rep.diff_offset);
|
||||
TEST_ASSERT_EQUAL_INT(0, rep.at_chunk_boundary);
|
||||
cleanup_dir();
|
||||
}
|
||||
|
||||
static void test_missing_path_errors(void)
|
||||
{
|
||||
snapdiff_report_t rep;
|
||||
int rc = snapdiff_paths("/tmp/xce_snapdiff_missing_a_zzz",
|
||||
"/tmp/xce_snapdiff_missing_b_zzz", stdout, &rep);
|
||||
TEST_ASSERT_LESS_THAN_INT(0, rc);
|
||||
}
|
||||
|
||||
void test_snapdiff(void)
|
||||
{
|
||||
RUN_TEST(test_identical_files);
|
||||
RUN_TEST(test_size_mismatch);
|
||||
RUN_TEST(test_diff_at_chunk_boundary_is_flagged);
|
||||
RUN_TEST(test_diff_mid_chunk_is_not_flagged_as_boundary);
|
||||
RUN_TEST(test_missing_path_errors);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue