Взаимные блокировки, приводящие к тому, что «серверу не удалось возобновить транзакцию» с помощью NHibernate и распределенных транзакций - PullRequest
11 голосов
/ 21 декабря 2011

У нас возникла проблема при использовании NHibernate с распределенными транзакциями.

Рассмотрим следующий фрагмент:

//
// There is already an ambient distributed transaction
//
using(var scope = new TransactionScope()) {
    using(var session = _sessionFactory.OpenSession())
    using(session.BeginTransaction()) {
        using(var cmd = new SqlCommand(_simpleUpdateQuery, (SqlConnection)session.Connection)) {
            cmd.ExecuteNonQuery();
        }

        session.Save(new SomeEntity());
        session.Transaction.Commit();
    }
    scope.Complete();
}

Иногда, когда сервер находится в состоянии экстремальной нагрузки, мы видимследующее:

  1. Запрос, выполненный с помощью cmd.ExecuteNonQuery , выбран в качестве жертвы тупика (мы видим это в SQL Profiler), но исключение не возникает.
  2. session.Save завершается с сообщением об ошибке, «Операция недопустима для состояния транзакции.»
  3. Каждый раз, когда этот код выполняется послечто session.BeginTransaction завершается неудачно.Первые несколько раз внутреннее исключение меняется (иногда это исключение тупика, которое должно было возникнуть на шаге 1).В конечном итоге он стабилизируется до "Серверу не удалось возобновить транзакцию. Desc: 3800000177." или "Новый запрос не может быть запущен, поскольку он должен идти с допустимым дескриптором транзакции."

Если оставить его в покое, приложение в конечном итоге (через секунды или минуты) восстановится после этого условия.

Почему исключение взаимоблокировки не сообщается на шаге 1?И если мы не можем решить эту проблему, то как мы можем предотвратить временное прекращение работы нашего приложения?

Проблема воспроизводится в следующих средах

  • Windows 7x64 и Windows Server 2003 x86
  • SQL Server 2005 и 2008
  • .NET 4.0 и 3.5
  • NHibernate 3.2, 3.1 и 2.1.2

Я создал тестовое устройство, которое иногда воспроизводит проблему для нас.Это доступно здесь: http://wikiupload.com/EWJIGAECG9SQDMZ

Ответы [ 4 ]

5 голосов
/ 18 января 2012

Мы наконец сузили это до причины.

При открытии сеанса, если есть окружающая распределенная транзакция, NHibernate присоединяет обработчик события к Transaction.TransactionCompleted, который закрывает сеанс после завершения распределенной транзакции. По-видимому, это связано с состоянием гонки, при котором соединение может быть закрыто и возвращено в пул до того, как возникнет ошибка взаимоблокировки, оставив соединение в непригодном для использования состоянии.

Следующий код будет время от времени воспроизводить нам ошибку, даже без какой-либо нагрузки на сервер. Если на сервере имеется экстремальная нагрузка, она становится более последовательной.

using(var scope = new TransactionScope()) {
    //
    // Force promotion to distributed transaction
    //
    TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);

    var connection = new SqlConnection(_connectionString);
    connection.Open();

    //
    // Close the connection once the distributed transaction is
    // completed.
    //
    Transaction.Current.TransactionCompleted += 
        (sender, e) => connection.Close();

    using(connection.BeginTransaction())
        //
        // Deadlocks but sometimes does not raise exception
        //
        ForceDeadlockOnConnection(connection);

    scope.Complete();
}

//
// Subsequent attempts to open a connection with the same
// connection string will fail
//

Мы не определились с решением, но следующие вещи устранят проблему (хотя, возможно, имеют другие последствия):

  • Отключение пула подключений
  • Использование AdoNetTransactionFactory вместо AdoNetWithDistributedTransactionFactory
  • Добавление обработки ошибок, которая вызывает SqlConnection.ClearPool () , когда возникает ошибка «серверу не удалось возобновить транзакцию»

Согласно Microsoft (https://connect.microsoft.com/VisualStudio/feedback/details/722659/), класс SqlConnection не является поточно-ориентированным, что включает закрытие соединения в отдельном потоке. На основании этого ответа мы подали отчет об ошибке для NHibernate (http://nhibernate.jira.com/browse/NH-3023).

0 голосов
/ 13 ноября 2013

Это проблема NHibernate .NHibernate не открывает и не закрывает соединение в том же потоке, что не поддерживается ADO.NET .Вы можете обойти это, открыв и закрыв соединение самостоятельно.NHibernate не будет закрывать соединение, если он также не открыл его.

Обходной путь

var connection = ((SessionFactoryImpl)_sessionFactory).ConnectionProvider.GetConnection();
using(var session = _sessionFactory.OpenSession(connection))
{
   //do database stuff
}
connection.Close();
0 голосов
/ 02 января 2012

Это не совсем решит вашу проблему, но вы можете заставить свой IPreInsertEventListener просто отправить сообщение NSB, а затем заставить получателя сообщения вызвать хранимую процедуру. В прошлом я делал это с проблемными слушателями до и после событий, используя NHibernate и NSB.

Еще одна мысль состоит в том, чтобы ваш прослушиватель до события создал свой собственный объект соединения, завернутый в красивое выражение using, тогда он не коснется соединения NHibernate. Если он заблокирован, просто сделайте бросок и убедитесь, что вы удалили любой объект из области видимости.

0 голосов
/ 21 декабря 2011

не точный ответ, но я подозреваю, что у вас есть некоторые проблемы с управлением сеансом, и что вы используете один и тот же сеанс для нескольких обращений к обработчикам.я не думаю, что это на самом деле соединение, которое находится в плохом состоянии, а скорее сеанс nhibernate.кажется, это не вяжется с тем, что вы не видите проблему с отключенным пулом соединений, так что я могу быть вне базы, но я все еще подозреваю, что это связано с повторным использованием сессий.

первое, что я бы предложилв том, что вы пытаетесь подтвердить это, регистрируя хэш-код сеанса и хэш-код сеанса. GetSessionImplementation () (мое понимание использования средства замка nhibernate заключается в том, что вы увидите один и тот же экземпляр сеанса, даже если это на самом деле другойсеанс и реализация сеанса фактически покажет разницу).Посмотрите, видите ли вы одинаковые хеш-коды, используемые при обработке разных сообщений.

Если речь идет об управлении сессиями, попробуйте использовать модуль nservicebus для управления вашими сессиями для ваших обработчиков.вот сообщение от Андреаса об этом.я не думаю, что его редакция о том, как сделать это встроенным в ствол, была в версии 2.5, так что вы, вероятно, хотите продолжить.(Я могу ошибаться.)

http://andreasohlund.net/2010/02/03/nhibernate-session-management-in-nservicebus/

...