Exception escapes from workflow despite TryCatch activity - c#

I have a workflow inside a Windows Service that is a loop that performs work periodically. The work is done inside a TryCatch activity. The Try property is a TransactionScope activity that wraps some custom activities that read and update a database. When the transaction fails, I would expect any exception that caused this to be caught by the TryCatch. However, my workflow aborts. The workflow I have is the following:
var wf = new While(true)
{
Body = new Sequence
{
Activities =
{
new TryCatch
{
Try = new TransactionScope
{
IsolationLevel = IsolationLevel.ReadCommitted,
Body = new Sequence
{
Activities = { ..custom database activities.. }
},
AbortInstanceOnTransactionFailure = false
},
Catches =
{
new Catch<Exception>
{
Action = new ActivityAction<Exception>
{
Argument = exception,
Handler = ..log error..
}
}
}
},
new Delay { Duration = new InArgument<TimeSpan>(duration) }
}
},
}
In my case, it's possible that the database is sometimes unavailable so obviously the transaction won't commit. What happens in this case is that the workflow aborts with the following exception:
System.OperationCanceledException: An error processing the current work item has caused the workflow to abort.
The inner exception is:
System.Transactions.TransactionException: The operation is not valid for the state of the transaction.
This makes sense because I have just switched off the database. However, why isn't this exception handled by my TryCatch activity?
EDIT 1: Some additional information. I run the workflow using the WorkflowApplication class. To better see what's going on, I specified the properties Aborted and OnUnhandledException. When the exception occurs, it goes directly to Aborted and OnUnhandledException is skipped (although this is clearly an unhandled exception).
EDIT 2: I enabled the debug log and this provides some additional insight. The 'custom database activities' successfully run to completion. The first event log entry that indicates that something is wrong is a Verbose level message: The runtime transaction has completed with the state 'Aborted'. Next I see an Information message: WorkflowInstance Id: 'dbd1ba5c-2d8a-428c-970d-21215d7e06d9' E2E Activity (not sure what this means). And the Information message after that is: Activity 'System.Activities.Statements.TransactionScope', DisplayName: 'Transaction for run immediately checks', InstanceId: '389' has completed in the 'Faulted' state.
After this message, I see that each parent (including the TryCatch activity) completes in the 'Faulted' state, ending with the abortion of my workflow.
EDIT 3: To be clear, everything works as expected when an exception occurs in any of the 'custom database activities'. The exception is caught and the workflow continues. It only goes wrong when the transaction can't commit at the end of the TransactionScope. See the following stacktrace that is logged from the Aborted callback:
at System.Transactions.TransactionStateInDoubt.Rollback(InternalTransaction tx, Exception e)
at System.Transactions.Transaction.Rollback(Exception e)
at System.Activities.Runtime.ActivityExecutor.CompleteTransactionWorkItem.HandleException(Exception exception)
If you follow the calls from TransactionScope.OnCompletion(...), eventually you will arrive at the ActivityExecutor class from the stacktrace.

Transactions commit asynchronously and after the fact. You can't react to a failure of the transaction to commit because of a problem at the resource manager level.
As you pointed out, you can deal with exceptions that occur in your activities. If you look at the tracking records for your workflow my guess is that you would see the TryCatch activity is closed prior to the transaction abort.
Many years ago when I was a program manager in the COM+ team I studied this issue because often people want a transactional component (or workflow) as in this case to be able to react to a transaction abort.
The async nature of the resolution of the transaction means that you simply cannot react to it in the component itself. The solution is to react in the caller which can then take some action.
The design assumption is that once a transaction has aborted, nothing about state aqcuired in the transaction can be safely used - it will all be discarded because the transaction is aborted.

So just to add to Ron's answer. Your only option here is to add the SqlWorkflowInstanceStore and drop a Persist activity just before the TransactionScope. When the transaction aborts the whole workflow will abort but the past saved state will still be in the persistence database and the workflow can be restarted from this previously saved state and execute the transaction again.

Related

How to know if transaction scope was successful or not

