As a part of the NServiceBus "Fault-Tolerant by Default" design
transactions are managed automatically by the infrastructure
so that developers don't have to remember the configuration
of all threading and state management elements.
Clients and Servers
While it is desirable for server code to process messages transactionally, it often isn't required for clients, particularly desktop applications.
This is one of the differences between the AsA_Client and AsA_Server settings of the generic host in NServiceBus.
Specifying transactions in code
If you aren't using the generic host, you can specify whether the current endpoint should process messages transactionally or not by setting the ".IsTransactional(true)" after ".MsmqTransport()"
If you want to override the System.Transactions default timeout of 10 minutes, follow the steps described in this blog post.
Distributed Transaction Coordinator
On Windows, there is an OS-level service called the DTC which manages transactions that need to span multiple resources - like queues and databases. This service isn't always configured correctly and may require some trouble-shooting. First of all, you'll need to download a tool called DTCPing. This tool will help you find out if one machine is able to access a remote machine over the DTC. Here's what that tool looks like:
If you get an error referring to the RPC Endpoint Mapper, go to the command prompt and run "dcomcnfg". After you start dcomcnfg you should see the Component Services screen below.
From here, what needs to be done is open some ports. To do this, right-click "My Computer" and go to the "Default Protocols" tab. From there, select "Connection-oriented TCP/IP" and click the "Properties" button. In the "Properties for COM Internet Services" dialog that opens, check that the Port Ranges includes "5000-6000" as shown in the following image:
If the list of Port Ranges is empty, click the "Add..." button and enter "5000-6000" in the dialog box that opens up. After doing so, your screen should look like the image above. You can probably make do with less than 1000 open ports, but it depends on the number of machines you are looking to connect to each other over the DTC.
After clicking OK and returning to the Component Services screen, navigate to the "Local DTC" node under the Distributed Transaction Coordinator folder, right click, and select "Properties". In the dialog that opens, select the Security tab as shown below:
Ensure that the properties you see are the same as the above.
After finishing all the steps above, restart the computer.
If DTCPing isn't working after all of the above, check that the needed ports are open in the firewall. Consider removing the DTC exceptions in the firewall and add them again to make sure.
If DTCPing gives you a message about finding the name but not reaching it, the first thing to do is a simple ping by running “ping computer_name” in the command prompt. If you discover that the machine cannot be reached by ping, it could be that you have a DNS problem that may require your Network Administrator's help.
Make sure you perform all of the above steps not just on the servers that connect to the database, but also on the database servers as well.
Finally, check the TCP ports in use on the servers making sure that each has a different port configured as the communication is bi-directional. At this point, you should be able to run transactional NServiceBus endpoints.
Content in this section derived from Mikael Henriksson's blog.
The Message Processing Loop
Messages are processed in NServiceBus as follows:
The queue is peeked to see if there's a message.
If so, a distributed transaction is started.
The queue is contacted again to receive a message. This is because multiple threads may have peeked the same message. The queue makes sure only one thread will actually get a given message.
If the thread is able to get it, NServiceBus tries to deserialize the message. If this fails, the message is moved to the configured error queue and the transaction commits.
After a successful deserialization, NServiceBus invokes all infrastructure and applicative message modules and handlers. An exception in this step will cause the transaction to rollback and the message to return to the input queue.
This will happen the "MaxRetries" configurable number of times
After that, the message will be passed to the Second Level Retries (SLR)
If after SLR the error is still occurring, the message will be moved to the configured error queue
In this manner, even under all kinds of failure conditions like the application server restarting in the middle of a message, or a database deadlock, messages are not lost.
In the common case, the automatic retry mechanism is able to recover from most temporary problems. When that isn't possible, the message is passed to the SLR to decide what to do next.
Resolving More Permanent Errors
There are multiple situations in which more permanent errors affect systems. Despite their diversity, the NServiceBus solution is the same. Before describing it, let's drill into some of these situations.
The database is down
An external or internal web service is down
The system was upgraded accidentally breaking backwards-compatibility
In all of the above, administrative action is needed - from things as simple as bringing up a database or web service again, to more complex actions like reverting to the previous version of the system.
SLRs also aids in the resolution of more permanent errors, to see how read this page.
What we see here is that there's nothing necessarily wrong with the message itself. It might contain valuable information that shouldn't get lost under these conditions. Therefore, after the administrator finishes resolving the issue, they should return the message to the queue it came from. Luckily, NServiceBus comes with a tool that does exactly that.
You can find this tool in the "Tools" directory found in your NServiceBus download.
Administrators provide the name of the error queue they'd like to use, and either specify a specific message ID to return to its source queue, or 'all' to return all messages in the given error queue, each to its respective source queue.