Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Dont allow exceptions to emerge on the threadpool #27439

Merged
merged 3 commits into from Feb 25, 2018

Conversation

saurabh500
Copy link
Contributor

PoolCreateRequest handles creation of new connections on the thread pool. However when an exception occurs, it is bubbled up to the thread pool. This is a regression from .Net Framework and causes applications using Min Pool Size to crash.

The solution is to catch all exceptions during connection open and to swallow them. This will not change the exception that app sees from Connection Pool, as the error is bubbled up from another code path where the errorWaitHandle is set during connection Open.

The test added, makes sure that we can open 3 bad connections using MinPoolSize. Before the fix, the test would crash after the first exception.

Fixes https://github.com/dotnet/corefx/issues/14615

@saurabh500
Copy link
Contributor Author

@dotnet-bot Test Windows x64 Debug Build please

@danmosemsft I can figure out what failed for the given build from the logs.

https://ci3.dot.net/job/dotnet_corefx/job/master/job/windows-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_false_prtest/8908/consoleFull

catch(Exception)
{
// Catch all the exceptions occuring during CreateObject so that they
// don't emerge as unhandled on the thread pool and don't crash applications
Copy link
Member

Choose a reason for hiding this comment

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

It'd be good to add why it's ok that we eat them... from your PR description, it sounds like there's some other backchannel through which they're passed up to the originating code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good.
What happens is that CreateObject handles the exception at

And then it sets the ErrorEvent. The caller of the connection pool is signalled on the connection pool about the ErrorEvent being set and it knows that an exception was thrown. The caller of Connection.Open() ultimately gets the same exception that they were receiving on .Net Framework. It's just that the exception doesn't escape on to the threadpool

{
try
{
connection.Open();
Copy link
Member

Choose a reason for hiding this comment

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

Is this going to fail every time? If so, how about using Assert.Throws? That would make it something like:

string connectionString = ...;
for (int i = 0; i < 3; i++)
{
    using (var connection = new SqlConnection(connectionString))
    {
        Assert.Throws<WhateverExceptionType>(() => connection.Open());
    }
}

That'll also help validate that the right exception is thrown.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it will fail everytime. I will adapt the testcase.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: The exception thrown is either InvalidOperationException (if the connection pool timeout is triggered) or SqlException (if the network error in connecting to invalid Data Source happens before the Connection Pool timeout) . So the specific exception type cannot be determined. I will use Assert.ThrowsAny<Exception>(() => connection.Open());

Copy link
Member

Choose a reason for hiding this comment

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

So the specific exception type cannot be determined. I will use Assert.ThrowsAny(() => connection.Open());

In that case you can do:

Exception error = Record.Exception(() => connection.Open());
Assert.True(error is InvalidOperationException || error is SqlException, $"Unexpected exception: {error}");

}
}
}
Assert.Equal(expected, count);
Copy link
Member

Choose a reason for hiding this comment

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

If you do end up keeping the current structure, this Assert.Equal seems unnecessary... the only way you'll get here, by construction, is if expected == count, as that's when you exit the loop.

// Don't specify any user options because there is no outer connection associated with the new connection
newObj = CreateObject(owningObject: null, userOptions: null, oldConnection: null);
}
catch(Exception)
Copy link
Member

Choose a reason for hiding this comment

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

Since you're catching everything, you can just do catch without the Exception part.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed.

{
int count = 0;
int expected = 3;
string connectionString = $"Data Source={Guid.NewGuid().ToString()};uid=random;pwd=asd;Connect Timeout=2; Min Pool Size=3";
Copy link
Member

Choose a reason for hiding this comment

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

Does this Connect Timeout mean this will take 2 seconds to fail? And we do it 3 times? Might want to make the test outerloop then.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test will try for a max of 2 seconds. However it could fail earlier. But it will go upto 2 seconds.
Outerloop makes sense. Thanks.

@stephentoub stephentoub merged commit 56ce6c1 into dotnet:master Feb 25, 2018
@karelz karelz added this to the 2.1.0 milestone Mar 10, 2018
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
* Dont allow exceptions to emerge on the threadpool

* Code review comments

* Check for specific exceptions


Commit migrated from dotnet/corefx@56ce6c1
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
5 participants