question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

TransactionScope usage for IReceiverLink - message order / .Receive() issues

See original GitHub issue

Hello,

I’m trying to use TransactionScope for IRecevierLink - during the receive I would like to rollback the transaction in case of message processing failures, keeping the message order intact - but that didn’t worked for me.

Previously I was using old ActiveMQ.NMS with OpenWire protocol and this was working just fine, this is also working fine when using newer Apache.NMS.Amqp (which uses amqpnetlite library).

I’m not sure what I’m doing wrong or maybe this is Artemis misconfiguration (but still works with Apache.NMS.Amqp).

Here is my minimal example:

Console.WriteLine("Start TestTransaction");

var maxMessages = 5;
var queueName = "amqpnetlite.transactions";

var amqpConnection = new Connection(new Address("amqp://127.0.0.1:61616"));

var amqpSession = ((IConnection)amqpConnection).CreateSession();

var amqpSender = amqpSession.CreateSender("sender-" + Guid.NewGuid(), new Target
{
	Address = queueName,
	Capabilities = new[]
	{
	    new Symbol("queue")
	},
	Durable = 1
});

for (var i = 0; i < maxMessages; i++)
{
	using (var transactionScope = new TransactionScope())
	{
	    var currentMessage = new Message($"{i}, {Guid.NewGuid()}");

	    amqpSender.Send(currentMessage);

	    if (i % 2 == 0)
	    {
		Console.WriteLine($"Send, complete: {currentMessage.Body}");

		transactionScope.Complete();
	    }
	    else
	    {
		Console.WriteLine($"Send, rollback: {currentMessage.Body}");
	    }
	}
}

var amqpReceiver = amqpSession.CreateReceiver("receiver-" + Guid.NewGuid(), new Source
{
	Address = queueName,
	Capabilities = new[]
	{
	    new Symbol("queue")
	},
	Durable = 1
});

amqpReceiver.SetCredit(1, CreditMode.Auto);

for (var i = 0; i < maxMessages * 2; i++)
{
	Console.WriteLine($"Receive loop, {i}");

	using (var transactionScope = new TransactionScope())
	{
	    var currentMessage = amqpReceiver.Receive(TimeSpan.FromSeconds(2));

	    if (currentMessage == null)
	    {
		Console.WriteLine($"Receive, null");

		continue;
	    }

	    if (i % 2 == 0)
	    {
		Console.WriteLine($"Receive, complete: {currentMessage.Body}");

		amqpReceiver.Accept(currentMessage);

		transactionScope.Complete();
	    }
	    else
	    {
		Console.WriteLine($"Receive, rollback: {currentMessage.Body}");

		amqpReceiver.Accept(currentMessage);
	    }
	}
}

amqpConnection.Close();

Console.WriteLine("Done TestTransaction");

and the output for the first run:

Start TestTransaction
Send, complete: 0, c0d38d91-308c-4c82-b216-3bcf1f887287
Send, rollback: 1, b3801b45-4b94-40ae-9cc0-edde209cb1b9
Send, complete: 2, 48778884-6dc4-429d-a854-e88a23b5baa6
Send, rollback: 3, 2012c046-9e5a-4830-9b33-5120a0263bce
Send, complete: 4, 9580cc66-354d-4f0b-b973-0523841bf8a8
Receive loop, 0
Receive, complete: 0, c0d38d91-308c-4c82-b216-3bcf1f887287
Receive loop, 1
Receive, rollback: 2, 48778884-6dc4-429d-a854-e88a23b5baa6
Receive loop, 2
Receive, complete: 4, 9580cc66-354d-4f0b-b973-0523841bf8a8
Receive loop, 3
Receive, null
Receive loop, 4
Receive, null
Receive loop, 5
Receive, null
Receive loop, 6
Receive, null
Receive loop, 7
Receive, null
Receive loop, 8
Receive, null
Receive loop, 9
Receive, null
Done TestTransaction

and second run:

