Пул соединений возвращает тот же экземпляр исключения для двух потоков с использованием той же плохой строки соединения?
Хорошо, это похоже на основную ошибку в .NET:
Рассмотрим следующую простую программу, которая намеренно пытается подключиться к несуществующей базе данных:
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");
}
}
}
Запустите эту программу и установите контрольные точки в блоках catch. Объект DBConnection будет пытаться подключиться в течение 15 секунд (в обоих потоках), после чего он выдаст ошибку. Проверьте трассировку стека исключений, и трассировка стека будет иметь двойные стеки вызовов, как показано ниже:
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
Возможно, вам придется несколько раз попробовать, чтобы это произошло, но я хочу, чтобы это произошло прямо сейчас на моей машине. Как это возможно? Это должно быть абсолютно невозможно на уровне VM. Похоже, функция DBConnection.Open() одновременно бросает одно и то же исключение на два потока одновременно или что-то странное.
Ответы
Ответ 1
Попробуйте это вместо этого и посмотрите, что произойдет:
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());
}
}
}
Я считаю, что здесь есть ошибка, хотя это ни главное, ни фундаментальное. После того, как вы сузили это (и делали что-то вроде удаления одного потока), похоже, что один и тот же экземпляр класса Exception
генерируется реализацией пула подключений на обоих потоках (для Gregory для этого). Иногда это проявляется как коррумпированная ( "смешанная" ) трассировка стека, а иногда просто как одна и та же трассировка стека для обоих потоков, даже когда код отличается от двух потоков.
Комментирование одного из вызовов Thread.Start
показывает совершенно другую трассировку стека, демонстрируя, что нечетная часть находится в реализации пула соединений - трассы нечетных стеков передаются пулом соединений, поскольку оба потока используют один и тот же строка подключения и учетные данные.
Я отправил сообщение об ошибке Connect this на https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=522506. Каждый человек должен свободно проголосовать за то, насколько важно (или неважно) вы его ощущаете, можете ли вы воспроизвести его или у вас есть обходной путь. Это поможет Microsoft определить приоритет исправления.
Обновление: Проблема с подключением была обновлена. Microsoft признает это как ошибку и планирует исправить ее в будущей версии.
Благодаря nganju, Gregory и всем остальным, которые участвовали в решении этой проблемы. Это была ошибка, и она будет исправлена, и это из-за нас.
Ответ 2
ОК, мне удалось воспроизвести это (VS2008, FX3.5SP1, двухъядерный) как внутри , так и вне (*) отладчика. И после того, как вы немного изменили свою логику catch, она даже надежно воспроизводится. И, как упоминал Грегори, это один и тот же экземпляр исключения, брошенный в оба потока.
Это должно быть совершенно невозможно в уровень VM.
Откуда у тебя такая идея?
Оба потока пытаются подключиться через пул соединений. Я ничего не знаю о том, как работает пул, но я угадаю: он сериализует 2 одновременных запроса. Похоже, что это хорошо для Сервера. И затем, когда попытка не удалась, у нее есть 1 исключение и 2 ожидающих потока.
Я тоже ожидал, что CLR или ConnectionPool дублируют исключение и добавят 2 отдельных стека, но вместо этого он объединяет 2 вызова.
Итак, я думаю, что ваша ошибка могла бы быть функцией, статус: по дизайну.
Потому что на самом деле это не "смешанная" стопка, а скорее умышленно Y-образная. Это не выглядит случайным.
Было бы неплохо, если бы кто-нибудь нашел ссылку на это поведение. Сейчас я не уверен, что это CLR или функция ConnectionPool.
(*) Изменить: Я думаю, что видел его за пределами отладчика один раз, но теперь я не могу воспроизвести это. Таким образом, это может быть отладчик или проблема с синхронизацией.
Ответ 3
Это не ошибка в виртуальной машине. Вот ваша оскорбительная линия:
private static readonly DbConnectionFactory _connectionFactory;
Внутри этого мы имеем пул соединений. В котором хранится ссылка на произошедшее исключение.
Это открывает условие гонки при выполнении многопоточности.
Как это доказать?
Логически, если вы используете разные пулы соединений, у нас не будет этого состояния гонки. Поэтому я повторил тот же тест с другим источником данных, указанным в строке соединения для каждого потока. Исключения теперь отображаются правильно.
Это действительно случай, когда пул соединений не является потокобезопасным.
Ответ 4
Вы получаете одно и то же исключение. Однако я не понимаю, почему. Посмотрите на окно вывода, в частности, что exception1 == exception2.
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(20000);
threadTwo.Join(20000);
Debug.WriteLine("Same?" + (exception1 == exception2));
}
catch (Exception e)
{
Debug.WriteLine("error main" + e);
}
}
static Exception exception1;
static void GetConnectionOne()
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
}
}
catch (Exception e)
{
Debug.WriteLine("Error Con one" + e);
exception1 = e;
}
}
static Exception exception2;
static void GetConnectionTwo()
{
try
{
using (SqlConnection conn = new SqlConnection(CONNECTION_STRING))
{
conn.Open();
}
}
catch (Exception e)
{
Debug.WriteLine("Error Con two" + e);
exception2 = e;
}
}
}
Изменить: ниже был мой оригинальный ответ.
Скорее всего, ваши "случайные" имена файлов схожи, если не совпадают, поскольку их иногда вызывают в очень близкие временные рамки. Часто, когда у вас есть проблема, которая случайно появляется, и у вас есть вызов Random.Next, это должно быть первое, что вы смотрите.