questdb / questdb/java-questdb-client
Flaky on JDK 11: BackgroundDrainerMidDrainCapabilityGapTest.testDeliveringBetweenTwoGapWindowsGrantsAFreshSettleBudget
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 10
- Forks
- 6
- Avg merge
- 6h 40m
- Merged PRs (30d)
- 2
Description
BackgroundDrainerMidDrainCapabilityGapTest#testDeliveringBetweenTwoGapWindowsGrantsAFreshSettleBudget fails intermittently on JDK 11, on main as well as on feature branches. Found while reviewing #99; it is not caused by that PR.
Symptom
java.lang.AssertionError: delivering between the windows ends the episode, so neither window
reaches the threshold and the slot must still drain [attempts=20] expected:<SUCCESS> but was:<FAILED>
at ...BackgroundDrainerMidDrainCapabilityGapTest.lambda$testDeliveringBetweenTwoGapWindowsGrantsAFreshSettleBudget$0(BackgroundDrainerMidDrainCapabilityGapTest.java:240)
at ...TestUtils.assertMemoryLeak(TestUtils.java:135)
at ...BackgroundDrainerMidDrainCapabilityGapTest.testDeliveringBetweenTwoGapWindowsGrantsAFreshSettleBudget(BackgroundDrainerMidDrainCapabilityGapTest.java:201)
The drainer ends the episode with DrainOutcome.FAILED and quarantines the slot, where the test expects SUCCESS. factory.attempts() is 20 — the exact end of the second scripted gap window.
Reproduction
JAVA_HOME=~/.sdkman/candidates/java/11.0.26-amzn \
mvn -pl core test -Dtest='BackgroundDrainerMidDrainCapabilityGapTest'
Re-run several times — it does not fail every run.
Evidence that it is flaky and not revision-specific
JDK 11.0.26-amzn (Corretto), Linux x86-64, repeated runs of the same test:
| revision | failures / runs |
|---|---|
981bdb0 (main) |
4 / 7 |
065c7be (PR #99 branch) |
3 / 7 |
Both revisions both pass and fail it. It initially looked like a regression on the PR branch (branch failed, main passed), but the outcome also inverts depending on how the test is invoked: running the single method in isolation vs. running the whole class flipped which revision failed, in both directions, across repeats. That rules out a code difference between the two revisions as the cause.
Not observed on JDK 26: a full mvn -pl core test there passed (3456 tests, 0 failures) — though that is a single observation, not a claim that the JDK matters.
What I ruled out
- Machine load. Reproduced on an idle machine (load ~1.6), not only under parallel builds.
- The 60 s wall-clock settle budget.
RECONNECT_MAX_DURATION_MILLIS = 60_000in the test, backoff 1–4 ms, and the whole run finishes in ~10 s, socapabilityGapElapsedNanos >= reconnectBudgetNanos(BackgroundDrainer.java:618) is not the terminal that fires. - The settle-budget accumulators failing to reset as a group.
capabilityGapAttempts,capabilityGapElapsedNanosandlastCapabilityGapNanosare reset together at all three sites (BackgroundDrainer.java:525-527, 563-565, 683-685), and a captured log of a passing run shows the episode restarting correctly — the second gap window begins again atattempt 1:
21:11:51.088 ... durable-ack capability gap mid-drain (...), re-entering settle budget
21:11:51.089 ... attempt 1: durable-ack unavailable, retrying after backoff
... attempts 2..8 ...
21:11:56.044 ... durable-ack capability gap mid-drain (...), re-entering settle budget <- ~5 s delivering gap
21:11:56.054 ... attempt 1: durable-ack unavailable, retrying after backoff
What I did not determine
Root cause. Two candidates remain, and I could not separate them:
- Test-harness race. The scripted delivering session (connection 2 acks frames 0 and 1, then drops —
drops.put(2, 1L)) sometimes loses the race and never advances the watermark, so no reset happens, the two 9-sweep windows accumulate to 18 ≥DEFAULT_MAX_DURABLE_ACK_MISMATCH_ATTEMPTS(16), andFAILEDis the correct outcome for what actually happened on the wire. - A genuine intermittent failure of the episode reset in
BackgroundDrainer.
The test drives a real TestWebSocketServer over a real loopback socket, so the delivering session's ack is genuinely timing-dependent.
The decisive diagnostic is the give-up line from a failing run, which reports which terminal fired and the elapsed time:
drainer giving up on slot {} after {} durable-ack-mismatch attempts ({}ms): {}
(BackgroundDrainer.java:620). If it reports ~18 attempts, candidate 1 is the explanation and the fix belongs in the test harness (make the delivering session's ack deterministic before the connection drops). I only managed to capture that log on passing runs.
Impact
Low for CI — build-jdk8 is the only job that runs tests, so this never reaches a pipeline today. It costs developer time on local JDK 11+ runs, and it is masking whichever of the two candidates above is true.
🤖 Generated with Claude Code
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce BackgroundDrainerMidDrainCapabilityGapTest#testDeliveringBetweenTwoGapWindowsGrantsAFreshSettleBudget with the provided Maven command on JDK 11. Inspect BackgroundDrainer.java around lines 525-527, 563-565, 618, and 683-685, then capture the failing give-up line to distinguish a TestWebSocketServer harness race from an episode-reset failure. Done means the test no longer flakes and the correct reset or deterministic acknowledgement behavior is covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 42/100