Timeout-Exception bei Transaction-Rollback

1. Dezember 2010

Als Entwickler sieht man sich tagtäglich mit neuen Herausforderungen konfrontiert, deren Lösung meist eine zeitaufwändige Fehleranalyse beinhaltet.

So auch bei einem meiner aktuellen Probleme. Die Herausforderung stellte ein .NET-Programm mit einer langlaufenden Transaktion auf dem SQL Server dar, in die eine ganze Menge an Daten geschrieben wurden. Dies stellt in dem Kontext der Anwendung kein Problem dar und ist aufgrund des Prozesses kaum vermeidbar. Zu Test- und Entwicklungszwecken ließ ich diese DbTransaction bei Beendigung der Verarbeitung einen Rollback() durchführen.

Und hier lag auch schon das Problem. Gelegentlich und ohne erkennbares Muster warf die Rollback()-Methode eine SqlException, die einen Timeout der Aktion nahelegte:

Message: 
Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
 
StackTrace: 
    at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) 
    at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj) 
    at System.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) 
    at System.Data.SqlClient.TdsParserStateObject.ReadSni(DbAsyncResult asyncResult, TdsParserStateObject stateObj) 
    at System.Data.SqlClient.TdsParserStateObject.ReadPacket(Int32 bytesExpected) at System.Data.SqlClient.TdsParserStateObject.ReadBuffer() 
    at System.Data.SqlClient.TdsParserStateObject.ReadByte() 
    at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) 
    at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
    at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) 
    at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransaction(TransactionRequest transactionRequest, String name, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) 
    at System.Data.SqlClient.SqlInternalTransaction.Rollback() at System.Data.SqlClient.SqlTransaction.Rollback()

Nach langer langer Fehleranalyse und Google-Suche stieß ich dann auf einen MSDN-Forum-Eintrag, der genau dieses Problem thematisiert. Im darin enthaltenen Quellcode der SqlInternalConnection.ExecuteTransactionYukon()-Methode des .NET-Frameworks ist erkennbar, dass als Timeout für die Rollback-Aktion der eingestellte ConnectionTimeout verwendet wird!

Das ist schon verwunderlich! Der ConnectionTimeout ist eigentlich dafür gedacht, die maximale Zeit zum Herstellen einer Verbindung zum DB-Server festzulegen. Hingegen wird er hier für den Timeout der Rollback-Aktion quasi missbraucht, was intuitiv so nicht erwartet wird.

Eine Rollback-Aktion kann im Vergleich zu einem Commit einer Transaktion durchaus einige Zeit dauern, die voreingestellten 15 Sekunden ConnectionTimeout führten hier sporadisch zu einer Exception. Nach einem moderaten Anheben des Timeouts über den Connection String konnte das Problem behoben werden.