History | Log In     View a printable version of the current page.  
Issue Details (XML | Word | Printable)

Key: GRAILS-2790
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Blocker Blocker
Assignee: Graeme Rocher
Reporter: J Pimmel
Votes: 1
Watchers: 2
Operations

If you were logged in you would be able to see more operations.
Grails

Pessimistic locking on domain objects dont appear to be getting locked for update

Created: 08/Apr/08 09:59 AM   Updated: 06/May/08 06:45 AM
Component/s: Persistence
Affects Version/s: 1.0-RC2, 1.0.2
Fix Version/s: 1.0.3

Time Tracking:
Not Specified

File Attachments: 1. Zip Archive locking-problem.zip (39 kb)

Environment: Java 1.6, Ubuntu 7.10

Testcase included: yes


 Description  « Hide
Using the attached example the failing test demonstrates that when locking a record for update we dont get the expected behaviour you would have thought to occur when 'selecting for update' and forcing the DB to queue pending changes to a row when concurrent saves on a locked row are being requested by clients

Resolution would be that the failing test would pass and concurrent writes to pessimistically locked rows would be correctly handled and the expected count.



 All   Comments   Work Log   Change History      Sort Order: Ascending order - Click to sort in descending order
Graeme Rocher - 09/Apr/08 05:15 AM
So there are a number of problems with your submitted code, some related to Grails some related to your code and the nature of the test.

First for Grails:

1) The HSQLDB database shipped with Grails doesn't support "SELECT .. FOR UPDATE" and hence pessimistic locking is not possible (see http://hsqldb.org/doc/src/org/hsqldb/jdbc/jdbcStatement.html#setCursorName(java.lang.String)). I have updated the documentation to reflect this.
2) Even on a database where SELECT .. FOR UPDATE is possible like MySQL you may still get a problem with data loss as you're doing an initial get and then a later lock:

def foo = Foo.get(id)
foo.lock()

In between the time it takes to do the get and call the lock() method another thread could have obtained and locked the db done its changes and you end up with data loss. To avoid this situation I've added a static lock method so you can do:

def foo = Foo.lock(id)

However, even with this change you test will not pass. Why? because the test shares the same Hibernate session even across all the threads, so essentially its locking on the same object. In Grails different requests will have different sessions bound to each thread which where each willl have a different object to lock on.


J Pimmel - 10/Apr/08 08:14 AM
Yeah, i only just saw your message and i was reminded of a similar problem we had once in hibernate and investigated that further during the day..

Because of the presence if so many concurrent sessions, the only way to atomically update something like a counter where there may be many contending threads attempting to increment, was to relinquish control to the database.

We solved by


def update = sessionFactory.currentSession.createSQLQuery("update foo set counter = counter + 1 where id = 123")
update.executeUpdate()
{code)


Stephen Cresswell - 06/May/08 06:33 AM
Graeme,

I'm using postgres and driving my application via HTTP Unit from multiple threads and locking still doesn't work as advertised.

From http://grails.org/doc/1.0.x/guide/single.html#5.3.5%20Pessimistic%20and%20Optimistic%20Locking

In Grails pessimistic locking is performed via the lock method:

def airport = Airport.get(10)
airport.lock() // lock for update
airport.name = "Heathrow"
airport.save()

When I follow this pattern the result is multiple OptimisticLockingFailureExceptions

Your suggestion of doing

airport.lock(10)
airport.name = "Heathrow"
airport.save()

results in

No signature of method: Person.lock() is applicable for argument types: (java.lang.String) values: {"ff80818119bdf60d0119bdf7d5c80005"}

(The id is a String since we're using uuid)


Stephen Cresswell - 06/May/08 06:45 AM
FYI I believe the reason airport.lock() doesn't work is because according to the hibernate docs, if "airport" has already been loaded in the session, then lock() does a version check - which may fail under concurrency. The workaround is it evict airport from the session then re-obtain it using session.load(clazz, id, LockMode.UPGRADE), e.g.

def session = sessionFactory.currentSession
session.evict(airport) // Force the following load to actually go to the DB instead of doing a version check
airport = session.load(Airport.class, airport.id, LockMode.UPGRADE)

This seems to work OK