feat(ha-repl-hardening): primary snapshot chunk sender (task 3.5a)

ha_send_snapshot_to_standby reads snapshot file, splits into 64KB
chunks, sends each via OPCode_snapshotTransfer with correct metadata
(snapshotLsn, chunkIndex, chunkOffset, totalSize, isLast). Payload
encoding deferred. Hook seam for unit testing.
2 tests: 128KB/2-chunks, small-file/1-chunk. Tasks 3.5b-3.5d remain.
This commit is contained in:
Rongsong Shen 2026-06-05 01:16:34 +00:00
parent 6b69a47c50
commit 834ab4d5e7
5 changed files with 241 additions and 11 deletions

View file

@ -1730,3 +1730,86 @@ int ha_stream_wal_to_standby(endpoint_t *ep, const char *wal_path,
"] to standby", standby_lsn, committed_lsn);
return 0;
}
/* ── Snapshot chunk transfer ───────────────────────────────────────────── */
#ifdef XCE_TESTING
int (*xce_snapshot_chunk_hook)(endpoint_t *ep, xce_request_t *req) = NULL;
#endif
#define SNAPSHOT_CHUNK_SIZE 65536
int ha_send_snapshot_to_standby(endpoint_t *ep, const char *snap_dir,
uint64_t checkpoint_lsn, uint64_t total_size)
{
if (!snap_dir)
return -EINVAL;
/* Find the snapshot file by naming convention */
char snap_path[4096];
snprintf(snap_path, sizeof(snap_path),
"%s/snap-%016" PRIx64 ".capnp", snap_dir, checkpoint_lsn);
FILE *f = fopen(snap_path, "rb");
if (!f)
return -errno;
uint8_t *buf = malloc(SNAPSHOT_CHUNK_SIZE);
if (!buf) {
fclose(f);
return -ENOMEM;
}
int rc = 0;
uint64_t offset = 0;
uint32_t chunk_index = 0;
/* Chunk the snapshot file and enqueue each chunk via xce_request().
* Payload encoding uses NULL for now — the user-defined Data type in capnp
* (xce_data_t tagged union) requires a proper table dispatch for non-null
* payloads. This is a known limitation shared with wal_catchup_entry_t
* payload; both will be resolved when the capnp schema is updated to use
* the built-in capnp Data (List(UInt8)) type. */
while (offset < total_size) {
size_t chunk_len = (total_size - offset > SNAPSHOT_CHUNK_SIZE)
? SNAPSHOT_CHUNK_SIZE
: (size_t)(total_size - offset);
/* Consume the file bytes to advance offset — data not used yet */
if (fread(buf, 1, chunk_len, f) != chunk_len) {
rc = -EIO;
break;
}
int is_last = (offset + chunk_len >= total_size);
snapshot_transfer_t chunk;
memset(&chunk, 0, sizeof(chunk));
chunk.snapshot_lsn = checkpoint_lsn;
chunk.chunk_index = chunk_index;
chunk.chunk_offset = offset;
chunk.total_size = total_size;
chunk.is_last = is_last;
chunk.payload = NULL; /* encoding deferred */
xce_request_t req;
memset(&req, 0, sizeof(req));
req.opcode = OPCode_snapshotTransfer;
req.body_which = Request_body_nomsg;
req.body.snapshottransfermsg = &chunk;
#ifdef XCE_TESTING
if (xce_snapshot_chunk_hook)
xce_snapshot_chunk_hook(ep, &req);
else
#endif
if (ep)
xce_request(ep, &req);
offset += chunk_len;
chunk_index++;
}
free(buf);
fclose(f);
return rc;
}