Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c66406
Fix test failures.
mdaigle Nov 10, 2025
3c74365
Expose transacted connection in debug to assert transacted pool state.
mdaigle Nov 13, 2025
39b7851
Remove unnecessary context messages.
mdaigle Nov 13, 2025
1f4bc05
Test cleanup
mdaigle Nov 14, 2025
fab9a43
Convert TransactedConnections to auto-property.
mdaigle Nov 20, 2025
cddc9eb
Make TransactedConnectionPool accessible when not in DEBUG.
mdaigle Nov 20, 2025
9ba0b9c
Clean up and fix tests. Expose min and max pool size properties.
mdaigle Nov 21, 2025
e1853da
Merge branch 'main' of github.com:dotnet/SqlClient into dev/mdaigle/t…
mdaigle Nov 21, 2025
d4533c6
Clean up dependencies.
mdaigle Nov 21, 2025
6cd9e82
Add missing method override.
mdaigle Nov 21, 2025
d4b1e21
Fix shared transaction test case.
mdaigle Nov 21, 2025
e68ea14
Fix async single transaction test.
mdaigle Nov 21, 2025
1191ca4
Remove uninteresting test cases. Add todos for missing test cases.
mdaigle Nov 21, 2025
b969832
Add more test cases. Speed up tests.
mdaigle Nov 21, 2025
42c8fbd
Clean up new tests.
mdaigle Nov 24, 2025
1f31bd2
Improve cleanup
mdaigle Nov 24, 2025
90f2686
Fix copilot issues. Add doc comment.
mdaigle Nov 24, 2025
861b0e7
Address copilot comments.
mdaigle Nov 24, 2025
017353c
Fix tests. Expose TransactedConnectionPool.
mdaigle Nov 24, 2025
516ba13
Improve property accessors
mdaigle Mar 20, 2026
01f176c
Merge branch 'main' of github.com:dotnet/SqlClient into dev/mdaigle/t…
mdaigle Mar 20, 2026
4a85142
Add deterministic tests to unit tests. Move stress situations to stre…
mdaigle Mar 20, 2026
9057103
Remove stress test changes.
mdaigle Mar 23, 2026
700c7b0
Address comments
mdaigle Mar 26, 2026
c13eb6d
Clean up more tests
mdaigle Mar 26, 2026
da057ba
Merge branch 'main' of https://github.com/dotnet/SqlClient into dev/m…
mdaigle Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Core" />
<PackageReference Include="System.Text.Encodings.Web" />
Comment thread
mdaigle marked this conversation as resolved.
Outdated
Comment thread
mdaigle marked this conversation as resolved.
Outdated
<PackageReference Include="Azure.Security.KeyVault.Keys" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@
<PackageReference Include="Microsoft.SqlServer.Server" />
<PackageReference Include="System.Configuration.ConfigurationManager" />
<PackageReference Include="System.Security.Cryptography.Pkcs" />

<!-- Transitive dependencies that would otherwise bring in older, vulnerable versions. -->
<PackageReference Include="System.Text.Json" />
Comment thread
mdaigle marked this conversation as resolved.
</ItemGroup>

<!-- netstandard dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1102,9 +1102,6 @@
<PackageReference Include="Microsoft.SqlServer.Server" />
<PackageReference Include="System.Configuration.ConfigurationManager" />
<PackageReference Include="System.Security.Cryptography.Pkcs" />

<!-- Transitive dependencies that would otherwise bring in older, vulnerable versions. -->
<PackageReference Include="System.Text.Json" />
Comment thread
mdaigle marked this conversation as resolved.
Outdated
</ItemGroup>

<Import Project="$(ToolsDir)targets\GenerateThisAssemblyCs.targets" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ internal ChannelDbConnectionPool(
Identity = identity;
AuthenticationContexts = new();
MaxPoolSize = Convert.ToUInt32(PoolGroupOptions.MaxPoolSize);
TransactedConnectionPool = new(this);

_connectionSlots = new(MaxPoolSize);

Expand Down Expand Up @@ -147,6 +148,9 @@ public ConcurrentDictionary<
/// <inheritdoc />
public DbConnectionPoolState State { get; private set; }

/// <inheritdoc />
public TransactedConnectionPool TransactedConnectionPool { get; }

/// <inheritdoc />
public bool UseLoadBalancing => PoolGroupOptions.UseLoadBalancing;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ bool hasTransactionAffinity
_hasTransactionAffinity = hasTransactionAffinity;
}

