-->

Integrity of my transaction is lost with “Transact

2019-02-28 21:08发布

问题:

I have a code that tries to insert entries scoped in a MSDTC transaction and if there is insert failure then insertion is retried up to a specific threshold.

Here is the code:

while(!SaveToDb){
    .......
 Thread.Sleep(TimeSpan.FromMinutes(AppConfiguration.RetryInsertionDuringFailureIntervalInMin));
}


    private bool SaveToDb()
    {
        try
        {
         ......
            using (var scope = new TransactionScope(TransactionScopeOption.Required, option))
            {
                Context.SaveEmail(_emailInfoList);
                Context.SaveSyncState(syncState);
                scope.Complete();
                return true;
            }

        }
        catch (Exception ex)
        {
         ........
            return false;
        }
    }

And there is this exception encountered:

Message : The transaction is in doubt. Stack Trace : at System.Transactions.TransactionStatePromotedIndoubt.PromotedTransactionOutcome(InternalTransaction tx) at System.Transactions.CommittableTransaction.Commit() at System.Transactions.TransactionScope.InternalDispose() at System.Transactions.TransactionScope.Dispose() at Presensoft.Exchange2010Puch.Core.PushJob.SaveEmailAndSyncState() InnerException : System.Data.SqlClient.SqlException (0x80131904): Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stat eObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) at System.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync()
at System.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket()
at System.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer() at System.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) 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.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment)

After this application tries to reinsert the entries and what follows is

System.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'pk_email'.

It looks to me that without transaction.commit() firing up successfully (partial) commit already took place in the first case(MSDTC exception) which led to the latter exception.

Is there any way I can find out whether the commit has happened when there is "transaction is in doubt" exception and roll back the commit if there is any.

回答1:

According to MSDN:

This exception is thrown when an action is attempted on a transaction that is in doubt. A transaction is in doubt when the state of the transaction cannot be determined. Specifically, the final outcome of the transaction, whether it commits or aborts, is never known for this transaction.

This exception is also thrown when an attempt is made to commit the transaction and the transaction becomes InDoubt.

This is a recoverable error.

EDIT:

For recovery: You have to catch TransactionInDoubtException& write compensate logic with apt checks.

using (var scope = new TransactionScope(TransactionScopeOption.Required, option))
    {
        try
        {
            Context.SaveEmail(_emailInfoList);
            context.SaveSyncState(syncState);
            scope.Complete();
            return true;
        }
        catch (TransactionInDoubtException ex)
        {
            //check whether any one record from the batch has been partially committed . If committed then no need to reprocess this batch.     

            // transaction scope should be disposed first .

            scope.Dispose();

            if (IsReprocessingNeeded(syncState))
                throw;

            return true;
        }
    }

        /// <returns></returns>
        private bool IsReprocessingNeeded(SyncStateDataModal syncState)
        {
            while (true)
            {
                try
                {
                    var id = _emailInfoList[0].ID;
                    bool isEmailsCommitted = Context.GetJournalEmail().FirstOrDefault(a => a.ID == id) != null;
                    if (!isEmailsCommitted)
                        return true;
                    if (context.EmailSynch(syncState.Id) == null)
                    {
                        context.SaveSyncState(syncState);
                    }
                    return false;
                }
                catch (Exception ex)
                {

                    Thread.Sleep(TimeSpan.FromMinutes(AppConfiguration.RetryConnectionIntervalInMin));
                }
            }
        }

SOURCE What is the recovery path for TransactionInDoubtException ?