[nhusers] how should optimistic concurrency using a version field work?

21 views
Skip to first unread message

Rémi Després-Smyth

unread,
Nov 20, 2009, 2:17:27 PM11/20/09
to nhu...@googlegroups.com

Can anyone explain optimistic locking in the context of NHibernate?  (Using NHib 2.1.1.)

 

I’ve been running tests and my results are counter-intuitive.  I have a versioned entity:

 

 

<class name="Test.Entity, Test" table="tblEntity" abstract="false" optimistic-lock="version">

 

      <id name="Id" column="scheduleId" access="property" unsaved-value="0" type="Int64">

            <generator class="hilo">

                  <param name="table">tblHiloUId</param>

                  <param name="column">nextHighValue</param>

                  <param name="max_lo">100</param>

            </generator>

      </id>

 

      <version column="version" name="Version" type="Int32" unsaved-value="0" />

 

      <property name="Prop1" column="prop1" update="false"

            access="property" not-null="false" type="Boolean"

            optimistic-lock="true" />

 

      <property name="Prop2" column="isDefaultOverridable"

            access="field" not-null="true" type="String"

optimistic-lock="true" />

</class>

 

And the following test:

 

[Test, ExpectedException(ExceptionType=typeof(StaleObjectStateException))]

public void SavingUpdatesOptimisticLockShouldThrow()

{

      var cfg = new NHibernate.Cfg.Configuration();

      cfg.AddAssembly("Test");

      cfg.Configure();

      var sessionFactory = cfg.BuildSessionFactory();

 

      var sess1 = sessionFactory.OpenSession();

      var sess2 = sessionFactory.OpenSession();

 

      sess1.BeginTransaction();

      sess2.BeginTransaction();

 

      // NOTE: I get the same results if I load with Lock.None

// A record is loaded in the DB in test setup, assigned to m_Id

      var a = sess1.Load<Entity>(m_Id);

      var b = sess2.Load<Entity>(m_Id);

 

      a.Prop2 = "New test value, session1”;

      sess1.Save(a);

      sess1.Transaction.Commit();

 

      b = "Another, session2";

      sess2.Save(b);

      sess2.Transaction.Commit();   // Should throw?

}

 

After reading the docs, this is what I’d expect to see:

 

Both instances start with version=1.  When I save and commit a, I see that its version number is incremented from 1 to 2, while b still has version=1 (as I’d expect).  I’d expect that the call to sess2.Transaction.Commit() should throw, because NHibernate will determine that the record was updated since b was loaded, so optimistic concurrency issue.  But it doesn’t – b commits fine, and overwrites changes saved when a was saved.

 

If I load explicitly selecting the lock I want, it does work as I’d expect and I get my exception.

 

This is surprising to me.  Ayende noted in a concurrency blog post (http://ayende.com/Blog/archive/2009/04/15/nhibernate-mapping-concurrency.aspx) that using a version column should result in the generated UPDATE SQL statement to compare against the version number – and if the version doesn’t match the original version, we should get a StaleObjectException.

 

Can anyone clarify what’s going on here?

 

Thanks.

Remi.

 

Rémi Després-Smyth

unread,
Nov 20, 2009, 2:20:02 PM11/20/09
to nhu...@googlegroups.com

Sorry, just noticed a typo in the code from the email.  Corrected it and re-sent.

 

Remi.

      b.Prop2 = "Another, session2";

      sess2.Save(b);

      sess2.Transaction.Commit();   // Should throw?

}

 

After reading the docs, this is what I’d expect to see:

 

Both instances start with version=1.  When I save and commit a, I see that its version number is incremented from 1 to 2, while b still has version=1 (as I’d expect).  I’d expect that the call to sess2.Transaction.Commit() should throw, because NHibernate will determine that the record was updated since b was loaded, so optimistic concurrency issue.  But it doesn’t – b commits fine, and overwrites changes saved when a was saved.

 

If I load explicitly selecting the lock I want, it does work as I’d expect and I get my exception.

 