/// <summary>
/// The time (in milliseconds) to wait for a connection to be created/returned before terminating the attempt.
/// </summary>
public int CreationTimeout
{
get { return _creationTimeout; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ internal interface IDbConnectionPool
/// </summary>
DbConnectionPoolState State { get; }

/// <summary>
/// Holds connections that are currently enlisted in a transaction.
/// </summary>
TransactedConnectionPool TransactedConnectionPool { get; }

/// <summary>
/// Indicates whether the connection pool is using load balancing.
/// </summary>
Expand All @@ -106,7 +111,7 @@ internal interface IDbConnectionPool
/// <param name="userOptions">The user options to use if a new connection must be opened.</param>
/// <param name="connection">The retrieved connection will be passed out via this parameter.</param>
/// <returns>True if a connection was set in the out parameter, otherwise returns false.</returns>
bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal> taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection);
bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal>? taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection);

Comment thread
mdaigle marked this conversation as resolved.
/// <summary>
/// Replaces the internal connection currently associated with owningObject with a new internal connection from the pool.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal class TransactedConnectionPool
/// A specialized list that holds database connections associated with a specific transaction.
/// Maintains a reference to the transaction for proper cleanup when the transaction completes.
/// </summary>
private sealed class TransactedConnectionList : List<DbConnectionInternal>
internal sealed class TransactedConnectionList : List<DbConnectionInternal>
{
private readonly Transaction _transaction;

Expand Down Expand Up @@ -59,9 +59,6 @@ internal void Dispose()
}

#region Fields

private readonly Dictionary<Transaction, TransactedConnectionList> _transactedCxns;

private static int _objectTypeCount;
internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);

Expand All @@ -79,7 +76,7 @@ internal void Dispose()
internal TransactedConnectionPool(IDbConnectionPool pool)
{
Pool = pool;
_transactedCxns = new Dictionary<Transaction, TransactedConnectionList>();
TransactedConnections = new Dictionary<Transaction, TransactedConnectionList>();
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.TransactedConnectionPool.TransactedConnectionPool|RES|CPOOL> {0}, Constructed for connection pool {1}", Id, Pool.Id);
}

Expand All @@ -97,6 +94,8 @@ internal TransactedConnectionPool(IDbConnectionPool pool)
/// <value>The IDbConnectionPool instance that owns this transacted pool.</value>
internal IDbConnectionPool Pool { get; }

internal Dictionary<Transaction, TransactedConnectionList> TransactedConnections { get; private set; }
Comment thread
mdaigle marked this conversation as resolved.
Outdated

Comment thread
mdaigle marked this conversation as resolved.
#endregion

#region Methods
Expand All @@ -122,9 +121,9 @@ internal TransactedConnectionPool(IDbConnectionPool pool)
TransactedConnectionList? connections;
bool txnFound = false;

lock (_transactedCxns)
lock (TransactedConnections)
Comment thread
paulmedynski marked this conversation as resolved.
{
txnFound = _transactedCxns.TryGetValue(transaction, out connections);
txnFound = TransactedConnections.TryGetValue(transaction, out connections);
}

// NOTE: GetTransactedObject is only used when AutoEnlist = True and the ambient transaction
Expand Down Expand Up @@ -181,10 +180,10 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal
// NOTE: because TransactionEnded is an asynchronous notification, there's no guarantee
// around the order in which PutTransactionObject and TransactionEnded are called.

