In NServiceBus, how can I handle when a message comes in without a matching saga? - nservicebus

If I have a saga that consists of two message types, say started by message1 and completed by message2, can I return a callback if a message2 comes in without a message1 already existing? I know it will dump it in the error queue, but I want to be able to return a status to the sending client to say there is an error state due to the first message not being there.

So I figured it out, I just needed to implement IFindSagas for the message type:
public class MySagaFinder : IFindSagas<MySagaData>.Using<Message2>
{
public ISagaPersister Persister { get; set; }
public IBus Bus { get; set; }
public MySagaFinder FindBy(Message2 message)
{
var data = Persister.Get<MySagaData>("MessageIdProperty", message.MessageIdProperty);
if (data == null)
{
Bus.Return(0);
}
return data;
}
}
I don't know if this is the right way to do it, but it works!

If you have a saga that can receive two messages, but messages can be received in any order, make sure the saga can be started by both messages. Then verify if both message have arrived by setting some state in the saga itself. If both messages have arrived, mark it as complete.
Default NServicebBus behavior is to ignore any message that has no corresponding saga. This is because you can set a timeout, for example. If nothing happens within 24 hours, the saga can send a Timeout message to itself. But if something did happen and you marked your saga as being completed, what should happen to the Timeout message? Therefor NServiceBus ignores it.

Related

Requeue the Spring amqp message with updated properties