This is surprising to me.  Ayende noted in a concurrency blog post (http://ayende.com/Blog/archive/2009/04/15/nhibernate-mapping-concurrency.aspx) that using a version column should result in the generated UPDATE SQL statement to compare against the version number – and if the version doesn’t match the original version, we should get a StaleObjectException.

 

Can anyone clarify what’s going on here?

 

Thanks.

Remi.

 

--

You received this message because you are subscribed to the Google Groups "nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nhusers?hl=.

Oskar Berggren

unread,
Nov 20, 2009, 6:31:56 PM11/20/09
to nhu...@googlegroups.com
This is somewhat of a guess, but I suspect you will see the expected
behavior if you replace Load with Get. Or don't commit sess1 until
after you've modified b.

Get fetches the object immediately, while Load returns a proxy, not
loading the object until you first access one of it's properties. This
should cause b to actually show the value committed in sess1, the way
your code looks now.

/Oskar


2009/11/20 Rémi Després-Smyth <re...@terracognita.ca>:

Fabio Maulo

unread,
Nov 20, 2009, 9:29:01 PM11/20/09
to nhu...@googlegroups.com
Oskar, that code can't be compiled (try to compile it by eyes).

2009/11/20 Oskar Berggren <oskar.b...@gmail.com>



--
Fabio Maulo

Oskar Berggren

unread,
Nov 21, 2009, 5:51:20 AM11/21/09
to nhu...@googlegroups.com
Fabio, are you referring to the fact he assigns a string to b, instead
of to b.Prop2? I noticed, but ignored that, and it was corrected in
another mail. Or is there something else I'm missing?

/Oskar


2009/11/21 Fabio Maulo <fabio...@gmail.com>:

Fabio Maulo

unread,
Nov 21, 2009, 9:48:19 AM11/21/09
to nhu...@googlegroups.com
is that ;)

btw what I mean, is that the tests is not well formed in many sense.
If our friend Remi want test NH behaviour he should try to recreate a behaviour-test using a more real scenario trying to reproduce how "things" happens in his application.
For example... How Remí can recreate that sequence of actions in a real app ?
Even if he can, how much is correct to have two opened transactions in the same thread ?
... and so on... 

2009/11/21 Oskar Berggren <oskar.b...@gmail.com>



--
Fabio Maulo

Socratees

unread,
Nov 22, 2009, 10:03:30 PM11/22/09
to nhusers
I don't think Session.Load<Entity>(Id), by default doesn't use any
locks. It loads objects in an unlocked mode.

Can you try Session.Load<Entity>(Id, LockMode) with Lockmode
explicitly specified?

Thanks,
Socratees.

