Skip to content

chore(qwp): de-flake the SF recovery header-fault test#68

Closed
glasstiger wants to merge 3 commits into
mainfrom
deflake-sf-recovery-header-fault-test
Closed

chore(qwp): de-flake the SF recovery header-fault test#68
glasstiger wants to merge 3 commits into
mainfrom
deflake-sf-recovery-header-fault-test

Conversation

@glasstiger

@glasstiger glasstiger commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Tandem: questdb/questdb#7404 bumps this branch into OSS core — merge together.

What

The SF store-and-forward recovery header-fault regression test
(MmapSegmentRecoveryFaultTest.testHeaderFaultOnMapPastEofIsSkippableAnyFilesystem)
flaked the JDK 8 CI with:

java.lang.InternalError: a fault occurred in a recent unsafe memory
access operation in compiled Java code

The test called MmapSegment.openExisting(ff, path) directly from the test
lambda. Under C2, HotSpot inlines openExisting into the handler-less test
frame and delivers the async unsafe-access InternalError past
openExisting's own catch (Throwable), so the fault-to-MmapSegmentException
conversion never runs and a raw InternalError escapes. This is a property of
how HotSpot delivers the async "recent unsafe memory access" fault for
JIT-compiled code, not a defect in the recovery code itself: production
recovery runs once at startup (interpreted/C1), where the fault is delivered
precisely and caught.

Change

  • Route the two header-skippable tests through SegmentRing.openExisting(...),
    production's real recovery caller. The faulting header read then sits behind a
    genuine, non-inlined call frame whose catch (Throwable) converts the fault
    to a MmapSegmentException, which SegmentRing's per-file catch skips — so
    recovery of the sole unreadable segment returns no ring, exactly as production
    behaves.
  • Add a facade-aware SegmentRing.openExisting(FilesFacade, String, long)
    overload so the length-injecting test facade reaches
    MmapSegment.openExisting. It mirrors MmapSegment.openExisting's existing
    facade seam; the FilesFacade.INSTANCE overload production uses is unchanged.

Fail-on-revert is preserved: revert the header-block conversion and the raw
InternalError escapes SegmentRing, so the test errors instead of returning
null.

Verification

The client's native lib was built locally so the tests run. Under full-suite
JIT warmup the routed header tests are stable:

  • JDK 11 (pre-21, the closest available proxy for the JDK 8 CI): 6/6 runs green
  • JDK 17: 13/13 runs green

JDK 8 was not available locally. JDK 11 shares the same pre-21 async-fault
delivery family as JDK 8, so it is the best available proxy; the JDK 8 CI is the
final confirmation.

Known limitation, left unchanged

testScanFaultOnMapPastEofIsHandledAnyFilesystem is a separate, pre-existing
flake of the same class and is not touched by this PR. It is green on the JDK 8
CI (both main and the failing branch reported only the header test failing)
and passes in isolation, but fails in-suite on JDK 11/17 because the sibling
methods warm the JIT and the scan-path async fault then escapes. I verified that
neither routing through SegmentRing nor -XX:CompileCommand=dontinline fixes
it — the fault escapes every Java-level catch under C2 regardless. Stabilizing
it on JDK 11/17 would require either relaxing it to accept the raw
InternalError (which weakens its fail-on-revert guard) or isolating it in a
dedicated fork (fragile under Surefire fork reuse). Because it is green on the
JDK 8 CI and production is unaffected, it is left as-is here; it can be a
follow-up if JDK 11/17 local-dev stability is wanted.

Test plan

  • mvn -pl core test -Dtest=MmapSegmentRecoveryFaultTest — the two
    header-skippable tests exercise the real SegmentRing recovery path
  • Verified locally under full-suite JIT warmup: JDK 11 6/6, JDK 17 13/13
  • CI on JDK 8 confirms the header flake is gone on the shipping target

🤖 Generated with Claude Code

The SF store-and-forward recovery header-fault test flaked the JDK 8
CI. It called MmapSegment.openExisting directly from the test lambda,
so under C2 HotSpot inlined openExisting into the handler-less test
frame and delivered the async "recent unsafe memory access"
InternalError past openExisting's own catch -- the conversion to a
MmapSegmentException never ran and a raw InternalError escaped,
failing the test.

Route the two header-skippable tests through SegmentRing.openExisting,
production's real recovery caller. There the faulting header read sits
behind a genuine, non-inlined call frame whose catch converts the
fault to a MmapSegmentException, which SegmentRing's per-file catch
skips -- so recovery of the sole unreadable segment yields no ring,
exactly as in production. Fail-on-revert is preserved: revert the
header-block conversion and the raw InternalError escapes SegmentRing
and the test errors.

Add a facade-aware SegmentRing.openExisting(FilesFacade, ...) overload
so the length-injecting test facade reaches MmapSegment.openExisting,
mirroring MmapSegment's own existing facade seam. The INSTANCE
overload production uses is unchanged.

Verified with the client's native lib built locally: the routed header
tests pass under full-suite JIT warmup on JDK 11 (6/6, the pre-21
proxy for the JDK 8 CI) and on JDK 17 (13/13).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@glasstiger glasstiger changed the title test(qwp): de-flake the SF recovery header-fault test chore(qwp): de-flake the SF recovery header-fault test Jul 15, 2026
The routed header-fault test asserted only that recovery returned no
ring. But SegmentRing.openExisting rebuilds the segment path as
sfDir + "/" + name and swallows the per-file MmapSegmentException, so
the test could no longer see why the skip happened. If the facade's
targetPath ever stopped matching that rebuilt path, openExisting would
read the real length 0, throw "file shorter than header" before the
mmap-fault guard, still skip, and still return null -- the test would
pass even with the guard reverted, silently gutting the only portable
fail-on-revert guard.

MapPastEofFacade now records whether it served the inflated length for
its target path, and the test asserts it did. That proves the
beyond-EOF fault, not a short-file throw, drove the skip, restoring the
structural guarantee the pre-routing reference-identity form had. A
mutation that breaks the path match now fails loudly at the assertion
instead of passing green (verified: it fails at the new assertTrue with
the coupling broken, and passes on JDK 11 and JDK 17 intact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mtopolnik

Copy link
Copy Markdown
Contributor

[PR Coverage check]

😍 pass : 2 / 2 (100.00%)

file detail

path covered line new line coverage
🔵 io/questdb/client/cutlass/qwp/client/sf/cursor/SegmentRing.java 2 2 100.00%

@glasstiger glasstiger closed this Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants