[Design reference — not for merge] Pump-based, fast-path-free ChannelDbConnectionPool#4446
Draft
mdaigle wants to merge 1 commit into
Draft
[Design reference — not for merge] Pump-based, fast-path-free ChannelDbConnectionPool#4446mdaigle wants to merge 1 commit into
mdaigle wants to merge 1 commit into
Conversation
Remove the optimistic fast path so every request queues on the idle/creation channel, giving strict FIFO ordering to parked waiters. Connection creation is decoupled from the requesting caller and runs on background "pump" tasks that grow the pool as demand and capacity allow. Creation results (connection, error, or bare wake) travel over the channel via a new CreateOutcome payload; a background creation error is delivered to a waiter and rethrown there. - Add CreateOutcome struct (connection | error | bare wake) as the channel payload. - Widen IdleConnectionChannel to carry CreateOutcome; Count tracks connection- bearing outcomes only. - Add _waiterCount / _pendingCreates accounting and TryPumpCreate / LaunchCreate. - Rewrite GetInternalConnection to register demand, pump, then block on the channel. - Background creates use the pool's CreationTimeout (not the caller's budget); a caller's ConnectTimeout governs only its channel wait. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refactors ChannelDbConnectionPool to a pump-based model where all connection requests queue through a single idle/creation channel, enabling strict FIFO ordering for contended waiters and moving physical connection creation onto background pump tasks.
Changes:
- Introduces
CreateOutcome(connection | error | bare wake) and widens the idle channel to carry outcomes instead of nullable connections. - Reworks
ChannelDbConnectionPool.GetInternalConnectionto pump connection creation on background tasks and deliver results/errors via the channel. - Updates/extends connection pool unit tests to validate pump behavior and
CreateOutcomesemantics.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs | Implements the pump-based queuing/creation model and error delivery via CreateOutcome. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs | Changes the channel payload to CreateOutcome and updates count semantics to track only connection-bearing outcomes. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/CreateOutcome.cs | Adds the new outcome payload type used to convey connection/error/wake signals through the channel. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs | Adds pump-behavior tests (parallel create, error delivery, doomed-return wakeup) and updates timeout expectations for pump creation. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs | Updates idle channel tests to use CreateOutcome and validate count behavior across outcomes. |
Comment on lines
+699
to
+701
| LaunchCreate(owningConnection); | ||
| } | ||
| } |
4 tasks
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.
Status: design reference, not intended to merge
This PR is kept open as a documented design alternative for the rate-limiting work (originally explored for #4396). It implements a pump-based, fast-path-free connection pool. After evaluation we decided not to adopt it, because the rate limiting we actually need is a concurrency limiter, which does not have the problem this design solves. See Why it isn't suitable below. Reference this PR from the rate-limiting PR to show what was considered and why it was rejected.
What it does
Reworks
ChannelDbConnectionPoolso that every request queues on the idle/creation channel — there is no optimistic fast path. Physical connection creation is decoupled from the requesting caller and runs on background pump tasks that grow the pool as demand and capacity allow. Creation results travel over the channel via a newCreateOutcomepayload (connection | error | bare wake).CreateOutcomepayload carried over the channel.defaultis a bare wake that nudges a parked waiter to re-evaluate.IdleConnectionChannelwidened to carryCreateOutcome; itsCounttracks connection-bearing outcomes only._waiterCount,_pendingCreates) plusTryPumpCreate(cheap synchronous demand+capacity gate) andLaunchCreate(backgroundTask.Runcreate that publishes its result to the channel).GetInternalConnectionrewritten: a warmTryReadstill serves buffered connections fast; on a miss the request registers demand (_waiterCount++), pumps, then blocks on the channel until an outcome arrives.Why this design exists
It targets a generic / time-based rate limiter (e.g. token bucket). With a time-based limiter, tokens replenish on a timer, so there is no event to signal a parked waiter when capacity becomes available. The existing pool relies on writing a bare wake to the channel whenever a slot frees (connection returned/removed); a timer-replenished limiter has no such moment, so a waiter can starve (a lost-wakeup problem). The pump solves this by continuously re-driving demand against capacity from background tasks and a
_waiterCount/_pendingCreatesgate, independent of any per-connection event.Why it isn't suitable
The rate limiting we actually need (pooling against on-prem SQL Server) is a concurrency limiter, not a time-based one. A concurrency limiter does not have the lost-wakeup problem: token availability is lease-release, which is a concrete event. That maps directly onto the pool's existing "write a wake to the channel when a slot frees" model — the limiter simply writes a wake on lease-release. So the central problem this PR solves does not exist for our use case, and the pump's machinery buys us nothing on that axis.
Meanwhile, adopting the pump has real costs relative to the current (caller-creates-inline) model:
ReadChannelSyncOverAsync(sync-over-async, with a thread-pool hop) — strictly more overhead for the pure-sync path._waiterCount/_pendingCreatesaccounting and a subtle lost-wakeup ordering argument (waiter increments_waiterCountbefore parking; slot-freeing events free the slot before reading_waiterCount). This is a lot of concurrency-critical surface area to maintain.Conclusion
For a concurrency limiter, integrate
AcquireAsync/lease-release directly into the existing pool's create path and write a wake to the channel on lease-release — no pump required. This preserves per-caller error precision and the inline sync path, and avoids the added complexity. This PR remains as a reference for what a time-based-limiter design would have looked like, should that need ever arise.Testing (for the record)
ChannelDbConnectionPoolTest/IdleConnectionChannelTestupdated forCreateOutcome; added pump-behavior tests (parallel creates up to max, sync + async creation-error delivery, doomed-return unblocks waiter).ConnectionPoolunit tests pass. The only failing unit tests in the suite are pre-existing, environment-specific ones (AlwaysEncrypted cert store + integrated auth) that also fail on a clean checkout.