0

What I'm really trying to do is leave the message on the queue in the case where it is rejected by the current consumer. In RabbitMQ I could send a NACK to accomplish this. Is NACK supported in EasyNetQ? Is there another way to achieve the behavior I'm looking for?

Update: not a lot of responses, so I'm wondering how people are generally handling the lack of NACK in EasyNetQ. Not having the equivalent of basic.reject limits consumers to "I can always process every message" scenarios. I suppose consumers could throw a specific "rejected" exception to cause EasyNetQ to dequeue the message to the error queue, and I could requeue messages with those errors. Anyone else have other workarounds in place?

BitMask777
  • 2,543
  • 26
  • 36

3 Answers3

4

I used EasyNetQ for almost a year, but no matter how we tweaked it (amongst other things added our own implementation of IConsumerErrorStrategy) I never really got it to work the way I wanted. The fact that it is single threaded gave us some unexpected behaviour (sometimes deadlocks) when performing RequestAsync while in a SubscribeAsync handler.

The solution for us was to move from EasyNetQ. After working with the official RabbitMq Client for a while, I spent a few days writing a super thin client on top of that. It is influenced by EasyNetQ and supports most of the concepts that EasyNetQ has. However, I added some neat features like pluggable message contexts. I think that the Nack feature of IAdvancedMessageContext that I just added can be something for you:

var client = service.GetService<IBusClient<AdvancedMessageContext>>();
client.RespondAsync<BasicRequest, BasicResponse>((req, ctx) =>
{
  ctx?.Nack(); // the context implements IAdvancedMessageContext.
  return Task.FromResult<BasicResponse>(null);
}, cfg => cfg.WithNoAck(false));

If you're interested you can read more about it at the Github page (especially the NackTests.cs).

pardahlman
  • 1,394
  • 10
  • 22
2

I think you can change the behavior by implementing your own IConsumerErrorStrategy:

https://github.com/EasyNetQ/EasyNetQ/blob/master/Source/EasyNetQ/Consumer/DefaultConsumerErrorStrategy.cs

But if you need that kind of control you might consider just using the RabbitMQ client directly?

Wiebe Tijsma
  • 10,173
  • 5
  • 52
  • 68
  • Thanks for the response. It looks like the handler in the error strategy is invoked when a consumer throws, so I'd have to throw an exception that I can recognize as being a "rejection" by the consumer and then nack. If there isn't a better way then I might just handle it myself by re-queuing the original message. Not really a nack, but it could be enough for my purposes. – BitMask777 Oct 19 '15 at 16:39
  • Update: I tried just requeueing the message but it has a side effect. The send occurs on the same thread as the handler for the original message, which is still unacked. This means that a connection failure after the send will requeue the original message, resulting in two duplicate messages on the queue. An edge case, but possible, and I don't like it. So I may got with the exception approach (see my update in the question). – BitMask777 Oct 20 '15 at 18:08
  • 1
    Yes, and this is inevitable, because using this approach you will implement the smantics "at least once" – sergeyxzc Oct 10 '20 at 22:57
  • @BitMask777 ideally you want to model your messages to be idempotent, there are many situations in which messages can be delivered multiple times (retries, connection errors etc). – Wiebe Tijsma Oct 13 '20 at 14:05
1

It sounds like you are trying to handle failures. You can NACK a message, but that means it sits at the head of the queue. Great, but then it means that you could end up with a bunch of messages that are truthfully unable to be processed, and you will be unable to actually process real messages.

The solution that I have always used when using RabbitMQ is to utilize the default error handling of EasyNetQ, and have a separate application to resend messages. That is, when an exception is captured in RabbitMQ, it routes the message to a queue called "EasyNetQ_Default_Error_Queue". You are able to override this name and have different queues go to different error queues, but for now let's stick with the default. You can then have a Windows Service/Azure Worker role reading these messages, and working out what to do. That may include having a "RetryCount" on your message envelope/wrapper to make sure that it only loops around so many times. All in all, it's going to be a bit of work.

What you are finding, is what many people run into when using RabbitMQ/EasyNetQ. She's pretty raw.

MindingData
  • 11,924
  • 6
  • 49
  • 68