Repository navigation
Description
Summary
Decide and document what disposing MongoPersistenceEngine means for an asynchronous read that has already started. The current async guard checks IsDisposed once before the operation; it does not recheck between cursor batches. #106 introduces per-batch checks for synchronous enumeration.
Priority: P3 (lifetime-contract/design question). This is not an established correctness defect, cancellation bug, or resource leak. Disposal may intentionally prevent new operations while allowing active reads to finish; the intended contract needs to be explicit before changing behavior.
Evidence and scope
At develop 8f580f1a:
- Dispose sets
IsDisposed; it does not cancel active operations or dispose their cursors. - TryMongoAsync checks the flag before awaiting the guarded callback.
- Async read loops own their cursor with
usingand advance it without another engine-disposal check. - At Fix deferred MongoDB read exception translation #106 head e5ea11b6, the sync iterator and guard check disposal on acquisition and every
MoveNextattempt. This is a boundary check, not immediate cancellation of an in-flight driver call or a check before every document in an already fetched batch.
These differences are confirmed by source inspection. No active-read disposal scenario was executed as part of this filing.
Deterministic characterization scenario
- Return a fake async cursor with at least two batches and observable acquisition, advancement, callback, and disposal counts.
- Pause the observer after receiving the first batch, call
engine.Dispose(), then release the observer so the read can request the next batch. - Record whether another
MoveNextAsyncoccurs, which records are delivered, the terminal notification/task result, and whether the cursor is disposed exactly once. - Separately gate acquisition and an in-flight advancement, disposing while each is pending. Avoid timing-based sleeps.
- Compare with disposal before invocation and with the synchronous behavior in Fix deferred MongoDB read exception translation #106.
Current async source permits continued advancement after disposal if the driver and observer continue normally. That observation alone does not choose the desired contract.
Decision and acceptance criteria
- State whether active async reads may finish, fail at a defined boundary, or require callers to cancel/await them before disposing the engine.
- Specify behavior before invocation, during acquisition, within a batch, between batches, and while an observer callback is pending.
- Define the observable task result and observer error/completion policy without conflating disposal with request-token cancellation.
- Document cursor ownership and guarantee cleanup on completion, early observer stop, failure, and cancellation.
- If interruption is desired, assess compatibility for shared engines and concurrent readers. Per-batch checks alone do not promise atomic exclusion of a disposal race or immediate interruption.
- Add deterministic tests for the chosen contract across async observer read families, preserving [P2] Task cancellation is swallowed and CommitAsync completes successfully with null #86/fix: propagate async cancellation and await observer callbacks (#86) #99 cancellation and terminal-callback guarantees. Explain intentional sync/async differences if retained.
Keep this separate from #91/#106's driver-exception translation and from the already-disposed cleanup guard gap in #107. Historical #16 concerned background stream-head writes after disposal, not the active async read contract here.
Co-authored by Alessandro Giorgetti and Codex (OpenAI).
Activity