I have a use case where i have to re-queue the message with updated properties , Messages are getting re queued but message properties are not getting updated
public class TestListener implements MessageListener{
#Override
public void onMessage(Message arg0) {
MessageProperties properties = arg0.getMessageProperties();
int count = properties.getMessageCount();
System.out.println(count);
properties.setMessageCount(++count);
throw new AmqpException("test");
}
But the value of count always prints its always as 0
You can't do that - the amqp protocol does not support sending data back when rejecting a message.
You have to republish the message yourself, e.g with a RabbitTemplate.send() call.
You should also not use a "system" property for your own purposes; use messageGetProperties().set("myHeader", count++).

Get message type from PipeContext from MassTransit

I am implementing a MassTransit middleware in my receive end point to record the performance of handling the message, i want to get the message type from the PipeContext, how can i get it?
public async Task Send(T context, IPipe<T> next)
{
// I want to know the message type from here so that i can log it
using (_logger.BeginTimedOperation("Time for handling message", null, LogEventLevel.Debug))
{
await next.Send(context);
}
}
You would need to intercept at the ConsumeContext, which has a property for the message types from the serialization envelope.
Then, you can get the supported message types using:
IEnumerable<string> SupportedMessageTypes {get;}
That should get you what you need to log the message type with the duration.
So a filter along the lines of:
public class LogMessageTypeFilter :
IFilter<ConsumeContext>
{
}
Implement the send method, call next within the method, and then take action after the next pipe completes.

RabbitMQ - Non Blocking Consumer with Manual Acknowledgement

I'm just starting to learn RabbitMQ so forgive me if my question is very basic.
My problem is actually the same with the one posted here:
RabbitMQ - Does one consumer block the other consumers of the same queue?
However, upon investigation, i found out that manual acknowledgement prevents other consumers from getting a message from the queue - blocking state. I would like to know how can I prevent it. Below is my code snippet.
...
var message = receiver.ReadMessage();
Console.WriteLine("Received: {0}", message);
// simulate processing
System.Threading.Thread.Sleep(8000);
receiver.Acknowledge();
public string ReadMessage()
{
bool autoAck = false;
Consumer = new QueueingBasicConsumer(Model);
Model.BasicConsume(QueueName, autoAck, Consumer);
_ea = (BasicDeliverEventArgs)Consumer.Queue.Dequeue();
return Encoding.ASCII.GetString(_ea.Body);
}
public void Acknowledge()
{
Model.BasicAck(_ea.DeliveryTag, false);
}
I modify how I get messages from the queue and it seems blocking issue was fixed. Below is my code.
public string ReadOneAtTime()
{
Consumer = new QueueingBasicConsumer(Model);
var result = Model.BasicGet(QueueName, false);
if (result == null) return null;
DeliveryTag = result.DeliveryTag;
return Encoding.ASCII.GetString(result.Body);
}
public void Reject()
{
Model.BasicReject(DeliveryTag, true);
}
public void Acknowledge()
{
Model.BasicAck(DeliveryTag, false);
}
Going back to my original question, I added the QOS and noticed that other consumers can now get messages. However some are left unacknowledged and my program seems to hangup. Code changes are below:
public string ReadMessage()
{
Model.BasicQos(0, 1, false); // control prefetch
bool autoAck = false;
Consumer = new QueueingBasicConsumer(Model);
Model.BasicConsume(QueueName, autoAck, Consumer);
_ea = Consumer.Queue.Dequeue();
return Encoding.ASCII.GetString(_ea.Body);
}
public void AckConsume()
{
Model.BasicAck(_ea.DeliveryTag, false);
}
In Program.cs
private static void Consume(Receiver receiver)
{
int counter = 0;
while (true)
{
var message = receiver.ReadMessage();
if (message == null)
{
Console.WriteLine("NO message received.");
break;
}
else
{
counter++;
Console.WriteLine("Received: {0}", message);
receiver.AckConsume();
}
}
Console.WriteLine("Total message received {0}", counter);
}
I appreciate any comments and suggestions. Thanks!
Well, the rabbit provides infrastructure where one consumer can't lock/block other message consumer working with the same queue.
The behavior you faced with can be a result of couple of following issues:
The fact that you are not using auto ack mode on the channel leads you to situation where one consumer took the message and still didn't send approval (basic ack), meaning that the computation is still in progress and there is a chance that the consumer will fail to process this message and it should be kept in rabbit queue to prevent message loss (the total amount of messages will not change in management consule). During this period (from getting message to client code and till sending explicit acknowledge) the message is marked as being used by specific client and is not available to other consumers. However this doesn't prevent other consumers from taking other messages from the queue, if there are more mossages to take.
IMPORTANT: to prevent message loss with manual acknowledge make sure
to close the channel or sending nack in case of processing fault, to
prevent situation where your application took the message from queue,
failed to process it, removed from queue, and lost the message.
Another reason why other consumers can't work with the same queue is QOS - parameter of the channel where you declare how many messages should be pushed to client cache to improve dequeue operation latency (working with local cache). Your code example lackst this part of code, so I am just guessing. In case like this the QOS can be so big that there are all messages on server marked as belonging to one client and no other client can take any of those, exactly like with manual ack I've already described.
Hope this helps.

What happens to messages sent/published in a Handler that fails with an exception?

I've read that each message handler is wrapped in an "ambient transaction", and that database access is automatically enlisted in that transaction when possible. Does NServiceBus do anything else with that transaction? Specifically, I'm wondering if it can somehow cancel any messages that a handler sends/publishes in the case of an exception.
In the code below, does the bus Send the ArchiveMessage as soon as the Send method is called, or does it queue it up and only send it if the handler executes successfully?
public class BadHandler
{
public IBus Bus { get; set; }
public void Handle(MyMessage msg)
{
Bus.Send(new ArchiveMessage(msg.MessageId)); //does this message send?
throw new Exception("Something terrible happened, maybe my database connection failed!");
}
}
I this case the message would not be sent. MyMessage will be retried the configured number of times and them moved to the designated error queue. You can have greater control over that process if you wish, you would need to create a custom FaultManager.

Is there an easy way to subscribe to the default error queue in EasyNetQ?

In my test application I can see messages that were processed with an exception being automatically inserted into the default EasyNetQ_Default_Error_Queue, which is great. I can then successfully dump or requeue these messages using the Hosepipe, which also works fine, but requires dropping down to the command line and calling against both Hosepipe and the RabbitMQ API to purge the queue of retried messages.
So I'm thinking the easiest approach for my application is to simply subscribe to the error queue, so I can re-process them using the same infrastructure. But in EastNetQ, the error queue seems to be special. We need to subscribe using a proper type and routing ID, so I'm not sure what these values should be for the error queue:
bus.Subscribe<WhatShouldThisBe>("and-this", ReprocessErrorMessage);
Can I use the simple API to subscribe to the error queue, or do I need to dig into the advanced API?
If the type of my original message was TestMessage, then I'd like to be able to do something like this:
bus.Subscribe<ErrorMessage<TestMessage>>("???", ReprocessErrorMessage);
where ErrorMessage is a class provided by EasyNetQ to wrap all errors. Is this possible?
You can't use the simple API to subscribe to the error queue because it doesn't follow EasyNetQ queue type naming conventions - maybe that's something that should be fixed ;)
But the Advanced API works fine. You won't get the original message back, but it's easy to get the JSON representation which you could de-serialize yourself quite easily (using Newtonsoft.JSON). Here's an example of what your subscription code should look like:
[Test]
[Explicit("Requires a RabbitMQ server on localhost")]
public void Should_be_able_to_subscribe_to_error_messages()
{
var errorQueueName = new Conventions().ErrorQueueNamingConvention();
var queue = Queue.DeclareDurable(errorQueueName);
var autoResetEvent = new AutoResetEvent(false);
bus.Advanced.Subscribe<SystemMessages.Error>(queue, (message, info) =>
{
var error = message.Body;
Console.Out.WriteLine("error.DateTime = {0}", error.DateTime);
Console.Out.WriteLine("error.Exception = {0}", error.Exception);
Console.Out.WriteLine("error.Message = {0}", error.Message);
Console.Out.WriteLine("error.RoutingKey = {0}", error.RoutingKey);
autoResetEvent.Set();
return Task.Factory.StartNew(() => { });
});
autoResetEvent.WaitOne(1000);
}
I had to fix a small bug in the error message writing code in EasyNetQ before this worked, so please get a version >= 0.9.2.73 before trying it out. You can see the code example here
Code that works:
(I took a guess)
The screwyness with the 'foo' is because if I just pass that function HandleErrorMessage2 into the Consume call, it can't figure out that it returns a void and not a Task, so can't figure out which overload to use. (VS 2012)
Assigning to a var makes it happy.
You will want to catch the return value of the call to be able to unsubscribe by disposing the object.
Also note that Someone used a System Object name (Queue) instead of making it a EasyNetQueue or something, so you have to add the using clarification for the compiler, or fully specify it.
using Queue = EasyNetQ.Topology.Queue;
private const string QueueName = "EasyNetQ_Default_Error_Queue";
public static void Should_be_able_to_subscribe_to_error_messages(IBus bus)
{
Action <IMessage<Error>, MessageReceivedInfo> foo = HandleErrorMessage2;
IQueue queue = new Queue(QueueName,false);
bus.Advanced.Consume<Error>(queue, foo);
}
private static void HandleErrorMessage2(IMessage<Error> msg, MessageReceivedInfo info)
{
}