Skip to content

Do not send a stale parameter name for an AddParams member the procedure does not declare - #135

Merged
electricessence merged 2 commits into
masterfrom
fix/pooled-param-slot
Nov 14, 2026
Merged

electricessence merged 2 commits into
masterfrom
fix/pooled-param-slot

Conversation

@electricessence

Copy link
Copy Markdown
Collaborator

Fix: an AddParams member that the procedure (or the text) does not declare is no longer sent under a stale name left in the process-wide ArrayPool<string?>.

Resolving the names of an AddParams object keeps the name each parameter ends up with in an array rented from ArrayPool<string?>.Shared. For a member that matched nothing, the first pass moved on without writing its slot, and the later passes read the slot anyway: whatever an earlier renter had left there was sent as the parameter name (or made two unmatched members collide). A text command with @name markers takes the same path.

What changed

  • ExpressiveCommandBase.Apply: the slot of an unmatched AddParams member is written (null: not sent) before it is read. One line. The clearing policy is unchanged (the used prefix is cleared on return).
  • PooledNameSlotTests (7 tests): fill several arrays of the pool with a sentinel name and return them without clearing, between AddParams and the terminal, then run the command:
    • stored procedure, unmatched member only: sync and async;
    • stored procedure, matched and unmatched members: sync and async;
    • text command with @name markers, unmatched member: sync and async (ExpressiveCommand, ExpressiveDbCommand);
    • text command, only unmatched members: sync.
  • Audit of every .Rent( site in Source/ (13): only this one read a slot before it was written. The other twelve write every slot they read (or hand the array to a caller under a documented contract). No other change.

Verification

Tests first, then the fix (two commits), Tests/Open.Database.Extensions.Core.Tests, net10.0 and net11.0.

  • Before the fix: dotnet test Tests/Open.Database.Extensions.Core.Tests -c Release --filter FullyQualifiedName~PooledNameSlotTests --nologo: Failed 7, Passed 0 on both targets (for example Collection: ["@stale"] where an empty list is expected).
  • With the fix, same command: Passed 7, Failed 0 on both targets.
  • dotnet test Tests/Open.Database.Extensions.Core.Tests -c Release --nologo: net10.0 Passed 10200 of 10200; net11.0 Passed 10200 of 10200.
  • dotnet test Tests/Open.Database.Extensions.Core.Tests -c Debug --nologo (so Debug.Assert fires): net10.0 Passed 10200 of 10200; net11.0 Passed 10199, Failed 1: SingleValueAllocationTests.ToArrayAndToListOfInt_AllocateNoMoreThanASelector(rows: 100) ("3890 B per call through the terminal, 1936 B by hand"). That test passes when run on its own (--filter FullyQualifiedName~SingleValueAllocationTests, Debug, net11.0, 12 of 12) and does not touch name resolution. It was not run against the unmodified tree.
  • Release build and format check (style and analyzers): 0 warnings, 0 errors, no format findings.

Not done / decisions

  • The one-off Debug failure above is reported, not fixed (an allocation measurement under a full parallel run). It is known to be intermittent on net11.0 and is being investigated separately, together with the other allocation test that fails on net11.0 Release only.
  • Known and tracked separately: MappedQuerySelectTests.TheAsyncProjection_AllocatesNothingPerRow fails intermittently on net11.0 Release (it did in this branch's full gate run: 10199 of 10200 passed). It was already known before this branch and is in code this change does not touch.
  • No clearing-policy change and no API change.
  • Not changed: the tail of a longer rented object[] that GetValues leaves unwritten (documented on the public GetValues(record, ..., pool) methods; the library reads only the field count).

🤖 Generated with Claude Code

electricessence and others added 2 commits November 14, 2026 13:44
…tied

The pool is filled with a sentinel name and returned without clearing before the command resolves its names. Stored procedure and text command, synchronous and asynchronous, unmatched-only and mixed members. They fail until the slot of an unmatched member is written.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ure does not declare

The names a command sends are resolved into a string?[] rented from ArrayPool<string?>.Shared, which is process-wide. For a member of an AddParams object that matched none of the declared names, the loop moved on without writing its slot, and the later passes then read whatever name the previous renter had left there. The member was sent under that stale name, or two such members collided on it. A text command with @name markers took the same path.

Write the slot (null: not sent) for every parameter before it is read. Regression tests dirty the shared pool without clearing it and run the command, for a stored procedure and a text command, synchronously and asynchronously, with unmatched-only and mixed members. IN1 audit of the other pool Rent sites found no other slot read before it is written.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@electricessence
electricessence merged commit 566f17f into master Nov 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant