3

Is optimistic locking supposed to catch concurrent update issues?

By concurrent update, I mean that two different users both attempt to update an object with the same version number. For example, if there is a Person domain class, and Person with id = 1, and version = 0, has name = Jack, and two different users both attempt to update the name on version 0, I would expect only one of the user to succeed and change the version to 1. I would expect the second user to get a Hibernate staleStateException or something similar. But that's not what happens.

Here's my use case:

Grails 3.1.5
grails generate-app person
grails create-domain-class Person
Edit Person.groovy to include String name
grails generate-all person.Person
gradle bootRun

Use two different browsers to access the app, such as Chrome and Firefox, to ensure that the two browsers are in different sessions. Create a Person in one of them and then open that same person (version 0) for editing in both browsers. Both browsers should now be editing version 0 of person. Save a name change in one browser, this works and changes the persisted version of the object to 1, but the second browser is still editing version 0. Now save the changes in the second browser, this also works. Despite the fact that the second browser just saved changes to a now stale object (version 0) no StaleObject or StaleStateException is thrown. Is this the correct behavior?

DAC
  • 707
  • 1
  • 6
  • 20

2 Answers2

1

Yes, Grails optimistic locking will detect a concurrent update and throw an exception. However, based on what you described, you're not doing a concurrent update. Let's take a closer look.

  1. The starting point is with both browsers having retrieved the same version of an object. Note however, that the browsers do not hold a reference to these objects. The GSP code grabbed them from the database and rendered them for the browser to display. In other words, the same object is being viewed not edited.
  2. When the first browser calls upon the controller to save the changes, the controller retrieves a fresh copy of the object from the database: ex. DomainClass.get(params.id). Then the changes are saved.
  3. When the second browser calls upon the controller to save the changes, the controller once again retrieves a fresh copy of the object from the database. This time it's version 1, but that's irrelevant because the version has nothing to do with retrieving the object, only when saving. And so the second save succeeds because the version matches what's already in the database.

To create the condition you're seeking, you'd have to play with it until there's an overlap long enough between both saves so that a sequence like this happens:

def a = DomainClass.get(1)
def b = DomainClass.get(1)

/* change a */
a.save()

/* change b */
b.save() // This would throw an exception because b.version does not match what's in the database.
Emmanuel Rosa
  • 9,697
  • 2
  • 14
  • 20
  • 3
    So then if I understand correctly, the Grails/GORM/Hibernate optimistic locking functionality is really designed to detect concurrent update issues that happen in memory on the server. So my hope that this functionality could be extended to the client by sending the version number down to the client and back with subsequent update requests is really outside the scope of it's functionality? – DAC Apr 28 '16 at 20:05
  • 2
    Correct. Not necessarily in memory, but simply on the server side. And your idea to pass the version number is exactly how you can achieve the functionality on the client side. – Emmanuel Rosa Apr 28 '16 at 20:24
  • 3
    But wait, the Grails generated app already is passing the version number to the client, and the client is sending it back. But when the request data is bound to the domain object Grails looks up the object by id only, not id and version, furthermore, it then overwrites the version from the client with the one from the db lookup. It doesn't overwrite the updated name property, it uses the name value from the client. If it would just use the version number from the client also, then the functionality would be extended to the client. – DAC Apr 28 '16 at 21:47
  • Cool. I had no idea it did that. – Emmanuel Rosa Apr 28 '16 at 23:15
-1

I think the precise answer is Grail doesn't implement optimistic lock between requests out of the box.

The confusion comes from the fact that scaffolding generates some code necessary (but not sufficient) for that in edit.gsp:

<g:hiddenField name="version" value="${myInstance?.version}" />

You must implement an extra check in your controller save() action to compare the original rendered version of your record with the database version in the exact saving instant, and inform the user that a dirty read occurred during concurrent edit operations. Something like:

if (new Long(params.version) != myInstance.version) {
    myInstance.errors.rejectValue("", "", "This record has been updated by a concurrent user. Please reopen it before saving again");
    myInstance.version = new Long(params.version); //the version to be re-rendered must still be the original one, just in case the user try saving again without refreshing
    return render(view: "edit", model: [myInstance: myInstance]);
}
Cléssio Mendes
  • 996
  • 1
  • 9
  • 25