-
Notifications
You must be signed in to change notification settings - Fork 314
WIP: Fix connection pool concurrency issue #3632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…le semaphore handles, instead of only creation handle during pool create request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes a concurrency issue in the connection pool by changing how semaphore handles are waited on during pool creation requests. Instead of waiting on multiple semaphore handles simultaneously, it now waits only on the creation handle to prevent potential race conditions.
- Replaces
WaitHandle.WaitAny()
with directWaitOne()
on the creation semaphore - Simplifies the wait result handling logic by removing the intermediate wait result case
- Removes unnecessary error tracing for non-timeout wait failures
...oft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs
Show resolved
Hide resolved
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3632 +/- ##
==========================================
+ Coverage 66.13% 66.71% +0.58%
==========================================
Files 276 276
Lines 60765 60764 -1
==========================================
+ Hits 40184 40541 +357
+ Misses 20581 20223 -358
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asking to simplify/clarify the semaphore block.
Also, is there a way to test this?
// and we must have the wait result | ||
waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(withCreate: true), CreationTimeout); | ||
bool creationHandleObtained = _waitHandles.CreationSemaphore.WaitOne(CreationTimeout); | ||
waitResult = creationHandleObtained ? CREATION_HANDLE : WaitHandle.WaitTimeout; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to assign WaitHandle.WaitTimeout
here. waitResult
is only compared to CREATION_HANDLE
for the remainder of its scope. You can simplify these 2 lines to:
if (_waitHandles.CreationSemaphore.WaitOne(CreationTimeout))
{
waitResult = CREATION_HANDLE;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking closer, I don't think we need waitResult
at all. It's being used as a stand-in for bool obtainedSemaphore
. I think this would be clearer:
bool obtainedSemaphore = false;
try
{
obtainedSemaphore = _waitHandles.CreationSemaphore.WaitOne(CreationTimeout);
if (obtainedSemaphore)
{
...
}
...
}
finally
{
if (obtainedSemaphore)
{
_waitHandles.CreationSemaphore.Release(1);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bonus points if you want to create a SemaphoreScope RAII helper that obtains the semaphore on construction, and releases it on disposal. That would let the compiler write the finally block for you, and would keep the semaphore scoped inside the try block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, the wait result pattern is only really meaningful when doing a WaitAny to see which semaphore you obtained.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started with bare minimum changes to make it work :P
This might also need to be backported to v5.1. #2390 ported the change from netcore, which has exactly the same behaviour: SqlClient/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs Line 1482 in ee2d196
v6.0 is the first point that it impacted netfx, but this has been the netcore behaviour from the outset of the driver (even when going back to the PR which added System.Data.SqlClient to its first version of netcore.) |
Fixes connection pool concurrency issue caused due to waiting on multiple semaphore handles, instead of only creation handle during pool create request.
Issue was first introduced in PR #2390 (https://github.com/dotnet/SqlClient/pull/2390/files#diff-d7c1dbddf2a7e205d7a85cbafbef036899799672dd5d1c486a64a05979e92230R1627) and has affected MDS v6 onwards.
Needs to be backported to v6.0 and v6.1 branches as well.
WIP: Working on adding tests - running CI first.