Collection-per-file mode: creating a collection can route another collection's page to the main file (lock-free read of a Dictionary)
Maintainers usually reply within 2 days
A pull request for this has already been merged.
- #314 by @CaffeinatedCoder — merged
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 58/100
Research direction
Start by reading StorageEngine.Memory.cs around GetCollectionNameBySlot, GetPageFile, and GetOrAssignCollectionSlot, then inspect _collectionSlotToName in src/BLite.Core/Storage/StorageEngine.cs. Reproduce the race with ConcurrentConsistencyTests under the described concurrent collection-creation load, especially around the fourth and eighth collection-slot assignments. Done means collection-page routing no longer falls back to the main file during concurrent reads or writes, while LoadCollectionSlots uses the same safe mapping and ordering.
Written by the indexing model from the issue text.
Description
In collection-per-file mode (PageFileConfig.Server), creating a collection while other collections read or write their pages can send one of those pages to the main file. The operation then fails with:
InvalidDataException: Page 3254779953 is outside the page file (file holds 65 pages): the file is corrupt.
3254779953 is 0xC2000031: the collection-page marker, slot 2, local page 49. Nothing on disk is corrupt. Slot 2's file exists, but the slot lookup missed.
Mechanism (0450b97)
_collectionSlotToNameis a plainDictionary<int, string>(src/BLite.Core/Storage/StorageEngine.cs:79).- It is written under
_collectionSlotLockwhen a collection gets its slot (GetOrAssignCollectionSlot,StorageEngine.Memory.cs:663-683). - It is read without that lock by
GetCollectionNameBySlot(StorageEngine.Memory.cs:333-335).GetPageFilecalls that for every read, write and free of a collection page. Dictionarydoes not support a read that runs concurrently with a write. When the 4th and the 8th collection get their slots, the dictionary resizes (capacity 3 → 7 → 17).Resizeassigns a new, still empty_bucketsarray before it re-links the entries, so a concurrentTryGetValuefor an existing slot can returnfalse.- On a miss,
GetPageFilefalls through to the main file and passes the whole encoded id as the physical page number (StorageEngine.Memory.cs:327-331).
A related ordering detail: GetOrAssignCollectionSlot publishes name → slot before slot → name (lines 679-680). A thread that finds the slot through _collectionNameToSlot can route a page before its name is in _collectionSlotToName.
Reads fail with the exception above. That is the only outcome observed so far. Writes use the same routing: WritePage on the main file would take its grow path (PageFile.EnsureCapacityCore) and try to extend the main file to the page's offset. With 16 KB pages that is tens of TB. The collection file would never get that page. I have not observed this.
Reproduction
The error appeared once while running ConcurrentConsistencyTests 60 times under load (DOTNET_PROCESSOR_COUNT=2, two full-suite copies running alongside). It happened in the workload of HighContention_ManySmallTransactions_ConsistentFinalState: 8 threads each create their own collection at the same moment and insert into it. It hit writer 2 in its second transaction, while the other collections were still being created. The race window is the few instructions of each resize, which happen only at the 4th and the 8th collection of an engine. So it is rare, but it hits exactly the workload that test runs on a loaded CI runner.
Fix considerations
- Make
_collectionSlotToNamesafe for lock-free readers: aConcurrentDictionary<int, string>, or astring?[MaxCollections]array (64 entries) written withVolatile.Write. The array fits because slots are dense and bounded. - Publish
slot → namebeforename → slot. LoadCollectionSlotswrites the same maps (at open, before any reader). It should use the same types.
Found while investigating #311 (the other failure of that test). Related: #288.
- Dominant language
- C#
- Stars
- 66
- Forks
- 6
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 58
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing 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.
More from EntglDb/BLite
-
GdprMode.Strict warns that deleted documents are not zeroed, but every delete zero-fills themPossibly taken @CaffeinatedCoder claimed this 4 days ago. Openbug gdpr package/core severity/low
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
EntglDb/BLite#299 · 1 comment ·
Maintainers usually reply within 2 days
-
bug package/core severity/medium triage
Difficulty 4/5 3-5 days Newbie friendliness 54/100
Maintainers usually reply within 2 days
-
bug package/core severity/medium
Difficulty 4/5 3-5 days Newbie friendliness 48/100
EntglDb/BLite#322 · 1 comment ·
Maintainers usually reply within 2 days
-
enhancement package/core
Difficulty 4/5 3-5 days Newbie friendliness 48/100
EntglDb/BLite#320 · 1 comment ·
Maintainers usually reply within 2 days
-
BLiteSession.Rollback() does not await the rollback: it returns while the transaction is still active, and failures go unobservedPossibly taken @CaffeinatedCoder claimed this 4 days ago. Openbug package/core severity/medium
Difficulty 3/5 1-2 days Newbie friendliness 78/100
EntglDb/BLite#307 · 1 comment ·
Maintainers usually reply within 2 days
Similar issues
-
bug mcp misleading small
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
accessibility help wanted macos
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
dorlugasigal/annotator#42 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
AvaloniaUI/Avalonia#22568 ·
Maintainers usually reply within 2 days
-
[bug] 截图失败日志冲爆硬盘Open
Difficulty 2/5 1-3 hours Newbie friendliness 63/100
babalae/better-genshin-impact#3846 · 2 comments ·
Maintainers usually reply within 2 days
-
[辞書] ブルアカ->ブルーアーカイブOpen要トリアージ 辞書
Difficulty 1/5 Under an hour Newbie friendliness 68/100
Maintainers usually reply within 1 day