[DO NOT MERGE] Enable ConnectionPoolV2 by default - #4537
Conversation
There was a problem hiding this comment.
Pull request overview
This PR flips the default of the internal Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2 AppContext switch to true, making the Channel-based pool (ChannelDbConnectionPool) the default connection pooling implementation while keeping the legacy pool (WaitHandleDbConnectionPool) available via explicit opt-out.
Changes:
- Change
LocalAppContextSwitches.UseConnectionPoolV2default fromfalse→true, and update its XML doc accordingly. - Update the unit test that asserts the default switch values.
- Update the internal feature documentation table for the switch default.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs | Flips UseConnectionPoolV2 default to true and updates XML documentation. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs | Updates default-value assertion to expect UseConnectionPoolV2 == true. |
| .github/instructions/features.instructions.md | Updates the documented default for UseConnectionPoolV2 in the AppContext switches table. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| | `Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour` | `false` | Uses legacy async behavior for compatibility | | ||
| | `Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni` | `false` | Uses legacy SNI processing path | |
|
CI investigation: the two failing legs ( This is not a pre-existing flake: the same test passes on #4504's CI (same base branch, Root cause: Per this PR's stated purpose, I'm not fixing pool internals here — documented the gap in the PR body so the pool-v2 workstream can add reclaim-on-open support to |
Broader CI update: failures across nearly the full manual-test matrixAs more legs of 1.
|
Flip the default value of the UseConnectionPoolV2 AppContext switch from false to true, making the new Channel-based connection pool (ChannelDbConnectionPool) the default implementation. The legacy V1 pool (WaitHandleDbConnectionPool) remains available by explicitly setting the switch to false. - Update XML doc comment on the switch to reflect the new default - Update features.instructions.md default value table - Update LocalAppContextSwitchesTest default-value assertion Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
4e920b5 to
6c1ea47
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/instructions/features.instructions.md:257
- The AppContext switch default-value table is now inconsistent with the actual defaults in
LocalAppContextSwitches.cs:UseCompatibilityAsyncBehaviourandUseCompatibilityProcessSniboth default totrue(compatibility mode), but the table still listsfalse. Since this PR already edits this section, please update these rows so the table reflects real defaults and explains that setting them tofalseenables the newer behaviors.
| `Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour` | `false` | Uses legacy async behavior for compatibility |
| `Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni` | `false` | Uses legacy SNI processing path |
| `Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2` | `true` | Enables the new `ChannelDbConnectionPool` implementation; set to `false` to restore the legacy `WaitHandleDbConnectionPool` |
This PR is not merge-ready as-is — it intentionally flips a default to exercise the full CI matrix against
ChannelDbConnectionPooland surface any remaining v1/v2 pool gaps before that pool becomes the shipped default. CI is doing exactly that:PR-SqlClient-Project's shard-3 test group and the newersqlclient-prpipeline).ConnectionPoolTest.ReclaimEmancipatedOnOpenTest—ChannelDbConnectionPool(V2) has no reclaim-on-open path for "emancipated" (GC'd-without-Dispose) connections, unlike legacyWaitHandleDbConnectionPool(V1)'sReclaimEmancipatedObjects(). Confirmed via Emit pool metrics and traces, and fix Count semantics in ChannelDbConnectionPool #4504's own CI (same base branch, switch stillfalse) that this test passes there, so it is caused specifically by this PR's default flip, not flakiness.TvpTest.TestPacketNumberWraparoundfails consistently on localhost-only legs, with a signature suggesting an early task fault (possibly a knock-on effect of the pool issue above, not fully root-caused).Summary
Flips the default value of the
UseConnectionPoolV2AppContext switch fromfalsetotrue, making the new Channel-based connection pool (ChannelDbConnectionPool) the default implementation for all connections. The legacy V1 pool (WaitHandleDbConnectionPool) remains fully available by explicitly setting the switch tofalse:Depends on #4504 — this branch is stacked on
dev/automation/channel-pool-v2-followupsand should be merged after it.Purpose
This PR is intended to validate, via CI, that there are no remaining easily identifiable functional gaps between the V1 and V2 connection pool implementations before making V2 the default in a shipped release. Routing the full CI test matrix through
ChannelDbConnectionPoolby default surfaces any behavioral differences that unit/functional/manual tests weren't already covering explicitly for both pool versions.Changes
LocalAppContextSwitches.cs:UseConnectionPoolV2now defaults totrue; XML doc updated to describe the new default and that setting it tofalserestores legacy V1 behavior..github/instructions/features.instructions.md: updated the switch default-value reference table.LocalAppContextSwitchesTest.cs: updated the default-value assertion for this switch fromfalsetotrue.No changes were needed in
LocalAppContextSwitchesHelper.csorConnectionPoolTest.cs(ClearAllPoolsTest) — both already exercise the switch explicitly for bothtrue/falsevalues viaConnectionPoolConnectionStringAndPoolVersionProvider, so v1 pool coverage is preserved.Suggested release note
Testing
dotnet build -t:TestSqlClientUnit -p:TestFilters="FullyQualifiedName~LocalAppContextSwitchesTest|FullyQualifiedName~ConnectionPool"— 332/332 passed on net8.0, net9.0, and net10.0.tests/UnitTests/ConnectionPool/*(Channel*/WaitHandle*/Transacted* test files) — these instantiate pool implementations directly rather than going through the switch, so they are unaffected by the default flip.CI-discovered gaps (genuine v1/v2 behavioral differences, not fixed here — surfacing is this PR's purpose)
CI on this PR surfaced two distinct, consistently-reproducing failures across the manual test matrix (see PR comments for full investigation details):
1.
ConnectionPoolTest.ReclaimEmancipatedOnOpenTest— fails on every azure/localhost leg, all TFMs (net8/9/10/462), Linux + Windows + macOSSystem.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.false).SqlConnectionwrapper goes out of scope and is GC'd without being explicitly closed/disposed), then opens a new connection withMaxPoolSize=1, expecting the pool to reclaim the emancipated internal connection.WaitHandleDbConnectionPool(V1) has an explicitReclaimEmancipatedObjects()routine invoked when the pool is exhausted and a new connection is requested.ChannelDbConnectionPool(V2) has no equivalent active reclamation path — a lone reference toIsEmancipatedexists only in a comment, with no reclaim-on-open logic invoked from the connection-acquisition path.Open()times out instead of reclaiming the abandoned connection.ReclaimEmancipatedObjects-equivalent path toChannelDbConnectionPool, invoked when the pool is exhausted during connection acquisition.2.
TvpTest.TestPacketNumberWraparound— fails on every localhost leg only (not azure, since the test isIsNotAzureServer-gated)enumerator.Count=3, enumerator.MaxCount=1000000, elapsed=00:00:00.0062206.connection.OpenAsync(cancellationToken)(the test's try/catch only wrapsExecuteNonQueryAsync, not theOpenAsynccall, so any exception there — e.g. a pool timeout/error — would abort the task before any enumeration happens).UseConnectionPoolV2=trueto capture the actual faulted exception fromRunPacketNumberWraparound, and determine whether it's connection-pool-related or an unrelated regression.Per this PR's stated purpose, pool internals are intentionally not being fixed here — both findings are being surfaced for the pool-v2 workstream to address before V2 fully replaces V1 as the shipped default.
Checklist