Repository navigation
docs(h28): resolve the DbCommand residual - #399
Merged
Merged
Conversation
H-28 (research/ownership-semantics-lab-v1 @ 298b305, not on main) settled DataTable.Load(reader) with a runtime falsifier: the reader IS closed, so the presumed BORROW was refuted and the demanding instance (victor-wiki/DatabaseManager DbInterpreter.GetDataTableAsync:699) is not a protocol leak. The same record then wrote one sentence and never classified it: "what remains is the never-disposed DbCommand `cmd` (an object leak without a protocol consequence)" That sentence is a description, not a verdict: it was never falsified, never entered the witnessed-callee table, never reached a gate. This closes it. VERDICT: H28-PROVIDER-SPECIFIC. The absence of DbCommand.Dispose() in this family is not one fact. * Npgsql 10.0.3 (and main, identical): NpgsqlCommand has no finalizer, both ctors GC.SuppressFinalize, and Dispose(bool) only nulls _transaction, sets a managed state flag and optionally recycles the command into the connection's one-slot CachedCommand. Reset() is managed-only. Server-side prepared statements belong to NpgsqlConnector.PreparedStatementManager and are never DEALLOCATEd by the command. => releases nothing. * Microsoft.Data.SqlClient, MySqlConnector: managed-only Dispose, no finalizer. => releases nothing. * Microsoft.Data.Sqlite: SqliteCommand.Dispose -> DisposePreparedStatements finalizes native sqlite3_stmt SafeHandles (ReleaseHandle -> sqlite3_finalize) AND disposes its reader. A plain ExecuteReader() runs PrepareAndEnumerateStatements(), which puts those handles on the COMMAND; SqliteDataReader.Close()/Dispose() does not finalize them. => the command is the only deterministic releaser. REAL defect. So the old residual is true for Npgsql and false for Microsoft.Data.Sqlite; it was written from a two-provider falsifier whose SQLite arm never looked at the command. Sharper witness found on the way (pivot recorded in the report): the ownership relation connection -> command -> reader is not what either side assumes. NpgsqlCommand.CurrentActivity (System.Diagnostics.Activity IS IDisposable in .NET 8 and 10) is stopped by exactly two call sites repo-wide, both in NpgsqlDataReader.Dispose/DisposeAsync -> Command.TraceCommandStop(). Neither NpgsqlDataReader.Close() nor NpgsqlCommand.Dispose() calls it — and DataTable.Load calls Close(). With a tracing listener attached the span never ends. Meanwhile reader Cleanup sets Command.State = Idle, which makes the command reusable: that is not ownership. CURRENT OWN.NET (extractor @ e889f8b, source-derived; engine executed): IsOwningFactory mints an owned local for IDbConnection.CreateCommand() (L4880-4882 -> L7035), IsDisposeOptional does not cover DbCommand (L2593), and the argument-escape rule untracks the reader at table.Load(reader) (L7155-7182). Predicted: OWN001 (error) on `command`, silence on `reader`. The engine half reproduces exactly that (evidence/engine-CommandDispose.txt: 2x command in B/E + 1x reader in E, A/D clean). On Npgsql with tracing on the verdict is inverted — error on the object whose Dispose frees nothing, silence on the one whose disposal is load-bearing. The escape drop is also only accidentally right: DataTable.Load closes the reader IFF !reader.NextResult(), so a multi-result-set reader is left OPEN and the drop is a false negative there. NO PRODUCTION CHANGE. Nothing here touches the extractor, the core, the vocabulary or a diagnostic. corpus/ownership-lab/ is not globbed by tests/test_corpus.py (still 33/33). The model gap is shown to be an instance of #382 — which already names this lowering and this exact loss — with proven_call (OwnIR v2, H1) as the landed transport precedent; section 10 proposes the smallest experiment and explicitly not a lattice, a new code, or provider special-casing. Deliverables: docs/notes/h28-npgsql-command-resolution.md plus corpus/ownership-lab/h28-command/ — fx/CommandDispose.cs (9 variants, abstract ADO.NET types on purpose: that is what IsOwningFactory matches), a runnable falsifier pinned to net8.0/Npgsql 10.0.3/MS.Data.Sqlite 10.0.12 with probes P1-P6 each chosen to separate two hypotheses, and 32 source citations auto-derived from pinned refs (blob sha + sha256 + line range) by scripts/derive_source_evidence.py, which exits non-zero when the note goes stale. Honest limits, stated in the report rather than papered over: there is no .NET SDK and no reachable NuGet feed in the producing sandbox, so the Roslyn extractor and the runtime falsifier were NOT executed. fx/CommandDispose.cs expectations and probes P2/P4/P6 are marked PREDICTED; run.sh runtime prints SKIP-WHY instead of faking it. What WAS executed: the engine half, all 32 source derivations, and a live PostgreSQL 16.2 (pgserver) used to pin that prepared statements and cursors are session-scoped — independently corroborating that no command-level Dispose can be their releaser. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DataTable.Load, reader disposal, and command disposal; P6 directly counts live SQLite statements withsqlite3_next_stmt(including its SQLitePCLRaw enablement and same-command reuse arm), with no RSS or forced GC.DEALLOCATE. OWN001 remains a possible-leak diagnostic, not a proof of native leakage or a confirmed false positive.Validation
tests/test_corpus.py: 33/33; whole-tree Ruff and mypy: pass.run.sh all-requiredcorrectly returned non-zero on the G3 and runtime SKIPs; seecorpus/ownership-lab/h28-command/evidence/required-gates-local.txt. The CI workflow is the required reproducible run.python tests/run_tests.pydid not complete: the checkout is shallow and Cargo is unavailable. The transcript is saved incorpus/ownership-lab/h28-command/evidence/local-validation.txt.No #382 architecture work is proposed or claimed from this report. Leave the PR open pending the required gate results.