Skip to content

Commit 1988750

Browse files
Fix connection pool concurrency issue (#3632)
1 parent 7dd31ee commit 1988750

File tree

3 files changed

+485
-34
lines changed

3 files changed

+485
-34
lines changed

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,41 @@ internal WaitHandle[] GetHandles(bool withCreate)
383383
}
384384
}
385385

386+
/// <summary>
387+
/// Helper class to obtain and release a semaphore.
388+
/// </summary>
389+
internal class SemaphoreHolder : IDisposable
390+
{
391+
private readonly Semaphore _semaphore;
392+
393+
/// <summary>
394+
/// Whether the semaphore was successfully obtained within the timeout.
395+
/// </summary>
396+
internal bool Obtained { get; private set; }
397+
398+
/// <summary>
399+
/// Obtains the semaphore, waiting up to the specified timeout.
400+
/// </summary>
401+
/// <param name="semaphore"></param>
402+
/// <param name="timeout"></param>
403+
internal SemaphoreHolder(Semaphore semaphore, int timeout)
404+
{
405+
_semaphore = semaphore;
406+
Obtained = _semaphore.WaitOne(timeout);
407+
}
408+
409+
/// <summary>
410+
/// Releases the semaphore if it was successfully obtained.
411+
/// </summary>
412+
public void Dispose()
413+
{
414+
if (Obtained)
415+
{
416+
_semaphore.Release(1);
417+
}
418+
}
419+
}
420+
386421
private const int MAX_Q_SIZE = 0x00100000;
387422

388423
// The order of these is important; we want the WaitAny call to be signaled
@@ -1321,20 +1356,14 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
13211356

13221357
if (onlyOneCheckConnection)
13231358
{
1324-
if (_waitHandles.CreationSemaphore.WaitOne(unchecked((int)waitForMultipleObjectsTimeout)))
1359+
using SemaphoreHolder semaphoreHolder = new(_waitHandles.CreationSemaphore, unchecked((int)waitForMultipleObjectsTimeout));
1360+
if (semaphoreHolder.Obtained)
13251361
{
13261362
#if NETFRAMEWORK
13271363
RuntimeHelpers.PrepareConstrainedRegions();
13281364
#endif
1329-
try
1330-
{
1331-
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.GetConnection|RES|CPOOL> {0}, Creating new connection.", Id);
1332-
obj = UserCreateRequest(owningObject, userOptions);
1333-
}
1334-
finally
1335-
{
1336-
_waitHandles.CreationSemaphore.Release(1);
1337-
}
1365+
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.GetConnection|RES|CPOOL> {0}, Creating new connection.", Id);
1366+
obj = UserCreateRequest(owningObject, userOptions);
13381367
}
13391368
else
13401369
{
@@ -1553,26 +1582,20 @@ private void PoolCreateRequest(object state)
15531582
{
15541583
return;
15551584
}
1556-
int waitResult = BOGUS_HANDLE;
15571585

15581586
#if NETFRAMEWORK
15591587
RuntimeHelpers.PrepareConstrainedRegions();
15601588
#endif
15611589
try
15621590
{
15631591
// Obtain creation mutex so we're the only one creating objects
1564-
// and we must have the wait result
1592+
using SemaphoreHolder semaphoreHolder = new(_waitHandles.CreationSemaphore, CreationTimeout);
1593+
1594+
if (semaphoreHolder.Obtained)
1595+
{
15651596
#if NETFRAMEWORK
15661597
RuntimeHelpers.PrepareConstrainedRegions();
15671598
#endif
1568-
try
1569-
{ }
1570-
finally
1571-
{
1572-
waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(withCreate: true), CreationTimeout);
1573-
}
1574-
if (CREATION_HANDLE == waitResult)
1575-
{
15761599
DbConnectionInternal newObj;
15771600

15781601
// Check ErrorOccurred again after obtaining mutex
@@ -1606,17 +1629,12 @@ private void PoolCreateRequest(object state)
16061629
}
16071630
}
16081631
}
1609-
else if (WaitHandle.WaitTimeout == waitResult)
1632+
else
16101633
{
16111634
// do not wait forever and potential block this worker thread
16121635
// instead wait for a period of time and just requeue to try again
16131636
QueuePoolCreateRequest();
16141637
}
1615-
else
1616-
{
1617-
// trace waitResult and ignore the failure
1618-
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.PoolCreateRequest|RES|CPOOL> {0}, PoolCreateRequest called WaitForSingleObject failed {1}", Id, waitResult);
1619-
}
16201638
}
16211639
catch (Exception e)
16221640
{
@@ -1630,14 +1648,6 @@ private void PoolCreateRequest(object state)
16301648
// thrown to the user the next time they request a connection.
16311649
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionPool.PoolCreateRequest|RES|CPOOL> {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", Id, e);
16321650
}
1633-
finally
1634-
{
1635-
if (CREATION_HANDLE == waitResult)
1636-
{
1637-
// reuse waitResult and ignore its value
1638-
_waitHandles.CreationSemaphore.Release(1);
1639-
}
1640-
}
16411651
}
16421652
}
16431653
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@
258258
<None Include="SQL\ParameterTest\SqlParameterTest_ReleaseMode_Azure.bsl" />
259259
</ItemGroup>
260260
<ItemGroup Condition="'$(TestSet)' == '' OR '$(TestSet)' == '3'">
261+
<Compile Include="SQL\ConnectionPoolTest\ConnectionPoolStressTest.cs" />
261262
<Compile Include="TracingTests\MetricsTest.cs" />
262263
</ItemGroup>
263264
<ItemGroup Condition="'$(TargetGroup)'=='netcoreapp' AND ('$(TestSet)' == '' OR '$(TestSet)' == '3')">

0 commit comments

Comments
 (0)