Every reopen strands the unwritten rest of the last page growth block
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
Research direction
Start with src/BLite.Core/Storage/PageFile.cs, especially Open, EnsureCapacityCore, and TruncateToMinimumAsync, then reproduce with the B-tree model test at tests/BLite.Tests/Oracles/BTreeModelTests.cs. Trace how allocation state is shared in multi-process mode before choosing a fix. Done means reopened databases reuse unwritten pages, file growth tracks data, and the model test no longer needs to retain affected rows as a candidate.
Written by the indexing model from the issue text.
Description
Each open that writes anything new grows the file by a whole growth block (GrowthBlockSize: 1 MB by default, 512 KB for Small, 2 MB for Large), and the unused part of the previous block is lost: it counts as allocated, is never handed out, is not on the free list and is not reclaimed by TruncateToMinimumAsync. The file grows with the number of opens, not with the data.
Found by the B-tree model test (tests/BLite.Tests/Oracles/BTreeModelTests.cs, #259), which carries the affected rows as a known candidate until this is fixed.
Repro
for (int i = 0; i < 10; i++)
{
using var db = new TestDbContext(path); // PageFileConfig.Default: 16 KB pages, 1 MB growth
for (int j = 0; j < 20; j++)
await db.PeopleV2.InsertAsync(new PersonV2 { Name = new string('x', 2000) + $"{i}/{j}", Age = i });
Console.Write($"{new FileInfo(path).Length / 1024} "); // after Dispose
}
// 3072 4096 5120 6144 7168 8192 9216 10240 11264 12288 (KiB), about 400 KB of documents
await reopened.Storage.TruncateToMinimumAsync(); // still 12288 KiB
With a bare StorageEngine that writes one page per open, PageCount after each of ten cycles is 66 129 193 257 321 385 449 513 577 641: 64 pages, one growth block, per reopen.
Expected: a closed and reopened database allocates from where it left off; pages never handed out stay available; the file grows with the data, not with the number of opens.
Actual: one growth block per reopen. In the model test, a reopen right after the index is created leaves 126 of 256 pages neither reachable nor free.
Mechanism (verified on 0450b97)
- Allocation appends at
_nextPageId, and the file grows ahead of it in whole blocks (EnsureCapacityCore,src/BLite.Core/Storage/PageFile.cs:770-773,AlignToBlockat:369); the pages between_nextPageIdand the end of the file are allocated later, in this session. Opensets_nextPageIdto the length of the file (PageFile.cs:483), so the next session starts allocating after the block's unwritten tail. Nothing records where the previous session stopped (the header page holds the free-list head, not the high-water mark).TruncateToMinimumAsynctrims trailing pages only while their header saysFree(PageFile.cs:1347); the stranded pages were never written and sayEmpty, so it stops at once.
Affected versions: Open has derived the next page from the file length since 4236d9e (v1.0.0, then with a fixed InitialFileSize); growth by blocks came with 237f9a9 (2026-02-24, v1.8.0), from which every reopen strands up to one block. Reproduced on v5.2.3 / 0450b97. An encrypted file grows page by page (PageFile.cs:770) and is not affected.
Fix considerations
- Persist the high-water mark in the file header next to the free-list head and restore
_nextPageIdfrom it on open; or - on open, walk back from the end over pages whose header is
Emptyand either lower_nextPageIdor push them onto the free list; or - truncate the file to
_nextPageIdon a cleanDispose.
The second also repairs files that are already affected, and TruncateToMinimumAsync could treat trailing Empty pages like Free ones. In multi-process mode another process may have allocated past this one's view (PageFile.cs:1076-1087), so the fix has to respect the shared allocation state.
- Dominant language
- C#
- Stars
- 66
- Forks
- 6
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 43
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
-
A DocumentDbContext whose class name does not end in "Context" gets no generated code and no diagnostic; its collections stay nullPossibly taken A pull request linked to this issue is open or already merged. Openbug package/generator triage
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
GdprMode.Strict warns that deleted documents are not zeroed, but every delete zero-fills themPossibly taken A pull request linked to this issue is open or already merged. Openbug gdpr package/core triage
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
QueryIndexAsync rejects a DateTime boundPossibly taken @CaffeinatedCoder claimed this 1 day ago. Openarea/indexing bug package/core triage
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
EntglDb/BLite#270 · 1 comment ·
Maintainers usually reply within 1 day
-
BLQL compares two Int64 values through doublePossibly taken @CaffeinatedCoder claimed this 1 day ago. Openarea/linq bug package/core triage
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
EntglDb/BLite#268 · 1 comment ·
Maintainers usually reply within 1 day
-
Collection-per-file mode: creating a collection can route another collection's page to the main file (lock-free read of a Dictionary)Possibly taken @CaffeinatedCoder claimed this 1 day ago. Openbug package/core severity/medium triage
Difficulty 4/5 3-5 days Newbie friendliness 58/100
EntglDb/BLite#312 · 1 comment ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
nightscout/nocturne#2135 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
Devolutions/UniGetUI#5541 ·
Maintainers usually reply within 1 day
-
agentic-workflows Needs: Triage :mag:
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
[誤判定]Openlinux アプリ: ブラウザー 再現済み 要トリアージ 誤判定
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
yksr-melt/Meltype#853 · 2 comments ·
Maintainers usually reply within 1 day