Giter Club home page Giter Club logo

rebus.transactionscopes's Introduction

Rebus.TransactionScopes

install from nuget

Provides a System.Transactions.TransactionScope helper for Rebus.


Use it like this when you send/publish things from anywhere besides inside Rebus handlers:

using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
	scope.EnlistRebus();

	// this one is automatically enlisted in the ambient .NET transaction
	await _bus.Send("ostemad");

	scope.Complete();
}

Use it like this to have Rebus handlers invoked inside a TransactionScope:

Configure.With(...)
    .(...)
    .Options(o =>
    {
        o.HandleMessagesInsideTransactionScope();
    })
    .Start();

By default, the transaction scope will use the IsolationLevel.ReadCommitted isolation level with a 1 minute timeout. These values can be configured by passing an instance of TransactionOptions to HandleMessagesInsideTransactionScope.

That's about it.

rebus.transactionscopes's People

Contributors

davidzidar avatar larsw avatar leendertdommicent avatar mookid8000 avatar nebelx avatar thomasdcses avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

nebelx

rebus.transactionscopes's Issues

Using HandleMessagesInsideTransactionScope breaks second-level retries

Since upgrading our services to .NET 8 and upgrading Rebus to version 8.x, second-level retry messages are no longer being dispatched.

Our setup makes use of the Rebus core, Rebus.ServiceProvider and Rebus.TransactionScopes packages. The issue does not appear to be related to the specific transport being used.

See this Gist for a minimal reproducible example.

Rebus will correctly attempt first-level retries, but fails to dispatch an IFailed message for the 2nd level. An exception (System.Transactions.TransactionAbortedException: The transaction has aborted.) will be thrown instead, and the message will be sent to the error queue.

Full log output:

info: Repro.ErrorMessageHandler[0]
      Handling ErrorMessage
[WRN] Rebus.Retry.Simple.DefaultExceptionLogger (Rebus 1 worker 1): Unhandled exception 1 while handling message with ID "bd502ade-f010-43a7-a84a-fadd439bce46"
System.Exception: Error
   at Repro.ErrorMessageHandler.Handle(ErrorMessage message) in C:\Users\BavoLuysterborg\source\repos\Repro\ErrorMessageHandler.cs:line 24
   at Rebus.Pipeline.Receive.HandlerInvoker`1.Invoke()
   at Rebus.Pipeline.Receive.DispatchIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Sagas.LoadSagaDataStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.ActivateHandlersStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleRoutingSlipsStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.FailedMessageWrapperStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.TransactionScopes.TransactionScopeIncomingStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.DeserializeIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.DataBus.ClaimCheck.HydrateIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleDeferredMessagesStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.DefaultRetryStep.Process(IncomingStepContext context, Func`1 next)
info: Repro.ErrorMessageHandler[0]
      Handling ErrorMessage
[WRN] Rebus.Retry.Simple.DefaultExceptionLogger (Rebus 1 worker 1): Unhandled exception 2 while handling message with ID "bd502ade-f010-43a7-a84a-fadd439bce46"
System.Exception: Error
   at Repro.ErrorMessageHandler.Handle(ErrorMessage message) in C:\Users\BavoLuysterborg\source\repos\Repro\ErrorMessageHandler.cs:line 24
   at Rebus.Pipeline.Receive.HandlerInvoker`1.Invoke()
   at Rebus.Pipeline.Receive.DispatchIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Sagas.LoadSagaDataStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.ActivateHandlersStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleRoutingSlipsStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.FailedMessageWrapperStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.TransactionScopes.TransactionScopeIncomingStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.DeserializeIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.DataBus.ClaimCheck.HydrateIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleDeferredMessagesStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.DefaultRetryStep.Process(IncomingStepContext context, Func`1 next)
[ERR] Rebus.Retry.PoisonQueues.DeadletterQueueErrorHandler (Rebus 1 worker 1): Moving message with ID "bd502ade-f010-43a7-a84a-fadd439bce46" to error queue "error" - error details: "3 unhandled exceptions: 02/02/2024 16:24:18 +01:00: System.Exception: Error
   at Repro.ErrorMessageHandler.Handle(ErrorMessage message) in C:\Users\BavoLuysterborg\source\repos\Repro\ErrorMessageHandler.cs:line 24
   at Rebus.Pipeline.Receive.HandlerInvoker`1.Invoke()
   at Rebus.Pipeline.Receive.DispatchIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Sagas.LoadSagaDataStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.ActivateHandlersStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleRoutingSlipsStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.FailedMessageWrapperStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.TransactionScopes.TransactionScopeIncomingStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.DeserializeIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.DataBus.ClaimCheck.HydrateIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleDeferredMessagesStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.DefaultRetryStep.Process(IncomingStepContext context, Func`1 next)

02/02/2024 16:24:19 +01:00: System.Exception: Error
   at Repro.ErrorMessageHandler.Handle(ErrorMessage message) in C:\Users\BavoLuysterborg\source\repos\Repro\ErrorMessageHandler.cs:line 24
   at Rebus.Pipeline.Receive.HandlerInvoker`1.Invoke()
   at Rebus.Pipeline.Receive.DispatchIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Sagas.LoadSagaDataStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.ActivateHandlersStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleRoutingSlipsStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.FailedMessageWrapperStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.TransactionScopes.TransactionScopeIncomingStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.DeserializeIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.DataBus.ClaimCheck.HydrateIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleDeferredMessagesStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.DefaultRetryStep.Process(IncomingStepContext context, Func`1 next)

02/02/2024 16:24:19 +01:00: System.Transactions.TransactionAbortedException: The transaction has aborted.
   at System.Transactions.TransactionStateAborted.CreateAbortingClone(InternalTransaction tx)
   at System.Transactions.DependentTransaction..ctor(IsolationLevel isoLevel, InternalTransaction internalTransaction, Boolean blocking)
   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 Rebus.TransactionScopes.TransactionScopeIncomingStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.DeserializeIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.DataBus.ClaimCheck.HydrateIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Pipeline.Receive.HandleDeferredMessagesStep.Process(IncomingStepContext context, Func`1 next)
   at Rebus.Retry.Simple.DefaultRetryStep.DispatchSecondLevelRetry(ITransactionContext transactionContext, StepContext context, Func`1 next)
   at Rebus.Retry.Simple.DefaultRetryStep.HandleException(Exception exception, ITransactionContext transactionContext, String messageId, IncomingStepContext context, Func`1 next)"
[WRN] Rebus.Workers.ThreadPoolBased.ThreadPoolWorker (.NET TP Worker): Error in transaction context
System.Transactions.TransactionAbortedException: The transaction has aborted.
   at System.Transactions.TransactionStateAborted.BeginCommit(InternalTransaction tx, Boolean asyncCommit, AsyncCallback asyncCallback, Object asyncState)
   at System.Transactions.CommittableTransaction.Commit()
   at System.Transactions.TransactionScope.InternalDispose()
   at System.Transactions.TransactionScope.Dispose()
   at Rebus.TransactionScopes.TransactionScopeTransportDecorator.<>c__DisplayClass7_0.<Receive>b__1(ITransactionContext _)
   at Rebus.Transport.TransactionContext.Dispose()

TransactionScope issue

Hi,

Based on the example that works file I tried to adapted it to my situation.
I'm using an abstraction to send/publish my messages using Rebus, this is my publisher:
`public class BusPublisher : IBusPublisher
{
private readonly IBus _rebus;
private TransactionScope _transactionScope;

    public BusPublisher(IBus rebus)
    {
        _rebus = rebus;
    }

    public Task PublishAsync(object message, CancellationToken token = default)
    {
        return _rebus.Publish(message);
    }

    public async Task SendAsync(object command, CancellationToken token = default)
    {
        await _rebus.Send(command);
    }

    public Task BeginTransactionAsync()
    {
        //Check if we are already inside RebusTransaction
        //If yes we keep it, otherwise we create a new transaction
        if (AmbientTransactionContext.Current == null)
        {
            _transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
            _transactionScope = _transactionScope.EnlistRebus();
        }

        return Task.CompletedTask;
    }

    public Task CommitAsync(CancellationToken token = default)
    {
        _transactionScope?.Complete();
        return Task.CompletedTask;
    }

    protected override void OnDispose()
    {
        _transactionScope?.Dispose();
    }
}`

Usage:

var publisher = new BusPublisher(rebus); await publisher.BeginTransactionAsync(); await publisher.SendAsync(myCommand); await publisher.CommitAsync();

My command is sent to the Queue before commiting the transaction.

Maybe the TransactionScope instantiation and sending the message are not in the same code bloc? (same thread)

Thank you in advance.

TransactionScope is not picked up when used in combination with Rebus.TransactionScopes

I'm using Rebus.SqlServer in combination with Rebus.TransactionScopes to make sure that bus.Send(...) commands are processed as one transaction inside a message handler and that an exception thrown after a send causes the message not to be queued since the transaction is rollbacked.

However, this doesn't seem to be the case when using Rebus.SqlServer as a transport layer.

I use enlistInAmbientTransaction (from Rebus.SqlServer) and options.HandleMessagesInsideTransactionScope() (from Rebus.TransactionScopes) so I expect that persisting the message happens in the TransactionScope created by the TransactionScopeIncomingStep. It seems as if the pipeline steps are executed after the DbConnection was fetched to persist the message so that the TransactionScope is not picked up.

Am I making any sense?
If you want I can reproduce the issue by writing a failing integration test.

await call in handler makes Transaction.Current lose its context

When awaiting code in a handler that's decorated with an ambient TransactionScope, Transaction.Current looses its context.
Probably it's because of this line that reassigns Transaction.Current. I guess the TransactionScopeAsyncFlowOption isn't maintained when using the setter on Transaction.Current.

As mentioned here Transaction.Current is ThreadStatic and all code should preferably manipulate an ambient context via a using statement on TransactionScope.

I have a failing test that reproduces the issue.

Can you confirm the bug?
Is there a reason you removed the using statement in this commit?

Why Rebus.TransactionScopes 4.0.1 was not released to nuget?

When using Rebus 5 and latest nuget version Rebus.TransactionScopes 3.0.1, when calling transactionScope.EnlistRebus(), it gives me an exception "Could not load type 'Rebus.Transport.DefaultTransactionContextScope' from assembly 'Rebus, Version=5.0.0.0" (as version Rebus.TransactionScopes 3.0.1 references Rebus 3). When using Rebus.TransactionScopes 4.0.1 (https://github.com/rebus-org/Rebus.TransactionScopes/releases/tag/4.0.1, manually built), it works fine.

My scenario, which works fine with Rebus.TransactionScopes 4.0.1 and msmq transport, is following:

  1. asp.net mvc controller method is called within a transaction scope (with rebus enlisted into the transaction scope - transactionScope.EnlistRebus()), controller method processes some business logic in a DB transaction (enlisted in the transaction scope), and sends a message to a service app (_bus.Send(message)).
  2. when the business logic fails, the message is not sent correctly.

This scenario works fine with NServiceBus out of the box, and I was looking for an alternative free service bus library which supports this scenario. But, I had to manually build Rebus.TransactionScopes 4.0.1 instead downloading it from nuget.

I'm wondering why Rebus.TransactionScopes 4.0.1 was not released to nuget? Is it not stable or reliable enough?

Rebus.TransactionScopes is not compatible with Rebus 6.0.0

Hi, while trying to upgrade to Rebus I noticed that Rebus.TransactionScopes is not compatiable with version 6 due to a breaking change in the arguments of ITransactionContext.OnCompleted and ITransactionContext.OnDisposed. The delegate signatures have changed from Func<Task> and Action to Func<ITransactionContext, Task> and Action<ITransactionContext> respectively, so a new version of Rebus.TransactionScopes needs to be released with these changes made.

While investigating this issue I noticed two other minor issues as well:

  • There is a .NET Framework reference to System.Transactions that should be conditional, it is already included in netstandard2.0 so it results in a build warning
  • Both the link and the badge referencing the NuGet-package are incorrect, they are both referencing the old unlisted package Rebus.TransactionScope

I have prepared a branch in a fork with working fixes for all three issues and could send a pull request unless you want to fix these issues yourselves.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.