TransactionalSessions: Exception after response has started

I’m using the sample application for TransactionalSessions (Using TransactionalSession with Entity Framework and ASP.NET Core • NServiceBus.Persistence.Sql.TransactionalSession Samples • Particular Docs) but have changed it to use a local RabbitMQ container as the transport. If the container dies mid-request, the control message fails to dispatch. Because of the exception, the transaction is rolled back, as expected.

The part I am having trouble with is that the dispatch exception does not happen until after the HttpResponse has started. The client receives a successful status code even though the transaction was rolled back.

I have attached a POC with a simple readme to recreate this problem.
POC.zip (67.4 KB)

It seems that middleware may not be the right place to open & commit a transactional session.

1 Like

Hi Mike,

While writing the sample we have never thought about the status code problem I must say. It seems that this is quite tricky because when the middleware executed the next then the response is already written and it is no longer possible to modify the headers.

What seems to work is

        httpContext.Response.OnStarting(async state =>
        {
            await ((ITransactionalSession)state).Commit();
        }, session);

        await session.Open(new SqlPersistenceOpenSessionOptions());

        await next(httpContext);

but unfortunately, there is no way to actually add a specific status code other than getting the InternalServerError

The obvious workaround for now would be moving the session handling into the controller action. Then you can use exception filters to globally handle the status code for session commit failures but you would still need explicit session handling code within the controller which is the thing we wanted to avoid.

I’ll think a bit more about this and keep you posted. Once we have a better approach, we will also update the sample.

Regards,
Daniel

We considered moving the session code to the controllers. We are adding messaging to a large existing RESTful API and wanted to treat that option as a last resort. I like your solution, it is much simpler. It would be great to provide a better status code but I’ll take the 500 to get our dev teams moving again. I’ll keep digging to see if I can build off of the solution you provided.

Thanks, I appreciate the help. I’ll keep an eye out for any updates and follow up here if I find another approach.

I do apologize if the answer to my question is obvious, but I was a bit confused by the documentation. Mike’s sample and Daniel’s answer help, but I am still not completely clear on the outcomes of this pattern.

The documentation says, “ exactly one of the following outcomes occur:

  • Transaction finishes with data being stored, and outgoing messages eventually sent - when the Commit path successfully stores the OutboxRecord
  • Transaction finishes with no visible side effects - when the control message stores the OutboxRecord

Then, it says “ If dispatching the control message fails, the transactional session changes will roll back, and an error will be raised to the user committing the session.”

Are there 2 or 3 potential outcomes? Is the failure to dispatch the control message the same as the second outcome? Perhaps it is just the wording.

More importantly, how are the results detected/differentiated in these 2 or 3 different outcomes?

Thanks in advance for clarifying.

Failure to send the control messages means it can’t commit (thus rollback) the database transaction as these are sequential.

It could happen that the control messages succeed but the storage transaction fails because some database error. In that case no issue as the outbox records pointed by the control messages will not exist and can be ignored after a timeout period.

Step 1: Send a control message directly to the transport
Step 2: Commit database transaction with a business data update and prepared outgoing messages
Step 3: Process the control message of step 1 and dispatch the prepared outgoing messages to the transport

Failure to send the control message means the database will never be committed. State wise that means nothing got modified and no messages are dispatched to the transport.

The failure scenarios section you are highlighting is explaining what happens when a failure would occur during database commit or when the control message was dispatched to the transport. The outcome for either is the same, no committed data and an exception.

Does that help? Would you have a suggestion on how to improve that section?

Hi @mambrow

I wanted to let you know that I have started working on improving the sample.

By moving everything to a resource filter, it is possible to annotate controller actions properly to make sure the control message is only used when really required. This approach would also allow modifying the response in case of a failure.

I’ll keep you posted once the approach is fully reviewed and done.

Regards,
Daniel

@mambrow as promised, the sample has been updated

It shows now a better approach on how to handle the session integration with ASP.NET Core that should also enable controlling the result when the commit fails.

Hope that helps
Daniel

1 Like