Skip to content

Commit 18a0778

Browse files
Fix connection pool concurrency issue (#3632)
1 parent 29635e1 commit 18a0778

File tree

3 files changed

+490
-36
lines changed

3 files changed

+490
-36
lines changed

src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,42 @@ internal WaitHandle[] GetHandles(bool withCreate)
362362
}
363363
}
364364

365-
private const int MAX_Q_SIZE = (int)0x00100000;
365+
/// <summary>
366+
/// Helper class to obtain and release a semaphore.
367+
/// </summary>
368+
internal class SemaphoreHolder : IDisposable
369+
{
370+
private readonly Semaphore _semaphore;
371+
372+
/// <summary>
373+
/// Whether the semaphore was successfully obtained within the timeout.
374+
/// </summary>
375+
internal bool Obtained { get; private set; }
376+
377+
/// <summary>
378+
/// Obtains the semaphore, waiting up to the specified timeout.
379+
/// </summary>
380+
/// <param name="semaphore"></param>
381+
/// <param name="timeout"></param>
382+
internal SemaphoreHolder(Semaphore semaphore, int timeout)
383+
{
384+
_semaphore = semaphore;
385+
Obtained = _semaphore.WaitOne(timeout);
386+
}
387+
388+
/// <summary>
389+
/// Releases the semaphore if it was successfully obtained.
390+
/// </summary>
391+
public void Dispose()
392+
{
393+
if (Obtained)
394+
{
395+
_semaphore.Release(1);
396+
}
397+
}
398+
}
399+
400+
private const int MAX_Q_SIZE = 0x00100000;
366401

367402
// The order of these is important; we want the WaitAny call to be signaled
368403
// for a free object before a creation signal. Only the index first signaled
@@ -1414,20 +1449,14 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
14141449

14151450
if (onlyOneCheckConnection)
14161451
{
1417-
if (_waitHandles.CreationSemaphore.WaitOne(unchecked((int)waitForMultipleObjectsTimeout)))
1452+
using SemaphoreHolder semaphoreHolder = new(_waitHandles.CreationSemaphore, unchecked((int)waitForMultipleObjectsTimeout));
1453+
if (semaphoreHolder.Obtained)
14181454
{
14191455
#if NETFRAMEWORK
14201456
RuntimeHelpers.PrepareConstrainedRegions();
14211457
#endif
1422-
try
1423-
{
1424-
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.GetConnection|RES|CPOOL> {0}, Creating new connection.", ObjectID);
1425-
obj = UserCreateRequest(owningObject, userOptions);
1426-
}
1427-
finally
1428-
{
1429-
_waitHandles.CreationSemaphore.Release(1);
1430-
}
1458+
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.GetConnection|RES|CPOOL> {0}, Creating new connection.", ObjectID);
1459+
obj = UserCreateRequest(owningObject, userOptions);
14311460
}
14321461
else
14331462
{
@@ -1657,26 +1686,20 @@ private void PoolCreateRequest(object state)
16571686
{
16581687
return;
16591688
}
1660-
int waitResult = BOGUS_HANDLE;
16611689

16621690
#if NETFRAMEWORK
16631691
RuntimeHelpers.PrepareConstrainedRegions();
16641692
#endif
16651693
try
16661694
{
16671695
// Obtain creation mutex so we're the only one creating objects
1668-
// and we must have the wait result
1696+
using SemaphoreHolder semaphoreHolder = new(_waitHandles.CreationSemaphore, CreationTimeout);
1697+
1698+
if (semaphoreHolder.Obtained)
1699+
{
16691700
#if NETFRAMEWORK
16701701
RuntimeHelpers.PrepareConstrainedRegions();
16711702
#endif
1672-
try
1673-
{ }
1674-
finally
1675-
{
1676-
waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(withCreate: true), CreationTimeout);
1677-
}
1678-
if (CREATION_HANDLE == waitResult)
1679-
{
16801703
DbConnectionInternal newObj;
16811704

16821705
// Check ErrorOccurred again after obtaining mutex
@@ -1710,17 +1733,12 @@ private void PoolCreateRequest(object state)
17101733
}
17111734
}
17121735
}
1713-
else if (WaitHandle.WaitTimeout == waitResult)
1736+
else
17141737
{
17151738
// do not wait forever and potential block this worker thread
17161739
// instead wait for a period of time and just requeue to try again
17171740
QueuePoolCreateRequest();
17181741
}
1719-
else
1720-
{
1721-
// trace waitResult and ignore the failure
1722-
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.PoolCreateRequest|RES|CPOOL> {0}, PoolCreateRequest called WaitForSingleObject failed {1}", ObjectID, waitResult);
1723-
}
17241742
}
17251743
catch (Exception e)
17261744
{
@@ -1732,15 +1750,7 @@ private void PoolCreateRequest(object state)
17321750
// Now that CreateObject can throw, we need to catch the exception and discard it.
17331751
// There is no further action we can take beyond tracing. The error will be
17341752
// thrown to the user the next time they request a connection.
1735-
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.PoolCreateRequest|RES|CPOOL> {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", ObjectID, e);
1736-
}
1737-
finally
1738-
{
1739-
if (CREATION_HANDLE == waitResult)
1740-
{
1741-
// reuse waitResult and ignore its value
1742-
_waitHandles.CreationSemaphore.Release(1);
1743-
}
1753+
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.PoolCreateRequest|RES|CPOOL> {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", Id, e);
17441754
}
17451755
}
17461756
}

src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@
253253
<None Include="SQL\ParameterTest\SqlParameterTest_ReleaseMode.bsl" />
254254
<None Include="SQL\ParameterTest\SqlParameterTest_ReleaseMode_Azure.bsl" />
255255
</ItemGroup>
256+
<ItemGroup Condition="'$(TestSet)' == '' OR '$(TestSet)' == '3'">
257+
<Compile Include="SQL\ConnectionPoolTest\ConnectionPoolStressTest.cs" />
258+
<Compile Include="TracingTests\MetricsTest.cs" />
259+
</ItemGroup>
256260
<ItemGroup Condition="'$(TargetGroup)'=='netcoreapp' AND ('$(TestSet)' == '' OR '$(TestSet)' == '3')">
257261
<Compile Include="TracingTests\EventCounterTest.cs" />
258262
<Compile Include="TracingTests\DiagnosticTest.cs" />

0 commit comments

Comments
 (0)