Start TestTransaction
Send, complete: 0, 1957cab3-e9b1-4275-b884-bfc082ab0aed
Send, rollback: 1, 0d697ae1-2b3c-4a7c-97f7-8a22b375e43c
Send, complete: 2, 7278430d-8e7e-4251-ac7b-231f31012195
Send, rollback: 3, cd16970f-da09-4334-8ca8-4868caf1339e
Send, complete: 4, 71509a7f-07ac-4841-95bd-493cb13d8c37
Receive loop, 0
Receive, complete: 2, 48778884-6dc4-429d-a854-e88a23b5baa6
Receive loop, 1
Receive, rollback: 0, 1957cab3-e9b1-4275-b884-bfc082ab0aed
Receive loop, 2
Receive, complete: 2, 7278430d-8e7e-4251-ac7b-231f31012195
Receive loop, 3
Receive, rollback: 4, 71509a7f-07ac-4841-95bd-493cb13d8c37
Receive loop, 4
Receive, null
Receive loop, 5
Receive, null
Receive loop, 6
Receive, null
Receive loop, 7
Receive, null
Receive loop, 8
Receive, null
Receive loop, 9
Receive, null
Done TestTransaction

The transaction for ISenderLink seems to work fine, but for IReceiverLink rollback messages are put back to the end of queue, moreover the example application needs second run to receive previously rollback messages. I expected that rollback messages are received on next .Receive() call, and everything is fetched on the first run:

Receive loop, 0
Receive, complete: 0, c0d38d91-308c-4c82-b216-3bcf1f887287
Receive loop, 1
Receive, rollback: 2, 48778884-6dc4-429d-a854-e88a23b5baa6
Receive loop, 2
Receive, complete: 2, 48778884-6dc4-429d-a854-e88a23b5baa6
Receive loop, 3
Receive, rollback: 4, 9580cc66-354d-4f0b-b973-0523841bf8a8
Receive loop, 4
Receive, complete: 4, 9580cc66-354d-4f0b-b973-0523841bf8a8
Receive loop, 5

Any ideas what I’m doing wrong?

Thank you, best regards!

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:13 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
xinchen10commented, May 11, 2020

Yes, it only completes the SinglePhaseEnlistment when the DischargeAsync task is completed (which happens when the outcome is received for the discharge message).

0reactions
jan-mariacommented, May 31, 2020

Hello,

Thanks for the idea - kinda works 😃 I mean - sometimes I’m receiving all messages without a problem, and sometimes I need to restart the consumer in order to get all messages.

I’ve changed the mode to CreditMode.Manual and it seems to work all the times - why? Does it have something to do with a null received messages?

Pseudo code:

while (true)
{
  receiverLink.SetCredit(1, CreditMode.Drain); // CreditMode.Manual

  var amqpMessage = await receiverLink
      .ReceiveAsync(TimeSpan.FromSeconds(5))
      .ConfigureAwait(false);

  var isSuccess = amqpMessage != null;

  if (!isSuccess)
  {
    continue;
  }

  try
  {
    var isProcessed = await Process(amqpMessage);

    if (isProcessed)
    {
      receiverLink.Accept(amqpMessage);
    }
    else
    {
      receiverLink.Release(amqpMessage);
    }
  }
  finally
  {
    amqpMessage.Dispose();
  }
}

Thank you, Best Regards,

Read more comments on GitHub >

github_iconTop Results From Across the Web

TransactionScope problem with Message queues
In ProcessDataRelatedWithThePreviousInserts I check for a condition and if needed, the rest of the work flow is redirected to a Message Queue in ......
Read more >
Troubleshooting Transactional Issues with TransactionScope
In this case, a transaction means a general way of structuring the interactions between autonomous agents in a distributed system. Each ...
Read more >
Implementing an Implicit Transaction using ...
The TransactionScope class provides a simple way to mark a block of code as participating in a transaction, without requiring you to ...
Read more >
TransactionScope in C# - TatvaSoft Blog
This blog will give you more idea about use of Transaction and TransactionScope. This is easy to use and simpler than other transaction ......
Read more >
Transport Transactions • NServiceBus
This article covers various levels of consistency guarantees with regards to: receiving messages; updating user data; sending messages.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found