I need to know if my transaction scope was successful or not. As in if the records were able to be saved in the Database or not.
Note: I am having this scope in the Service layer, and I do not wish to include a Try-Catch block.
bool txExecuted;
using (var tx = new TransactionScope())
{
//code
// 1 SAVING RECORDS IN DB
// 2 SAVING RECORDS IN DB
tx.Complete();
txExecuted = true;
}
if (txExecuted ) {
// SAVED SUCCESSFULLY
} else {
// NOT SAVED. FAILED
}
The commented code will be doing updates, and will probably be implemented using ExecuteNonQuery() - this returns an int of the number of rows affected. Keep track of all the return values to know how many rows were affected.
The transaction as a whole will either succeed or experience an exception when it completes. If no exception is encountered, the transaction was successful. If an exception occurs, some part of the transaction failed; none of it took place
By considering these two facts (records affected count, transaction exception or no) you can know if the save worked and how many rows were affected
I didn't quite understand the purpose of txExecuted- if an exception occurs it will never be set and the if will never be considered. The only code that will thus run is the stuff inside if(true). I don't see how you can decide to not use a try/catch and hope to do anything useful with a system that is geared to throw an exception if something goes wrong; you saying you don't want to catch exceptions isn't going to stop them happening and affecting the control flow of your program
To be clear, calling the Complete() method is only an indication that all operations within the scope are completed successfully.
However, you should also note that calling this method does not
guarantee a commit of the transaction. It is merely a way of informing
the transaction manager of your status. After calling this method, you
can no longer access the ambient transaction via the Current property,
and trying to do so results in an exception being thrown.
The actual work of commit between the resources manager happens at the
End Using statement if the TransactionScope object created the
transaction.
Since you are using ADO.NET, ExecuteNonQuery will return the number of rows affected. You can do a database lookup after the commit and outside of the using block.
In my opinion, its a mistake not to have a try/catch. You want to catch the TransactionAbortedException log the exception.
try
{
using (var scope = new TransactionScope())
{
using (var conn = new SqlConnection("connection string"))
{
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
// log
}

MassTransit Activity Fault with parameters

I am currently using Masstransit in with the Courier pattern.
I´ve set up an Activity which may fail, and I want to be able to subscribe to this failure and act accordingly.
My problem is, even though I can subscribe to the failure, and even see the exception that caused the failure, I am unable to pass any arguments to it.
For testing purposes, supose I have the following activity:
public class MyActivity : ExecuteActivity<MyMessage>
{
public Task<ExecutionResult> Execute(ExecuteContext<MyMessage> context)
{
try
{
// .... some code
throw new FaultException<RegistrationRefusedData>(
new RegistrationRefusedData(RegistrationRefusedReason.ItemUnavailable));
// .... some code
}
catch (Exception ex)
{
return Task.FromResult(context.Faulted(ex));
}
}
}
The problem is in the reason (RegistrationRefusedReason) I am passing as a argument of the exception. If I subscribe a RoutingSlipActivityFaulted consumer, I can almost get all the information I need:
public class ActivityFaultedConsumer : IMessageConsumer<RoutingSlipActivityFaulted>
{
public void Consume(RoutingSlipActivityFaulted message)
{
string exceptionMessage = message.ExceptionInfo.Message; // OK
string messageType = message.ExceptionInfo.ExceptionType; // OK
RegistrationRefusedReason reason = ??????;
}
}
I feel like I am missing something important here, (maybe misusing the pattern?).
Is there any other way to get parameters from a faulted activity ?
So, the case you're describing isn't a Fault. It's a failure to meet a business condition. In this case, you wouldn't want to retry the transaction, you'd want to terminate it. To notify the initiator of the routing slip, you'd Publish a business event signifying that the transaction was not completed due to the business condition.
For instance, in your case, you may do something like:
context.Publish<RegistrationRefused>(new {
CustomerId = xxx,
ItemId = xxxx,
Reason = "Item was unavailable"
});
context.Terminate();
This would terminate the routing slip (the subsequent activities would not be executed), and produce a RoutingSlipTerminated event.
That's the proper way to end a routing slip due to a business condition or rule. Exceptions are for exceptional behavior only, since you'll likely want to retry them to handle the failure.
Kinda raising this from the dead, but I really haven't found a neat solution to this.
Here is my scenario:
I want to implement a request/response, but I want to wait for the execution of a routing slip.
As Fabio, I want to compensate for any previous activities and I want to pass data back to the request client in case of a fault.
Conveniently, Chris provided a RoutingSlipRequestProxy/RoutingSlipResponseProxy which does just that. I've found 2 approaches, but both of them seem very hacky to me.
Approach 1:
The request client waits for ISimpleResponse or ISimpleFailResponse.
RoutingSlipRequestProxy sets the ResponseAddress in the variables.
The activity sends ISimpleFailResponse to the ResponseAddress.
The client waits for either response
The RoutingSlipResponseProxy sends back Fault<ISimpleResponse> to the ResponseAddress.
From what I see the hackiness comes from step 4/5 and their order. I am pretty sure it works, but it could easily stop working in case messages are consumed out-of-order.
Sample code: https://github.com/steliyan/Sample-RequestResponse/commit/3fcb196804d9db48617a49c7a8f8c276b47b03ef
Approach 2:
The request client waits for ISimpleResponse or ISimpleFailResponse.
The activity calls ReviseItirery with the variables and adds a faulty activity.*
The faulty activity faults
The RoutingSlipResponseProxy2 get the ValidationErrors and sends back ISimpleFailResponse to the ResponseAddress.
* The activity needs to be Activity and not ExecuteActivity because there is no overload of ReviseItinerary with variables but with no activity log.
This approach seems hacky because an additional fault activity is added to the itinerary, just to be able to add a variable to the routing slip.
Sample code: https://github.com/steliyan/Sample-RequestResponse/commit/e9644fa683255f2bda8ae33d8add742f6ffe3817
Conclusion:
Looking at MassTransit code, it doesn't seem like a problem to add a FaultedWithVariables overload. However, I think Chris' point is that there should be a better way to design the workflow, but I am not sure about that.

Task database error in App Insight

I am having an issue with Application Insight reporting about exceptions with EF context while saving to database.
private async Task SendAsync(IEmailDataModel model)
{
await _emailHelper.SendAsync(model.To, model.Subject, model.Body);
/* send messages */
var logEmail = new LogEmail
{
UserSignatureId = GetUserSignature(model.To),
LogTaskId = model.LogTaskId,
Type = (int)model.Type,
Subject = model.Subject,
From = _defaultConfig.SupportEmailAddress,
To = model.To,
Content = model.Body,
CreatedOn = DateTime.UtcNow
};
_unit.Repository<LogEmail>().Insert(logEmail);
await _unit.SaveChangesAsync();
}
How am I suppose to debug this? I am using WebJobs with TimerTrigger.
If App Insights is telling you this exception occurred, it's because the exception is really occurring.
Chances are, somewhere in the stack, whoever calls SendAsync isn't awaiting that, or farther and farther back, someone isn't calling await on one of these async methods at some point.
So when the change is rejected (which the inner exception says it was, for validation reasons), nobody is around to catch it and tell anyone about it, so the finalizer is throwing the exception that nobody handled.
And application insights is registered to log unhandled exceptions, so it is showing you exceptions that you otherwise might not be aware of.

WCF msmq transactioned and unit of work

I built a MSMQ WCF service that is transactional. I used the following attribute on my operation:
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
I am using Nhibernate in service . Using Nhibernate I give a commit on my session. If I disable the Nhibernate commit the message is correctly processed and removed from the queues. With this commit, Nhibernate transaction goes correctly but my message goes into the retry queue.
Here is the exception that I get into Nhibernate service trace.
Description Handling an exception. Exception details: System.Transactions.TransactionAbortedException: The transaction has aborted. ---> System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Transaction'.
at System.Transactions.Transaction.DependentClone(DependentCloneOption cloneOption)
at System.Transactions.TransactionScope.SetCurrent(Transaction newCurrent)
at System.Transactions.TransactionScope.PushScope()
at System.Transactions.TransactionScope.Initialize(Transaction transactionToUse, TimeSpan scopeTimeout, Boolean interopModeSpecified)
at System.Transactions.TransactionScope..ctor(Transaction transactionToUse, TransactionScopeAsyncFlowOption asyncFlowOption)
at System.Transactions.TransactionScope..ctor(Transaction transactionToUse)
at NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext.System.Transactions.IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
--- End of inner exception stack trace ---
at System.Transactions.TransactionStatePromotedAborted.PromotedTransactionOutcome(InternalTransaction tx)
at System.Transactions.TransactionStatePromotedEnded.EndCommit(InternalTransaction tx)
at System.Transactions.CommittableTransaction.Commit()
at System.ServiceModel.Dispatcher.TransactionInstanceContextFacet.Complete(Transaction transaction, Exception error)
It seems that the nhibernate commit destroys the transaction on WCF. I cannot find the way to fix this.
Any help may be appreciated
I'm not too familiar with these systems, but the simplest answer is usually the right one, so I'll give it a shot - at a guess, I would say that whatever service you're calling the items to is stopping the process that removes the items before it has a chance to remove them, so I would add some sort of function call into the service you're calling the items to so it is forced to remove the item from the list before it can finish the transaction.
Of course, I'm not familiar with this topic, so don't take my word for it - that's just generally what I would do for a similar problem within the bounds of my programming knowledge.

Wcf transaction

Is there a way to know in a wcf operation that a transaction has committed?
Ok, second attempt into being more specific.
I got a WCF service with an Operation with Transaction flow allow.
Now when a client call my wcf service it can have a transaction. But my service is also interested in the fact that the transaction on the client has succeeded. Because on my wcf service level, if everything went well. It has other things to do, but only if all transactions has been committed....
Is there like an event I can subscribe to or something?
It depends on the service itself and how you are handling transactions. If you are engaging in transactions in WCF through WS-Transaction then if the call to the client succeeds without exception, you can assume the transaction took place.
However, if this is in the context of another transaction, then you can't be sure if the transaction went through until the containing transaction is completed.
Even if you are using the TransactionScope class, if you have the service enabled to use transactions, you still have to take into account the encompassing transaction (if there is one).
You will have to provide more information about where the transaction is in relation to the call in order for a more complete answer.
Try using the operation behavior attribute above, in your operation that allows TransactionFlow:
[OperationBehavior(TransactionScopeRequired=true)]
If a transaction flows from the client, then the service will use it.
bool isTransactionComplete = true;
try
{
using (TransactionScope trScope = new TransactionScope(TransactionScopeOption.Required))
{
//some work
trScope.Complete();
}
}
catch (TransactionAbortedException e)
{
//Transaction holder got exception from some service
//and canceled transaction
isTransactionComplete = false;
}
catch//other exception
{
isTransactionComplete = false;
throw;
}
if (isTransactionComplete)
{
//Success
}
As casperOne wrote it depends on the settings. But you should be aware of complex transactions like
1) session service and simultaneous transactions for one service instance
2) transaction inside transaction

Categories

Resources