lock (_transactedCxns)
lock (TransactedConnections)
{
// Check if a transacted pool has been created for this transaction
if ((txnFound = _transactedCxns.TryGetValue(transaction, out connections))
if ((txnFound = TransactedConnections.TryGetValue(transaction, out connections))
&& connections is not null)
{
// synchronize multi-threaded access with GetTransactedObject
Expand Down Expand Up @@ -212,14 +211,14 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal
transactionClone = transaction.Clone();
newConnections = new TransactedConnectionList(2, transactionClone); // start with only two connections in the list; most times we won't need that many.

lock (_transactedCxns)
lock (TransactedConnections)
{
// NOTE: in the interim between the locks on the transacted pool (this) during
// execution of this method, another thread (threadB) may have attempted to
// add a different connection to the transacted pool under the same
// transaction. As a result, threadB may have completed creating the
// transacted pool while threadA was processing the above instructions.
if (_transactedCxns.TryGetValue(transaction, out connections)
if (TransactedConnections.TryGetValue(transaction, out connections)
&& connections is not null)
{
// synchronize multi-threaded access with GetTransactedObject
Expand All @@ -237,7 +236,7 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal
// add the connection/transacted object to the list
newConnections.Add(transactedObject);

_transactedCxns.Add(transactionClone, newConnections);
TransactedConnections.Add(transactionClone, newConnections);
transactionClone = null; // we've used it -- don't throw it or the TransactedConnectionList that references it away.
}
}
Expand Down Expand Up @@ -296,9 +295,9 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra
// TODO: that the pending creation of a transacted pool for this transaction is aborted when
// TODO: PutTransactedObject finally gets some CPU time?

lock (_transactedCxns)
lock (TransactedConnections)
{
if (_transactedCxns.TryGetValue(transaction, out connections)
if (TransactedConnections.TryGetValue(transaction, out connections)
&& connections is not null)
{
bool shouldDisposeConnections = false;
Expand All @@ -318,7 +317,7 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra
if (0 >= connections.Count)
{
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.TransactedConnectionPool.TransactionEnded|RES|CPOOL> {0}, Transaction {1}, Removing List from transacted pool.", Id, transaction.GetHashCode());
_transactedCxns.Remove(transaction);
TransactedConnections.Remove(transaction);

// we really need to dispose our connection list; it may have
// native resources via the tx and GC may not happen soon enough.
Expand Down Expand Up @@ -350,4 +349,4 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,9 @@ public bool IsRunning
get { return State is Running; }
}

private int MaxPoolSize => PoolGroupOptions.MaxPoolSize;
internal int MaxPoolSize => PoolGroupOptions.MaxPoolSize;

private int MinPoolSize => PoolGroupOptions.MinPoolSize;
internal int MinPoolSize => PoolGroupOptions.MinPoolSize;

public DbConnectionPoolGroup PoolGroup => _connectionPoolGroup;

Expand All @@ -328,6 +328,8 @@ public bool IsRunning

private bool UsingIntegrateSecurity => _identity != null && DbConnectionPoolIdentity.NoIdentity != _identity;

public TransactedConnectionPool TransactedConnectionPool => _transactedConnectionPool;

Comment thread
mdaigle marked this conversation as resolved.
private void CleanupCallback(object state)
{
// Called when the cleanup-timer ticks over.
Expand Down Expand Up @@ -946,6 +948,8 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj

// If automatic transaction enlistment is required, then we try to
// get the connection from the transacted connection pool first.
// If automatic enlistment is not enabled, then we cannot vend connections
// from the transacted pool.
if (HasTransactionAffinity)
{
obj = GetFromTransactedPool(out transaction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,13 +668,14 @@ internal class MockDbConnectionPool : IDbConnectionPool
public DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException();
public DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException();
public DbConnectionPoolState State => throw new NotImplementedException();
public TransactedConnectionPool TransactedConnectionPool => throw new NotImplementedException();
public bool UseLoadBalancing => throw new NotImplementedException();

public ConcurrentBag<DbConnectionInternal> ReturnedConnections { get; } = new();

public void Clear() => throw new NotImplementedException();

public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal> taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection)
public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal>? taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection)
{
throw new NotImplementedException();
}
Expand Down Expand Up @@ -739,4 +740,4 @@ internal override void ResetConnection()
}

#endregion
}
}
Loading
Loading