Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Every reopen strands the unwritten rest of the last page growth block

Open
#258 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
csharp
Domain
database

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

area/indexing bug package/core triage

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, AlignToBlock at :369); the pages between _nextPageId and the end of the file are allocated later, in this session.
  • Open sets _nextPageId to 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).
  • TruncateToMinimumAsync trims trailing pages only while their header says Free (PageFile.cs:1347); the stranded pages were never written and say Empty, 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 _nextPageId from it on open; or
  • on open, walk back from the end over pages whose header is Empty and either lower _nextPageId or push them onto the free list; or
  • truncate the file to _nextPageId on a clean Dispose.

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

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from EntglDb/BLite

All issues in EntglDb/BLite

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.