Extract pool blocking-period error state into BlockingPeriodErrorState#4395
Conversation
Move the connection pool's blocking-period (error backoff) logic out of WaitHandleDbConnectionPool into a reusable, testable BlockingPeriodErrorState class with exponential backoff (5s..60s), cached-exception fast-fail, and an injectable TimeProvider for deterministic tests. Add DbConnectionPoolGroup.IsBlockingPeriodEnabled() and the ADP.UnsafeCreateTimer(TimeProvider,...) overload it depends on. Includes BlockingPeriodErrorStateTest.
There was a problem hiding this comment.
Pull request overview
Refactors the legacy wait-handle connection pool’s blocking-period (error backoff) logic into a dedicated BlockingPeriodErrorState component, adds a pool-group helper for determining when blocking is enabled, and introduces a TimeProvider-based timer factory to enable deterministic unit testing.
Changes:
- Added
BlockingPeriodErrorState(cached exception + exponential backoff + exit timer) and corresponding unit tests usingFakeTimeProvider. - Refactored
WaitHandleDbConnectionPoolto delegate blocking-period logic to the new state object and moved the blocking-period enablement decision intoDbConnectionPoolGroup. - Added an
ADP.UnsafeCreateTimer(TimeProvider, ...)overload returningITimerto supportTimeProvider-driven timers.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/BlockingPeriodErrorStateTest.cs | Adds comprehensive unit tests for the new blocking-period state logic using FakeTimeProvider. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs | Replaces inlined error/backoff fields and timer logic with BlockingPeriodErrorState. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs | Centralizes the PoolBlockingPeriod decision into IsBlockingPeriodEnabled(). |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/BlockingPeriodErrorState.cs | Introduces the new reusable blocking-period error state implementation. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs | Adds a TimeProvider-aware UnsafeCreateTimer overload returning ITimer. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4395 +/- ##
==========================================
- Coverage 65.78% 63.97% -1.81%
==========================================
Files 286 283 -3
Lines 43696 66728 +23032
==========================================
+ Hits 28745 42690 +13945
- Misses 14951 24038 +9087
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
paulmedynski
left a comment
There was a problem hiding this comment.
Latest changes look good. Waiting for further offline discussion on my earlier comments.
…m:dotnet/SqlClient into dev/mdaigle/pool-blocking-period-refactor
#4395) * Extract pool blocking-period error state into BlockingPeriodErrorState Move the connection pool's blocking-period (error backoff) logic out of WaitHandleDbConnectionPool into a reusable, testable BlockingPeriodErrorState class with exponential backoff (5s..60s), cached-exception fast-fail, and an injectable TimeProvider for deterministic tests. Add DbConnectionPoolGroup.IsBlockingPeriodEnabled() and the ADP.UnsafeCreateTimer(TimeProvider,...) overload it depends on. Includes BlockingPeriodErrorStateTest. * Add comments * Improve locking * First round of review changes. * Require exhaustive switch statements. * Add wait handle pool unit tests for blocking period. * Remove rule. * Improve tests. Add TODOs. * Update test names and xml. * Add documentation about callback idempotency. * Fix xml docs * Add default switch case. * Swallow errors. Fix disposal. * Fix test conflicts * Simplify enter/exit API. * Restore original behavior. * Revert editorconfig changes.
Summary
This is Part 1 of 3 splitting #4376 ("Dev/mdaigle/pool rate limit") into stacked, reviewable PRs.
This PR extracts the connection pool's blocking-period (error backoff) logic out of
WaitHandleDbConnectionPoolinto a reusable, testableBlockingPeriodErrorStateclass. This keeps the pool's connection-acquisition path focused on capacity/queue concerns. Part 2 will use the new class to implement blocking period support in the ChannelDbConnectionPool.The pool blocking period follows this state machine:

The pool calls
Enter()any time blocking is enabled and an error occurs while opening a connection. This puts the pool in the blocked state and blocks future requests from attempting an open until the period ends. Subsequent errors double the blocking period (up to a 60s max). Successful connections reset the error state and blocking period.To show that nothing changed for the existing pool, I added new unit tests and applied them selectively in a separate branch. If these pass, then the refactors have not impacted behavior: #4411
Changes
BlockingPeriodErrorState— encapsulates cached-exception fast-fail, exponential backoff (5s → 60s cap), exit timer, and synchronization. Takes an injectableTimeProviderso timer scheduling is deterministic in tests.WaitHandleDbConnectionPool— refactored to useBlockingPeriodErrorStateinstead of inline error-state fields/logic.DbConnectionPoolGroup.IsBlockingPeriodEnabled()— new helper centralizing thePoolBlockingPerioddecision (Auto→ not an Azure SQL endpoint,AlwaysBlock→ true,NeverBlock→ false). Consumed by Part 2.ADP.UnsafeCreateTimer(TimeProvider, ...)— new overload returningITimerthat suppressesExecutionContextflow while honoring the injectedTimeProvider; required byBlockingPeriodErrorState.BlockingPeriodErrorStateTest— unit tests usingFakeTimeProvider(29 tests).Stacking
mainChecklist
FakeTimeProvider)