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

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.

Related

Repository save not rollbacked if send message fails

Given the following code :
#RabbitListener
public void process(Message myMessage) {
Event event = ..get event from myMessage
handleMessage(event);
}
#Transactional
public void handleMessage(Event event) {
ObjectToSend objecToSend = ...get objectToSend from event
rabbitTemplate.convertAndSend(exchange1, routingKey1, objectToSend); // line 1 : supposte that at this point rabbit is still connected
persistService.save(new MyEntity()); // line 2
doSomethingElse(); // line 3 : suppose that at this point rabbit is disconnected (network failure)
}
I notice that if persistService.save fails then :
objecToSend is not sent (and this is fine)
the original myMessage in the RabbitListener is sent to DLQ (and this is fine)
but if persistService.save succeed and convertAndSend fails (because of a rabbit server connection failure after persistService.save ), original myMessage go back in DLQ (this is ok) but the problem is that MyEntity is not rollbacked.
What am I doing wrong ?
persistService.save(myEntity) should be executed ONLY IF rabbitTemplate.convertAndSend is REALLY sent.
The only solution I found is to use "publish confirms" and BLOCK after convertAndSend(message, correlationData) and then using a correlationData.getFuture() with future.get (possibly with a Timeout) and only after "positive" confirm received confirm I can proceed invoking method persistService.save()
Is it the right solution ? (I suspect this could be "slow)
Consider even that if the publish of objectToSend fail I must reject myMessage to the DLQ.
Thank you
You can also set channelTransacted on the template but the performance will be similar to waiting for the confirmation because it requires a round trip to the broker.
That will use a local transaction which will commit when the send completes.
Or, add your transaction manager to the listener container and it will synchronize the rabbit transaction with the DB transaction.

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++).

WCF Async call that return void and is OneWay

I use WCF to async communicate between 2 processes.
till now I implemented the IAsyncResult pattern, and did it by having 3 methods:
BeginOperation - client.BeginOperation, when service receive it queue the job on threadpool with delegate for Operation
Operation - run on service side
EndOperation - this is what client callback.
My question is, I want to send strings from client to service, I want the send to be async, AND I dont want to get response - just want the service to print the string.
Is this enough ? This must be Non-Blocking
[OperationContract(IsOneWay = true)]
void PrintString(string message);
OR i need to do as following:
[OperationContract(IsOneWay = true, AsyncPattern=true)]
void BeginPrintString(string message, AsyncCallback callback, object state);
void EndPrintString(IAsyncResult asyncResult);
IsOneWay should be enough, but it still can block your client in specific circumstances, as well as throw errors.
See this and this posts for more details:
General things you should keep in mind about OneWay operations -
O/W operations must return void. O/W operations can still yield
exceptions. invoking an operation on the client channel might still
throw an exception if it couldn’t transmit the call over to the
service. O/W operations can still block. if the service is pumped with
messages and a queue had started, calling O/W operation may block your
following code.
You can make it more asynchronous by using a separate thread to do your work.
Instead of:
public void MyOneWayServiceOperation() {
// do stuff
}
do something like:
public void MyOneWayServiceOperation() {
new Thread(() => {
// do stuff
}).Start();
}
The service operation will have been executed on a new thread anyway, so it shouldn't cause any more problems for your code to be on a different new thread.
You can add a Thread.Sleep() call before your work if you want the service operation to have completed before you begin. This allowed me to quit the service from a service operation without having an exception thrown.

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

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.

WCF Callback channel faulted

I'm trying to implement a reconnect logic for a wcf client. I'm aware that you have to create a new channel after the current channel entered the faulted state. I did this in a channel faulted event handler:
internal class ServiceClient : DuplexClientBase, IServiceClient
{
public ServiceClient(ICallback callback, EndpointAddress serviceAddress)
: base(callback, MyUtility.GetServiceBinding("NetTcpBinding"), serviceAddress)
{
// Open the connection.
Open();
}
public void Register(string clientName)
{
// register to service
}
public void DoSomething()
{
// some code
}
}
public class ClientApp
{
private IServiceClient mServiceClient;
private ICallback mCallback;
public ClientApp()
{
mServiceClient = new ServiceClient( mCallback, new EndpointAddress("someAddress"));
mServiceClient.Register();
// register faulted event for the service client
((ICommunicationObject)mServiceClient).Faulted += new EventHandler(ServiceClient_Faulted);
}
void ServiceClient_Faulted(object sender, EventArgs e)
{
// Create new Service Client.
mServiceClient = new ServiceClient( mCallback, new EndpointAddress("someAddress"));
// Register the EI at Cell Controller
mServiceClient.Register();
}
public void DoSomething()
{
mServiceClient.DoSomething();
}
}
But in my unit test I still get a "The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state" exception.
Is it possible that the callback channel is still faulted and if yes how can I replace the callback channel?
so far I have experienced that a WCF connection needs to be recreated on fault - there doesn't seem to be a way to recover it otherwise. As for when a fault occurs, the method seems to fire fine, but often it fires and cleans up the WCF connection (establishing a new one, etc) as the current request is going through - causing this to fail - especially true on timeouts.
A couple of suggestions:
- If it is timeout related, keep track of the last time a call was made and a constant containing the timeout value. If the WCF connection will have been dropped due to inactivity, drop it and recreate it before you send the request over the wire.
- The other thing, it looks like you are not re-adding the fault handler, which means the first fault will get handled, but the second time it faults it will fall over without a handler cause no new one has been attached.
Hope this helps
Have you tried to reset the communications channel by calling mServiceClient.Abort in the Faulted event handler?
Edit:
I see that you do not reinitialize the mCallback object in your recovery code. You may need to assign it to a new instance.