Repository navigation
Description
Split from #6406 item 4 ("a dictionary page is missing from the file after a drain that reported success"), which named this as possibly the one item in that batch that isn't a test bug.
The failure
DictionaryMultiPageTest.aDictionaryPageThatIsClaimedButMissingFailsLoudly fails on main's unit-tests lane with:
[the premise: the file really holds page 0 and nothing after it]
expected: 1L
but was: 0L
right after the test asserts waitAllPagesOfDatabaseAreFlushed(other) returned true. So the drain reported success while PaginatedComponentFile.getTotalPages() (channel.size() / pageSize, not a cached value) still says the page never reached the file.
Current rate (checked 2026-08-18, last 16 completed mvn-test.yml runs on main): 3/16 (32099192174, 32116404436, 32150657917), always the identical assertion, always this exact test. Consistent with #6406's original "4 of the last 20" count.
What I ruled out (issue #6406's PR, l.garulli)
#6406 item 4 suggested this lead: every per-database map in PageManagerFlushThread is keyed by LocalDatabase.equals() (compares databasePath, not instance identity), and the test's other database could be aliasing a stale entry. That hazard is real in general but does not apply here:
- It was already fixed for
flushedPagesPerDatabaseandslotsInUseby Follow-ups from #6281: path-keyed flush-thread maps, an unchecked worker pause, and async-dispatched DDLΒ #6303/fix(#6303): a stale batch stops answering for the database at its path, an index build waits for the workers to actually park, and async DDL runs off themΒ #6309 (both now carry their counter on the batch itself, resolved once at enqueue time, never re-looked-up by path). pageIndex's own per-database counter (FlushPageIndex.pending) is guarded byremoveAllOfDatabaserunning under the database's own write lock, which excludes a concurrentputAllfor the same database (see the class's own javadoc). It isn't guarded against a different, path-aliased database, but this test'sotherdatabase is created fresh (TestHelper.createDatabasedrops any existing DB at that path first) and nothing else in the suite uses that exact path, so there's no other instance to alias with.- I traced every possible interleaving of
FlushPageIndex.putAll/removeIfSamefor the shard's known two-commit sequence (schema init writes page 0's header, thengetIdByName'saddItemToPagemodifies the same page again) by hand. The counter's arithmetic is self-correcting under every ordering I could construct - it never crosses zero while a newer, unflushed copy of the page is still the one indexed. I could not find a hole in the counting mechanism itself. flushPage's "file dropped" branch (fileManager.existsFile(fileId)false, releases the WAL ack without writing) is the only path I found that decrements the pending count without writing bytes - but it requires the file to have been dropped, which never happens to the dictionary file in this test.
What I tried and couldn't reproduce
- 3300+ iterations of the test's exact logic in a single JVM, with 4 concurrent noise databases hammering the shared flush thread to maximize scheduling jitter: zero repros.
- 15 fresh-JVM runs of just this test method (
mvn test -Dtest=DictionaryMultiPageTest#aDictionaryPageThatIsClaimedButMissingFailsLoudly): zero repros. - 2 full
enginemodule suite runs (~12750 tests each, matching CI's actual invocation) locally: zero repros, including of this specific test.
So this reproduces on CI's runners but not on a dev machine even under deliberately adversarial local conditions - suggests either something specific to the CI runners' I/O/scheduling characteristics, or a window narrower than what I could force locally.
A data point for whoever picks this up
A fork run gathering fresh CI data for this investigation noticed a broader shape in the same time window: three other, unrelated tests also failed on "expected a positive/non-zero value, got 0" assertions in this period - Issue6111StaleSnapshotReadFloorTest.retryFiresAndResolvesTheFloorWhenNothingIsRunning (retry counter), Issue5596MergeCoverageTest.aCleanAppendConflictIsStillMerged (merge counter), MaterializedViewRefreshAtomicityTest (index size on disk, slow-unit-tests lane). Whether these share a root cause with this one or are coincidentally-shaped unrelated flakes is unknown - flagging it because "the same shape of assertion failing across several unrelated flush/counter-adjacent tests in the same window" is the kind of thing worth checking before assuming they're independent.
Suggested next steps
- Add temporary diagnostic logging around
waitAllPagesOfDatabaseAreFlushed/flushPage(e.g. log the exact branch taken influshPage- written vs. file-dropped vs. exception - for every page of this test's database) and let it ride in CI a few times to catch the mechanism in the act, since local reproduction has not worked. - Check whether the CI runners' filesystem (overlay/network FS on GitHub Actions) has different
FileChannel.write/size()visibility semantics than a local disk -PaginatedComponentFile.write()andgetTotalPages()both assume awrite()that returns has extendedchannel.size()for a same-process, same-channel read, which is POSIX-guaranteed but worth confirming holds on the actual runner image. - If the broader "0L" pattern above recurs, treat it as the same investigation rather than as separate flakes.
CC @lvca
Activity
fix(#6406): TimeSeries check parallelism, CHECK DATABASE docs sync guβ¦
lvca commentedon Aug 18, 2026
Re-checked CI after #6351 merged (
68cf79803, "the dictionary asks the committed page count whether there is anything to read, not the file's size"). Pulled theunit-testslane job logs for the 8 most recentmainruns since that merge (2026-08-18, 14:52 through 21:16 UTC):DictionaryMultiPageTestpassed cleanly (15/15) in every one of them, including the two runs where the lane as a whole went red - both failed on unrelated tests (ParallelRangeTopKTest,Issue6111StaleSnapshotReadFloorTest, the latter matching the "same shape of assertion failing across several unrelated flush/counter-adjacent tests" pattern already flagged in this issue's own body).That's 0 recurrences in 8+ runs against the ~3/16 (β19%) rate this issue documented before #6351. Not proof - 8 clean runs has a ~19% chance of happening even if the true rate were unchanged - but it's the strongest signal available, and I could not find a concrete code defect to fix on top of it: I re-traced the writeβflushβdrain ordering in
PageManagerFlushThread/PaginatedComponentFilemyself and, like the original investigation, found no window wherewaitAllPagesOfDatabaseAreFlushed()can returntruebefore the page'schannel.write()call has returned -removeFromFlushIndex()(which is what lets the drain complete) only runs in thefinallyafter the write, on the same thread, andchannel.size()reading the sameFileChannelobject after awrite()on it returning is a POSIX guarantee, not a maybe. A blind fix here risks masking whatever the real mechanism is rather than fixing it.Given that, rather than a speculative production change, I added a diagnostic enrichment to the test's own premise assertion (
DictionaryMultiPageTest.aDictionaryPageThatIsClaimedButMissingFailsLoudly, in the PR referenced below): it now also reads the raw OS file length viagetOSFile().length(), bypassingPaginatedComponentFile'sFileChannelentirely, and includes it in the assertion message. If this recurs, that one extra number tells us which of the two remaining hypotheses is live - a genuine flush-pipeline bug (raw length also 0) vs. a channel-visibility/caching bug local toPaginatedComponentFile(raw length correct, channel disagrees) - without needing a special diagnostic-enabled CI run to catch it.Leaving this open rather than closing it: the absence of recurrence is encouraging but not conclusive, and the instrumentation is there specifically so the next occurrence (if any) is self-diagnosing.
fix(#6418, #6404, #6415): Cypher function-call write classification, β¦
lvca commentedon Aug 19, 2026
The diagnostic enrichment (raw OS file length in the premise assertion) merged in #6435. Leaving this open and unlabeled rather than in-progress - no root cause was found, and the flake has not recurred since #6351, so there's nothing actively being worked. Re-open the label if/when it recurs; the added diagnostic should make the next occurrence self-explanatory.