Ok this looks like a major fundamental bug in .NET:
Consider the following simple program, which purposely tries to connect to a non-existent database:
class Program
{
static void Main(string[] args)
{
Thread threadOne = new Thread(GetConnectionOne);
Thread threadTwo = new Thread(GetConnectionTwo);
threadOne.Start();
threadTwo.Start();
}
static void GetConnectionOne()
{
try
{
using (SqlConnection conn = new SqlConnection("Data Source=.\\wfea;Initial Catalog=zc;Persist Security Info=True;Trusted_Connection=yes;"))
{
conn.Open();
}
} catch (Exception e)
{
File.AppendAllText("ConnectionOneError.txt", e.Message + "\n" + e.StackTrace + "\n");
}
}
static void GetConnectionTwo()
{
try
{
using (SqlConnection conn = new SqlConnection("Data Source=.\\wfea;Initial Catalog=zc;Persist Security Info=True;Trusted_Connection=yes;"))
{
conn.Open();
}
}
catch (Exception e)
{
File.AppendAllText("ConnectionTwoError.txt", e.Message + "\n" + e.StackTrace + "\n");
}
}
}
Run this program and set breakpoints on the catch blocks. The DBConnection object will attempt to connect for 15 seconds (on both threads), then it will throw an error. Inspect the exception's stack trace, and the stack trace will have TWO call stacks intermingled, as follows:
at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject)
at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject)
at System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
at System.Data.SqlClient.SqlConnection.Open()
at ZoCom2Test.Program.GetConnectionOne() in C:\src\trunk\ZTest\Program.cs:line 38
at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
at System.Data.SqlClient.SqlConnection.Open()
at ZoCom2Test.Program.GetConnectionTwo() in C:\src\trunk\ZTest\Program.cs:line 54
You may have to try it several times to get this to happen, but I'm getting this to happen right now on my machine. How is this possible? This should be totally impossible at the VM level. It looks like the DBConnection.Open() function is simultaneously throwing the same exception on two threads at once, or something bizarre like that.
Connection pooling allows you to reuse connections rather than create a new one every time the ADO.NET data provider needs to establish a connection to the underlying database. Connection pooling behavior can be controlled by using connection string options (see the documentation for your data provider).
Connection pooling means that connections are reused rather than created each time a connection is requested. To facilitate connection reuse, a memory cache of database connections, called a connection pool, is maintained by a connection pooling module as a layer on top of any standard JDBC driver product.
Using connection pools helps to both alleviate connection management overhead and decrease development tasks for data access. Each time an application attempts to access a backend store (such as a database), it requires resources to create, maintain, and release a connection to that datastore.
If the maximum pool size has been reached and no usable connection is available, the request is queued. The pooler then tries to reclaim any connections until the time-out is reached (the default is 15 seconds). If the pooler cannot satisfy the request before the connection times out, an exception is thrown.
Try this instead, and see what happens:
class ThreadingBug
{
private const string CONNECTION_STRING =
"Data Source=.\\wfea;Initial Catalog=catalog;Persist Security Info=True;Trusted_Connection=yes;";
static void Main(string[] args)
{
try
{
Thread threadOne = new Thread(GetConnectionOne);
Thread threadTwo = new Thread(GetConnectionTwo);
threadOne.Start();
threadTwo.Start();
threadOne.Join(2000);
threadTwo.Join(2000);
}
catch (Exception e)
{
File.AppendAllText("Main.txt", e.ToString());
}
}
static void GetConnectionOne()
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
}
}
catch (Exception e)
{
File.AppendAllText("GetConnectionOne.txt", e.ToString());
}
}
static void GetConnectionTwo()
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
}
}
catch (Exception e)
{
File.AppendAllText("GetConnectionTwo.txt", e.ToString());
}
}
}
I believe there is a bug here, though it's neither major, nor fundamental. After working to narrow this down (and to do things like removing one thread), it looks like the same instance of the Exception
class is thrown by the Connection Pool implementation on both threads (kudos to Gregory for discovering this). This sometimes shows up as a corrupt ("intermingled") stack trace, and sometimes simply as the same stack trace on both threads, even when the code is quite different between the two threads.
Commenting out one of the Thread.Start
calls shows an entirely different stack trace, demonstrating that the odd part is in the connection pool implementation - the odd stack traces are being handed out by the connection pool, since both threads use the same connection string and credentials.
I've submitted a Connect issue on this at https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=522506. Everyone should feel free to vote on how important (or unimportant) you feel it is, whether you can reproduce it, or whether you have a workaround. This will help Microsoft prioritize a fix.
Update: The Connect issue has been updated. Microsoft acknowledges it as a bug, and plans to fix it in a future release.
Thanks to nganju, Gregory, and everyone else who participated in solving this problem. It was indeed a bug, and it will be fixed, and it's because of us.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With