Skip to content

Commit 6f8c701

Browse files
committed
test(qwp): prove legacy migration silently sanitizes proven-dead sealed residue
Deleting sanitizeSealedResidue(chain, false) in the no-manifest migration branch passed all 395 SF/recovery tests: every legacy-migration test migrated a clean chain (the sanitize ran as a no-op), and the existing residue test poisons the sealed suffix only after the first recovery has created the manifest, exercising exclusively the fail-closed (chain, true) pass. The new black-box twin plants the pre-fix-client junk in the sealed suffix gap BEFORE the first recovery ever runs and pins the one-pass silent heal: no first-sight throw, residue durably zeroed on disk, frames untouched, manifest created, and a clean restart -- no spurious SfSanitizedResidueException deferred to production's next start.
1 parent 36a6117 commit 6f8c701

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

core/src/test/java/io/questdb/client/test/cutlass/qwp/client/sf/cursor/SegmentRingTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,86 @@ public void testProvenDeadSealedResidueSanitizedThenHealsAfterOneRestart() throw
543543
});
544544
}
545545

546+
/**
547+
* The legacy-migration twin of
548+
* {@link #testProvenDeadSealedResidueSanitizedThenHealsAfterOneRestart}:
549+
* the SAME proven-dead sealed residue, but already on disk BEFORE the
550+
* first recovery ever runs — a pre-manifest slot written by a pre-fix
551+
* client. Legacy slots predate the fail-closed sealed-suffix contract,
552+
* so migration must zero the residue SILENTLY (no first-sight throw)
553+
* and hand the manifest era a chain that already satisfies the
554+
* all-zero sealed-suffix invariant — the next restart comes up clean
555+
* instead of tripping a spurious {@link SfSanitizedResidueException}
556+
* over bytes migration should have healed.
557+
*/
558+
@Test
559+
public void testLegacyMigrationSilentlySanitizesProvenDeadSealedResidue() throws Exception {
560+
TestUtils.assertMemoryLeak(() -> {
561+
long segSize = MmapSegment.HEADER_SIZE
562+
+ 4 * (MmapSegment.FRAME_HEADER_SIZE + 16)
563+
+ 12; // sealed suffix gap that can never fit a frame
564+
String m0Path = tmpDir + "/q0.sfa";
565+
String m1Path = tmpDir + "/q1.sfa";
566+
long buf = Unsafe.malloc(16, MemoryTag.NATIVE_DEFAULT);
567+
try {
568+
fillPattern(buf, 16, 6);
569+
MmapSegment s0 = MmapSegment.create(m0Path, 0, segSize);
570+
for (int i = 0; i < 4; i++) s0.tryAppend(buf, 16);
571+
long gapStart = s0.publishedOffset();
572+
s0.close();
573+
MmapSegment s1 = MmapSegment.create(m1Path, 4, segSize);
574+
s1.tryAppend(buf, 16);
575+
s1.close();
576+
// Poison the sealed suffix gap BEFORE any recovery: no
577+
// manifest exists yet, so this is a legacy slot carrying
578+
// pre-fix-client residue into migration.
579+
int fd = Files.openRW(m0Path);
580+
assertTrue("openRW must succeed", fd >= 0);
581+
long junk = Unsafe.malloc(12, MemoryTag.NATIVE_DEFAULT);
582+
try {
583+
for (int i = 0; i < 3; i++) {
584+
Unsafe.getUnsafe().putInt(junk + i * 4L, 0xCAFEBABE);
585+
}
586+
assertEquals(12L, Files.write(fd, junk, 12, gapStart));
587+
Files.fsync(fd);
588+
} finally {
589+
Unsafe.free(junk, 12, MemoryTag.NATIVE_DEFAULT);
590+
Files.close(fd);
591+
}
592+
// Migration: contiguity proves the residue dead, so the
593+
// legacy chain recovers in ONE pass — silently.
594+
try (SegmentRing ring = SegmentRing.openExisting(tmpDir, segSize)) {
595+
assertNotNull("legacy migration must not fail closed over "
596+
+ "proven-dead sealed residue", ring);
597+
assertEquals(1, ring.getSealedSegments().size());
598+
assertEquals(4, ring.getSealedSegments().get(0).frameCount());
599+
assertEquals(4, ring.getActive().baseSeq());
600+
}
601+
// The silent heal: residue durably zeroed, frames intact,
602+
// slot migrated to the manifest era.
603+
try (MmapSegment seg = MmapSegment.openExisting(m0Path)) {
604+
assertEquals("frames must be untouched", 4L, seg.frameCount());
605+
assertEquals("legacy migration must durably zero the dead residue",
606+
0L, seg.tornTailBytes());
607+
}
608+
assertTrue("legacy migration must create the manifest",
609+
Files.exists(tmpDir + "/sf-manifest.bin"));
610+
// The manifest-era invariant holds: the restart's fail-closed
611+
// sealed-suffix pass finds nothing to surface. A
612+
// SfSanitizedResidueException here means migration skipped
613+
// the sanitize and deferred the incident to production's
614+
// next restart.
615+
try (SegmentRing ring = SegmentRing.openExisting(tmpDir, segSize)) {
616+
assertNotNull("restart over the migrated chain must come up clean", ring);
617+
assertEquals(1, ring.getSealedSegments().size());
618+
assertEquals(5, ring.nextSeqHint());
619+
}
620+
} finally {
621+
Unsafe.free(buf, 16, MemoryTag.NATIVE_DEFAULT);
622+
}
623+
});
624+
}
625+
546626
@Test
547627
public void testRingRecoverySanitizesResumedActiveTornTail() throws Exception {
548628
TestUtils.assertMemoryLeak(() -> {

0 commit comments

Comments
 (0)