Skip to content

Reset session isolation level on pool return (fixes #96)#4330

Open
priyankatiwari08 wants to merge 1 commit into
dotnet:mainfrom
priyankatiwari08:feature/sqltransaction-isolation-leak
Open

Reset session isolation level on pool return (fixes #96)#4330
priyankatiwari08 wants to merge 1 commit into
dotnet:mainfrom
priyankatiwari08:feature/sqltransaction-isolation-leak

Conversation

@priyankatiwari08
Copy link
Copy Markdown
Contributor

Fixes #96.

Problem

SqlTransaction and TransactionScope leave the underlying SQL Server
session with the elevated isolation level after Commit/Rollback. Because
sp_reset_connection does not reset the session isolation level, the next
user of the pooled physical connection silently inherits it.

Repro

  • using var c = new SqlConnection(cs); c.Open();
  • using var tx = c.BeginTransaction(IsolationLevel.Serializable); tx.Rollback();
  • Dispose, reopen on the same pooled SPID — sys.dm_exec_sessions.transaction_isolation_level is still 4 (Serializable).

Reproduced on 5.2.2 and 7.0.1 against SQL Server, on both net8.0 and net472.

Fix

  • Track in SqlInternalConnectionTds when a non-default isolation level was set via the TM Begin path (_isolationLevelDirty).
  • In ResetConnection() (called from Deactivate on pool return), if dirty, issue SET TRANSACTION ISOLATION LEVEL READ COMMITTED; via TdsExecuteSQLBatch. The batch piggybacks the queued sp_reset_connection byte on the same TDS header, so there is no extra round trip vs. the legacy reset.
  • On failure, the connection is doomed so it's destroyed instead of pooled.

Kill switch

Switch.Microsoft.Data.SqlClient.UseLegacyIsolationLevelBehavior (default false) restores the previous behavior.

Tests

Adds IsolationLevelLeakTest under ManualTests/SQL/TransactionTest/:

  • SqlTransaction_SerializableDoesNotLeakAcrossPool
  • TransactionScope_SerializableDoesNotLeakAcrossPool
  • LegacySwitch_PreservesOldLeakBehavior (negative test)

Validation

Built clean for net462 / net8.0 / net9.0. Standalone repro run end-to-end against local SQL Server: [After] isolation is now ReadCommitted on both scenarios with the same pooled SPID. With the legacy switch enabled, [After] is Serializable (kill-switch verified).

Copilot AI review requested due to automatic review settings June 1, 2026 13:59
@priyankatiwari08 priyankatiwari08 requested a review from a team as a code owner June 1, 2026 13:59
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Jun 1, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

SqlTransaction and TransactionScope leave the underlying SQL Server session with the elevated isolation level after Commit/Rollback. sp_reset_connection does not reset it, so the next user of the pooled physical connection silently inherits it.

This change tracks when a non-default isolation level was set via the TM Begin path and, on pool return, issues a 'SET TRANSACTION ISOLATION LEVEL READ COMMITTED' batch right before sp_reset_connection (piggybacked on the same TDS header, no extra round trip). On failure the connection is doomed instead of returned to the pool.

A new AppContext switch 'Switch.Microsoft.Data.SqlClient.UseLegacyIsolationLevelBehavior' preserves previous behavior for callers that depend on it (default: false).

Adds IsolationLevelLeakTest under ManualTests covering the two repro scenarios plus a negative test for the kill switch.
@priyankatiwari08 priyankatiwari08 force-pushed the feature/sqltransaction-isolation-leak branch from 4e8ed47 to 9f67fb1 Compare June 1, 2026 14:19
@priyankatiwari08 priyankatiwari08 requested a review from Copilot June 1, 2026 14:20
@priyankatiwari08 priyankatiwari08 added this to the 7.0.2 milestone Jun 1, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment on lines +31 to +38
private static string PooledMaxOneConnString =>
new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString)
{
Pooling = true,
MaxPoolSize = 1,
MultipleActiveResultSets = false,
ApplicationName = "IsoLeakTest"
}.ConnectionString;
Comment on lines +106 to +109
const string Switch = "Switch.Microsoft.Data.SqlClient.UseLegacyIsolationLevelBehavior";
AppContext.SetSwitch(Switch, true);
try
{
Comment on lines +136 to +140
finally
{
AppContext.SetSwitch(Switch, false);
SqlConnection.ClearAllPools();
}
Copy link
Copy Markdown
Contributor

@apoorvdeshmukh apoorvdeshmukh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you look into this test failure?
Seems related to the changes

@github-project-automation github-project-automation Bot moved this from To triage to Waiting for customer in SqlClient Board Jun 1, 2026
@apoorvdeshmukh apoorvdeshmukh self-assigned this Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting for customer

Development

Successfully merging this pull request may close these issues.

SqlTransaction and TransactionScope leak isolation level

3 participants