test(raft-ha-core-safety): cross-term streamer payload-integrity RED (task 2.3)

Minimal XCE_TESTING fault-injection seam in ha_stream_wal_to_standby
(NULL/production-inert) forces a short body read on a cross-term entry.
CORE RED proves the streamer ships PLEN=0 for a full (source_len=64)
cross-term entry: instrumentation source_len=64 SEND_len=0 RECV_len=0 ->
loss is SEND-side (answers task 1.3's open send-vs-transitive question).
2 honest pins (term-blind route guarded by 2.2; below-tip fence). 11
tests/1 RED; production xce_controller links clean without the seam.
This commit is contained in:
Rongsong Shen 2026-06-27 11:24:16 +00:00
parent 056675c8b3
commit ae8291ed56
3 changed files with 385 additions and 12 deletions

View file

@ -1994,6 +1994,20 @@ int handle_ctrlhello(cluster_t *c, endpoint_t *ep, xce_request_t *req)
/* Test-only seam: intercepts each batch xce_request() call.
* Defined here (translation unit scope); declared extern in test file. */
int (*xce_wal_stream_request_hook)(endpoint_t *ep, xce_request_t *req) = NULL;
/* raft-ha-core-safety task 2.3 — test-only fault-injection seam for the catch-up
* streamer's entry-body read (the "Catch-up Payload Integrity" boundary). When
* non-NULL AND it returns nonzero for an entry, ha_stream_wal_to_standby treats
* that entry's body pread as a SHORT READ — the same outcome as a malloc-fail —
* which reproduces the live cross-term catch-up payload-loss signature: a
* non-empty source entry is shipped with PLEN=0. NULL in production: behaviour
* is then byte-for-byte unchanged. This seam ONLY makes the defect
* deterministically observable for the task-2.3 RED test; the fix that stops the
* empty ship (fail loud + bounded retry) is task 2.4. Declared extern in the
* test file (same pattern as xce_wal_stream_request_hook). */
int (*ha_stream_body_read_fault_hook)(uint64_t lsn, uint64_t stored_term,
uint64_t leader_term,
uint32_t payload_len) = NULL;
#endif
/* send_wal_batch: enqueue a single OPCode_walCatchupEntry batch message.
@ -2156,6 +2170,19 @@ int ha_stream_wal_to_standby(endpoint_t *ep, const char *wal_path,
if (pbuf) {
ssize_t pr = pread(fd, pbuf, hdr.payload_len,
(off_t)(off + sizeof(hdr)));
#ifdef XCE_TESTING
/* raft-ha-core-safety task 2.3 fault-injection seam: a
* test may force this body read to look SHORT (the same
* outcome as the malloc-fail tail above) to prove the
* baseline ships a non-empty entry as PLEN=0. NULL in
* production — no behaviour change. Task 2.4 owns the
* fix (fail loud + bounded retry); this seam ships no fix. */
if (ha_stream_body_read_fault_hook != NULL &&
ha_stream_body_read_fault_hook(hdr.lsn, entry->term,
leader_term,
hdr.payload_len))
pr = 0; /* simulate a short read */
#endif
if (pr == (ssize_t)hdr.payload_len) {
entry->raw_payload = pbuf;
entry->raw_payload_len = hdr.payload_len;