BLite.Wasm: synchronous waits block or kill the browser runtime (HasIndex open, sync LINQ/BLQL ToList, Dispose, IsAvailable)
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- csharp, javascript, wasm
Research direction
Read src/BLite.Core/Collections/DocumentCollection.cs, src/BLite.Core/Query/BTreeQueryProvider.cs, src/BLite.Core/Query/Blql/BlqlQuery.cs, src/BLite.Core/Storage/StorageEngine.cs, and the IsAvailable implementations in src/BLite.Wasm/Storage. Start with the browser cases BW2, BW3, BW4, and BW7 in tests/BLite.Wasm.BrowserTests; run those cases across the affected publish variants and backends. Done means indexed context opening, supported sync operations, disposal, and availability probes behave as specified without blocking or terminating the browser runtime.
Written by the indexing model from the issue text.
Description
Four entry points that are safe on desktop block, throw or kill the runtime in the browser, where .NET runs on one thread and a blocking wait on a thread-pool task can never be satisfied (PlatformNotSupportedException: Cannot wait on monitors on this runtime). Each one is a Task.Run(...).GetAwaiter().GetResult() or a Task.Wait in BLite.Core, or a [JSImport] whose module is not loaded yet, in BLite.Wasm. The README's "Async-Only" line covers CRUD, which works; these are the paths around it.
Found by the browser harness (tests/BLite.Wasm.BrowserTests, https://github.com/EntglDb/BLite/pull/295), which carries the affected rows as known candidates BW2, BW3, BW4 and BW7 until this is fixed. Same result in all four publish variants, on all three backends, including the in-memory one, so it is the runtime, not the storage.
1. A typed DocumentDbContext with any declared index cannot open (BW2)
Rows Typed_context_with_async_linq InMemory/indexed=True, Typed_write_survives_reload IndexedDb/checkpoint=True/indexed=True.
Constructing a context whose OnModelCreating declares HasIndex throws on an empty, newly created database:
InvalidOperationException: Cannot build index 'idx_Value' of collection 'items' from the documents already in it: Cannot wait on monitors on this runtime. Nothing was saved, but every open fails the same way …
The same entity without the index works (Typed_context_with_async_linq */indexed=False passes on all three backends, and the write survives a reload after a checkpoint).
Mechanism: ApplyIndexBuilder fills the new index with Task.Run(() => FillUnregisteredIndexAsync(index, ct)).GetAwaiter().GetResult() (src/BLite.Core/Collections/DocumentCollection.cs:1233); the rebuild path for a changed definition does the same (:927). The comment says the Task.Run is there so a captured SynchronizationContext cannot deadlock the wait; on the browser runtime the wait itself is rejected. The message also misleads: the collection holds no documents.
Expected: a typed context with model-declared indexes opens on a browser backend; the generated context is the path WASM_SUPPORT.md's DocumentDbContext(StorageEngine) constructor exists for.
2. Synchronous LINQ and BLQL ToList throw (BW3)
Rows Typed_sync_linq_operator InMemory/* (tolist, count, first, any, foreach), Blql_sync_operator InMemory/tolist.
Every synchronous System.Linq terminal operator on collection.AsQueryable() throws PlatformNotSupportedException: Cannot wait on monitors on this runtime. BLQL ToList() throws the same; BLQL Count(), FirstOrDefault() and Any() pass.
Mechanism: BTreeQueryProvider.Execute runs the async path through Task.Run(...).GetAwaiter().GetResult() (src/BLite.Core/Query/BTreeQueryProvider.cs:95), and so does BlqlQuery.AsEnumerable (src/BLite.Core/Query/Blql/BlqlQuery.cs:146). The three BLQL operators that pass call XxxAsync().GetAwaiter().GetResult() without Task.Run; they work only because, with every page cached in memory, the async path completes without ever yielding. A backend that awaited I/O inside a read would make them fail too.
Expected: either the synchronous operators work in the browser, or they throw a clear NotSupportedException naming the async operator to use, and the docs say so.
3. Every Dispose freezes the tab for 5 seconds (BW4)
Row Dispose_returns_promptly; visible in every scenario that disposes an engine (5.0–5.2 s against 40–100 ms for the same work without Dispose).
Mechanism: StorageEngine.Dispose waits for the group-commit writer with _writerTask?.Wait(TimeSpan.FromSeconds(5)) (src/BLite.Core/Storage/StorageEngine.cs:462, also :369). The writer can only finish on the thread that is waiting for it, so the wait always runs to its timeout, with the browser's only thread blocked and the page unresponsive. The pending-free checkpoint on dispose (:498, Task.Run(() => CheckpointAsync()).GetAwaiter().GetResult()) would throw the same way; it is caught, and the pages stay leaked.
Expected: Dispose returns in milliseconds, or BLite.Wasm offers DisposeAsync and the docs say to use it.
4. IsAvailable() as the first call kills the .NET runtime (BW7)
Rows IsAvailable_before_any_other_call IndexedDb, Opfs.
Calling the public IndexedDbPageStorage.IsAvailable() or OpfsPageStorage.IsAvailable() before BLiteWasm.CreateAsync or DetectBestBackendAsync does not throw: the runtime asserts and exits, and every later .NET call in the tab fails.
MONO_WASM: Assert failed: ES6 module ./blite-indexeddb.mjs was not imported yet, please call JSHost.ImportAsync() first in order to invoke idbIsAvailable
MONO_WASM: Assert failed: ES6 module ./blite-opfs-bridge.mjs was not imported yet, please call JSHost.ImportAsync() first in order to invoke opfsWorkerIsAvailable
Mechanism: both methods call a [JSImport] function (src/BLite.Wasm/Storage/IndexedDbPageStorage.cs:60, OpfsPageStorage.cs:61) whose module only EnsureLoadedAsync imports; OpfsPageStorage's own doc comment says "Use IsAvailable to feature-detect at runtime", and WASM_SUPPORT.md lists IsAvailable as the feature probe.
Expected: a feature probe returns false or throws; it does not take the runtime down. Either make the probes IsAvailableAsync (import, then probe), or make the synchronous ones return false when the module is not loaded.
Affected versions: BLite.Wasm since #63 (830e83e, v4.4.0) through v5.2.3; the blocking waits in BLite.Core since the index fill of #151 (e187bd6, v5.2.0) and the query provider's Task.Run (v4.x).
Fix considerations
The four share one rule: on OperatingSystem.IsBrowser() nothing may block on a task. The index fill has an async caller already (FillUnregisteredIndexAsync); exposing the context's index build as part of an async open (DocumentDbContext.InitializeAsync, or building model indexes lazily on first async access) removes the wait. The LINQ provider can throw NotSupportedException with the async operator's name in the browser instead of PlatformNotSupportedException from inside the runtime. Dispose can skip the writer wait when the writer cannot run (the channel is completed, nothing is pending), or BLite.Wasm can add DisposeAsync. The probes can become async.
Related: #57 / #63 (BLite.Wasm), #151 / #156 (the index fill on open), #209 / #242 (the checkpoint on dispose). The other issues from the same harness: #289, #291, #292, #293.
- 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
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
-
[Bug]: Revit selector reads process ids 2000-2099 as years (flaky AgentClientTests.AYearOrAProcessIdSelectsTheHost)Possibly taken @Wurschdhaud claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
pyrevitlabs/pyRevit#3817 ·
Maintainers usually reply within 1 day
-
bug effort:S P2
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
nightscout/nocturne#2449 ·
Maintainers usually reply within 1 day
-
Python: Rejected system messages leave AgentChat permanently activePossibly taken @Michael-Duck claimed this today. Openpython triage
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
microsoft/semantic-kernel#14638 ·
Maintainers usually reply within 3 days
-
audit:1 bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
NethermindEth/dotnet-libp2p#395 ·
Maintainers usually reply within 1 day