On Nov 20, 1:20 pm, Rémi Després-Smyth <r...@terracognita.ca> wrote:
> Sorry, just noticed a typo in the code from the email.  Corrected it and
> re-sent.
>
> Remi.
>
> (http://ayende.com/Blog/archive/2009/04/15/nhibernate-mapping-concurre...

Socratees Samipillai

unread,
Nov 20, 2009, 3:16:25 PM11/20/09
to nhu...@googlegroups.com
Hi Remi,

I'm not an expert, but your updates might be taking place successfully because of the way you're loading the object.

Session.Load<Entity>(Id) by default doesn't use any locks. It loads objects in an unlocked mode.


Can you try Session.Load<Entity>(Id, LockMode) with Lockmode explicitly specified?

Hope that solves your problem.

Thanks,
Socratees.



2009/11/20 Rémi Després-Smyth <re...@terracognita.ca>

Rémi Després-Smyth

unread,
Nov 23, 2009, 9:21:08 AM11/23/09
to nhu...@googlegroups.com

Fabio,

There wouldn’t be two transactions on the same thread, but I’m working on a web app, so different worker threads would be running using different sessions.  I did not think setting up a test that uses two different threads to be necessary to test the scenario – instead, I just used two different sessions, which is close enough to how it would work in production anyhow.

 

I don’t think this is an invalid question to be asking.  The app’s on a web server, and concurrent requests are processed by different worker threads, aren’t they?  Setting up a test that uses two distinct threads would be unnecessary and unreliable, since it would be difficult to ensure correct timing between the two threads for the behaviour I want to see.  As a result, I believe the test I setup is actually quite appropriate for what I want to check.

 

No?

 

Remi.

Fabio Maulo

unread,
Nov 23, 2009, 9:26:06 AM11/23/09
to nhu...@googlegroups.com
no.

2009/11/23 Rémi Després-Smyth <re...@terracognita.ca>



--
Fabio Maulo

Rémi Després-Smyth

unread,
Nov 23, 2009, 9:37:39 AM11/23/09
to nhu...@googlegroups.com

Thanks Socratees.

 

It does work with the lock mode explicitly specified – I’ve tried that.  In the NHib docs, they refer to this (loading with an explicit lock mode) as pessimistic locking (http://nhforge.org/doc/nh/en/index.html#transactions-optimistic, section 10.6).  However, the behavior I’m seeing when specifying the lock mode looks like optimistic locking to me (as described in section 10.4).  That is: grab the record, assume that you can edit it, and when it’s time to save, check the version – if it was updated by someone else, throw an exception due to conflict.

 

In my test below:

 

·         If I load the instances specifying a lock mode myself (the NHib docs call “pessimistic locking”), it looks to me like it’s doing optimistic locking;

·         If I don’t specify the lock mode when loading the isntances (which NHib calls “optimistic locking”), it looks to me like it isn’t doing anything at all.

 

I assume that I don’t have a proper understanding of locking in this context, and I was hoping that someone could clarify it for me.

 

Remi.

Rémi Després-Smyth

unread,
Nov 23, 2009, 9:39:00 AM11/23/09
to nhu...@googlegroups.com

Clearly I don’t have a correct understanding of how it works then.  Can I bother you to clarify where I’m wrong, so I can learn?  I’m not looking for an answer to a specific problem, I want to understand.

 

Regards,

Oskar Berggren

unread,
Nov 23, 2009, 9:43:13 AM11/23/09
to nhu...@googlegroups.com
Did the changes I suggested make a difference?

/Oskar


2009/11/23 Rémi Després-Smyth <re...@terracognita.ca>:

Rémi Després-Smyth

unread,
Nov 23, 2009, 10:03:28 AM11/23/09
to nhu...@googlegroups.com
Thanks Oskar - you're right, it did. Using 'Get' gives me the behavior I'd
expect, while using Load does not. I'm not sure what to think about this.

Remi.

Oskar Berggren

unread,
Nov 23, 2009, 10:28:18 AM11/23/09
to nhu...@googlegroups.com
It is according to documentation.

Rémi Després-Smyth

unread,
Nov 23, 2009, 11:01:33 AM11/23/09
to nhu...@googlegroups.com
Sorry, yes, I see in the docs that Load will pull a proxy. What I don't
understand is why the differences in regards to locking, and how
careful/concerned I need to be.

And given that I'm clearly too undignified for Fabio's valuable time and
attention, if anyone else might correct the mental model I have of how this
works, I'd appreciate it.

My belief is/was that: (1) an IIS has a thread pool to server requests; (2)
as a result, different requests in an application may be handled by
different threads; (3) since the NHib session isn't thread-safe, you need to
use a session-per-request model for apps - and, in this context, different
sessions could be accessing the same instances - thus my locking test below
that has 2 sessions.

Oskar Berggren

unread,
Nov 23, 2009, 11:36:36 AM11/23/09
to nhu...@googlegroups.com
The proxy does not actually contain any of your data after the Load.
Not until you actually access its properties. When it's finally loaded
it would already see the changes committed in the other transaction,
and so there is no conflict. You should get a conflict error if you
keep using Load(), but alter the order to modify b.Prop2 before commit
the transaction that has loaded a.

Consider what could happen in web scenario:

Client 1 submits a form, that uses Load() to get a proxy.

Client 2 access the same form, which uses Load() to get a proxy.

Client 1's page request access the object (causing data to be fetched
from DB), modifies it and commits.

Client 2's page request begin rendering by accessing a property of the
object, causing the load. This now already sees the values as commited
by client 1.


Important thing to consider: If you design your page to load the
object, render to form, send form to client, user edits, submit form,
you load object again and begin update it, then YOU need to verify
that the object you load the second time is the same version as when
you first displayed the form to the client. NHibernate cannot help you
with that.

Rémi Després-Smyth

unread,
Nov 23, 2009, 1:42:57 PM11/23/09
to nhu...@googlegroups.com
Oskar,
my very sincere thanks for your help - your reply is incredibly helpful in
clearing up my mind, understanding what was going on in my test, and in
better understanding how NHibernate works generally.
Reply all
Reply to author
Forward
0 new messages