I got a wierd problem with NServiceBus retying the message X number of times although no exception is thrown in the handler. There is some information out there dealing with the NHibernate session and the NSB ambiant transaction. Since no error is thrown I'm not 100% sure of the problem and thus can't really decide on what to do.
I got NSB configured with Castle Windsor like so:
IWindsorContainer container = new WindsorContainer(new XmlInterpreter());
container.Install(new ContainerInstaller());
container.Install(new UnitOfWorkInstaller(AppDomain.CurrentDomain.BaseDirectory, Castle.Core.LifestyleType.Scoped));
container.Install(new FactoryInstaller(AppDomain.CurrentDomain.BaseDirectory));
container.Install(new RepositoryInstaller(AppDomain.CurrentDomain.BaseDirectory));
Configure.With()
.CastleWindsorBuilder(container)
.FileShareDataBus(Properties.Settings.Default.DataBusFileSharePath)
.MsmqTransport()
.IsTransactional(true)
.PurgeOnStartup(false)
.UnicastBus()
.LoadMessageHandlers()
.ImpersonateSender(false)
.JsonSerializer();
The UnitOfWorkInstaller
registers the unit of work (the NHibernate session) like so:
public void Install(IWindsorContainer container, IConfigurationStore store)
{
var fromAssemblyDescriptor = AllTypes.FromAssemblyInDirectory(new AssemblyFilter(_installationPath));
container.Register(fromAssemblyDescriptor
.IncludeNonPublicTypes()
.Pick()
.If(t => t.GetInterfaces().Any(i => i == typeof(IUnitOfWork)) && t.Namespace.StartsWith("Magma"))
.WithService.AllInterfaces()
.Configure(con => con.LifeStyle.Is(_lifeStyleType).UsingFactoryMethod(k => k.Resolve<IUnitOfWorkFactory>().Create())));
}
So each time a message arrives the same unit of work is used for all the repositories. I read that manually rolling back the current transaction results in an error (I don't really know why) and I also know that NSB creates a child container for every transport message and that this child container is disposed after the processing of the message. The problem is that when the child container is disposed the unit of work is disposed this way:
public void Dispose()
{
if (!_isDisposed)
{
DiscardSession();
_isDisposed = true;
}
}
private void DiscardSession()
{
if (_transaction != null && _transaction.IsActive)
{
_transaction.Dispose();
}
if (Session != null)
{
Session.Dispose();
}
}
My handlers are structured like this: (the _unitOfWork is passed as a constructor dependency)
public void Handle(<MessageType> message)
{
using (_unitOfWork)
{
try
{
// do stuff
_unitOfWork.Commit();
}
catch (Exception ex)
{
_unitOfWork.Rollback();
// rethrow so the message stays in the queue
throw;
}
}
}
I found out that if I don't commit the unit of work (which flushes the session and commit the transaction) I got an error saying that the message has retried beyond the max retry count bla bla bla...
So it seems to be linked with the NHibernate session and the way it's created and disposed of but since it's created within the unit of work I can't really use the session factory. I read that I could use the IMessageModule to create and dispose the session but again I don't know if this is the right way to go since I don't understand what's causing the error in the first place.
So to recap:
I'm using a scoped unit of work so that all the handler dependencies using it will share the same instance (thx to the child container, BTW: I've setup the unit of work as transient thinking that the child container will treat all transient object as singleton within that container but I saw that the unit of work wasn't shared so this is why it's setup as scoped)
I'm wrapping my handler in a
using(_unitOfWork) { }
statement to dispose the unit of work after each processing.When the unit of work get's disposed, the NHibernate session is also disposed
If I don't explicitly call
Commit
on the_unitOfWork
, the message retries beyond the max retry count and then an error is thrown.
What is causing this behavior? and is the IMessageModule is the answer for this?
<CorrId />
. It seems to be related to NHibernate but I don't have any NSB integration with NHibernate. – Francois Joly