Reputation: 2538
I use NHibernate in a multi-threaded scenario. It's a test that performs the following actions.
Foo
object with state A
and stores it in the DB using NHibernate.B
for the newly created Foo
object.Foo
from the DB using NHibernate, reads its state (should be B
) and acts accordingly.Now my problem is that sporadically (in 1 - 2% of the cases) in step 4 the server reads state A
when it should read B
and the test fails. What am I doing wrong here?
NHibernate sessions are retrieved via CurrentSessionContext
which is set to thread static. I use Fluent NHibernate, but I guess this doesn't matter.
Details about the server:
The server is a simple TCP server. When started it creates a new thread task to wait for incoming connections. When a new connection is accepted it creates another thread task to process the request from the connection and send a response. The the processing thread task terminates.
The server uses Task.Factory.StartNew()
to create new threads tasks.
I wrote a small NUnit test to reproduce this behavior. It fails with
Expected: Open But was: Initialized
on the first iteration.
[Test]
[Explicit]
public void NHibernateProblem()
{
for (var i = 0; i < 100; i++)
{
var identifier = Guid.NewGuid().ToString();
var session1 = Database.GetCurrentSession();
using (var dbTransaction = session1.BeginTransaction())
{
var transaction1 = new Transaction
{
Identifier = identifier,
Status = TransactionStatus.Initialized
};
session1.SaveOrUpdate(transaction1);
dbTransaction.Commit();
}
Task.Factory.StartNew(() =>
{
var session2 = Database.GetCurrentSession();
using (var dbTransaction = session2.BeginTransaction())
{
var transaction2 = session2
.QueryOver<Transaction>()
.Where(t => t.Identifier == identifier)
.SingleOrDefault();
transaction2.Status = TransactionStatus.Open;
session2.SaveOrUpdate(transaction2);
dbTransaction.Commit();
}
}).Wait();
var session3 = Database.GetCurrentSession();
using (var dbTransaction = session3.BeginTransaction())
{
var transaction3 = session3
.QueryOver<Transaction>()
.Where(t => t.Identifier == identifier)
.SingleOrDefault();
dbTransaction.Commit();
Console.WriteLine("iteration {0}", i);
Assert.That(transaction3.Status, Is.EqualTo(TransactionStatus.Open));
}
}
}
Database.GetCurrentSession()
looks like this:
public virtual ISession GetCurrentSession()
{
if (CurrentSessionContext.HasBind(SessionFactory))
{
var currentSession = SessionFactory.GetCurrentSession();
return currentSession;
}
var session = SessionFactory.OpenSession();
session.FlushMode = FlushMode.Always;
session.CacheMode = CacheMode.Ignore;
CurrentSessionContext.Bind(session);
return session;
}
I believe the mappings are less relevant.
Upvotes: 0
Views: 877
Reputation: 6054
In a server general practice is to create a session per request, and to start the transaction at the beginning of the request. When the request is about to complete transaction is either committed or rolled back depending on the result.
In your test it does not actually simulate this scenario, and it uses the same session for request 1 and request 3 where it will never be the case in actual scenario.
So, to test the actual scenario, you can create a new session for session 3.
Upvotes: 1
Reputation: 5629
In the unit test shown, session3 will be the same as session1, and therefore transaction3 will be the same object instance as transaction1, and it will therefore still show the values as they were when the transaction-object was added.
The query in the third part will hit the database, but NHibernate will see that the fetched object is already loaded and return the instance that the session already holds in memory. It will normally not overwrite the values in the object.
Upvotes: 1