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

An engine and a session hold separate DynamicCollection instances: an index created through one misses the other's writes, and a session commit can erase the index definition

Open
#305 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 2 days

Nobody has claimed this yet.

Assessment

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

Research direction

Start with src/BLite.Core/BLiteEngine.cs, src/BLite.Core/BLiteSession.cs, and src/BLite.Core/DynamicCollection.cs, focusing on collection construction, secondary-index state, metadata persistence, and write locks. Use the issue's repro to compare engine and session collection views, then verify that index queries match full scans and index definitions survive reopening. Locate the collection and session tests and add coverage for cross-view writes, commits, index creation, and reopen behavior.

Written by the indexing model from the issue text.

Description

area/indexing bug package/core severity/medium

BLiteEngine.GetOrCreateCollection("people") and BLiteSession.GetOrCreateCollection("people") return two different DynamicCollection objects for the same collection. Each has its own list of secondary indexes and its own write lock. When the session's instance exists before an index is created through the engine:

  1. writes through the session are not added to the index, so an index query misses them, permanently;
  2. the session's commit rewrites the collection's index metadata from its own (empty) list, so unless the engine commits again afterwards, the index definition is gone after a reopen.

Found while reviewing the collection layer for #296.

Repro

using var engine = new BLiteEngine("d2.db");
var col = engine.GetOrCreateCollection("people");
await col.InsertAsync(col.CreateDocument(["age"], b => b.AddInt32("age", 1)));

using var session = engine.OpenSession();
var scol = session.GetOrCreateCollection("people");        // second instance
await col.CreateIndexAsync("age", name: "idx_age");          // index created on the engine's instance
await col.InsertAsync(col.CreateDocument(["age"], b => b.AddInt32("age", 5)));
await scol.InsertAsync(scol.CreateDocument(["age"], b => b.AddInt32("age", 5)));
await session.CommitAsync();

// col.QueryIndexAsync("idx_age", 5, 5) → 1 document; a full scan finds 2

Then dispose the engine and reopen:

Run After reopen
no session, no engine.CommitAsync() index present, correct
no session, engine.CommitAsync() index present, correct
session, no engine.CommitAsync() ArgumentException: Index 'idx_age' not found on collection 'people'
session, engine.CommitAsync() index present, but it still misses the session's document

Expected: one collection behaves the same whichever object it is reached through: one index list, one lock, one metadata writer.

Actual: two instances with diverging index lists; the last one to persist metadata wins.

Mechanism (0450b97)

  • Separate caches: the engine keeps ConcurrentDictionary<string, DynamicCollection> _collections (src/BLite.Core/BLiteEngine.cs:31, 294); each session keeps its own and builds its own instance (src/BLite.Core/BLiteSession.cs:140-151). GdprDocumentDbContextExtensions.cs:50 builds a third, short-lived one.
  • Each instance has its own _secondaryIndexes (src/BLite.Core/DynamicCollection.cs:97), loaded from metadata when the instance is constructed; an index created later on another instance is never added to it, so its writes skip that index.
  • BLiteSession.CommitAsync calls PersistIndexMetadata() on every collection the session has touched (BLiteSession.cs:95-96). PersistIndexMetadata clears the stored index list and rewrites it from that instance's _secondaryIndexes (DynamicCollection.cs:2177-2181: metadata.Indexes.Clear();), which drops the engine's new index. BLiteEngine.CommitAsync does the same from the engine's instances (BLiteEngine.cs:423-429), which is why committing the engine afterwards restores the definition.
  • Each instance also has its own SemaphoreSlim (DynamicCollection.cs:49), so writes through the two instances are not serialised against each other, while they share the free-space index (_freeSpaceIndexes.GetIndex()).

Affected versions: since BLiteSession (v3.8.0) through v5.2.3.

Fix considerations

  • One instance per collection name per engine: the session returns a view that carries the session's transaction holder but shares the engine's collection state (indexes, lock, metadata). The same for the GDPR helper.
  • PersistIndexMetadata should never derive the stored list from one instance's memory; index creation and drop should update the catalog in the transaction that creates or drops the index.
  • Test: a cross-view oracle — the same operation sequence through engine and session instances in any order, checked against one model (index queries equal full scans, definitions survive reopen).

Related: #296 (one collection registry), #211 (pages of old dynamic indexes), #301 (another typed vs schema-less divergence).

Dominant language
C#
Stars
66
Forks
6
Avg merge
1d 2h
Merged PRs (30d)
58

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.