6

I have reached an issue in Grails which I think could be a potential issue with how concurrency is handled; and I am unsure of how to best handle this (or if there is a solution / practice already in place I can adapt).

Background


My Grails application is serving as a REST API, and has an enciphering method for data which relies on a counter variable as a salting mechanism for the cipher.

This counter variable must be maintained, and cannot be lower as the data is going to a SIM card where the counter can not be modified post-issuance, therefore it is important that I maintain this counter correctly. Additionally, if the counter is incorrect, the message will be rejected by the SIM.

When a user calls, for example: http://example.tld/service/controller/action?id=1 the server will do the following:

  • get the object controller with identifier 1
  • Modify the counter member/row of the object
  • save the object

This has been fine, for over 20,000 requests. However, twice, I have had a StaleObjectException which I have determined to happen due to the object being accessed at the exact same time. I have determined that this is happening as the wrapper API I have provided uses ten threads, and by chance both threads have called the action at the same time.

I have read and noted that I can either:

  • Turn off Optimistic Locking (this seems like a bad idea)
  • Turn on dynamic updates (I don't think that this will help me, as I am still updating the same row at the same time)
  • lock the object -- but I think this will still raise an Exception due to the object being locked the second time it is accessed?
  • Use executeUpdate which I think is similar to turning on Dynamic Updates? Perhaps not useful in my use case.

Question


I am wondering if there's perhaps some transaction mechanism I can use? I.e. a method to check if an object is currently locked, and if so, sleep for t to allow the transaction to complete in the database.

Ultimately, my end goal is to not reject any requests, therefore if the transaction mechanism above exists (which I assume will be some sort of pessimistic lock) and said transaction mechanism rejects the request as the object is locked, I would rather some other solution as I want to avoid at all costs rejecting requests to the service as it complicates prior deliveries to clients.

The current solution I am thinking of is to just:

try { 
    Foo.save()
catch (RespectiveException ex) {
    Thread.sleep(1000)
    if(depth < 3) {
        recursiveCallToThisMethod(depth++)
    } else {
        render(letTheUserKnowWhyItFailed)
    }
}

But yeah... Pretty ugly.

chrisburke.io
  • 1,497
  • 2
  • 17
  • 26
  • Typically a concurrent modification exception is expected to be handled by the client, which then has to retry the operation. – Ken Liu Aug 13 '13 at 20:00
  • this question might help: http://stackoverflow.com/questions/129329/optimistic-vs-pessimistic-locking – Ken Liu Aug 13 '13 at 20:01
  • Did you come up with any solution to this? I have the same problem. The only thing I can think to try is to have a concurrenthashmap of synchonized block lock objects referenced by unique key from the domain object itself and held by the service singleton. When the service method is called it firsts checks the map to see if there is a lock object already there for the domain objects key value and uses that in the synchronized block. If the lock object is not there it creates one and stores it to the map. The object must be removed from the map in a finally block when you done – Chris.D Dec 20 '13 at 22:20
  • The StaleObjectException occurs as the object is locked by the database, so you would need to take into account why that has happened. If you're storing new data, you need some validation in place, however if you're just regularly updating then you can just wait until the lock is returned and re-attempt the operation (however in high-traffic systems, this could cause a misrepresentation of the 'latest' thing sent to the server, versus what is in the database). It could be better to try to understand why a member must be updated so frequently, I think. Perhaps adding ephemeral data is a way. – chrisburke.io Dec 26 '13 at 02:58
  • try using object.refresh and object.merge. refresh will avoid staleobjectexception and merge will allow merging two object with different state in hibenrate. – Vinay Prajapati Apr 12 '15 at 04:44

1 Answers1

0

Try pessimistic locking. There will be no exception on the second request, it will just block until the first is finished.

def controller = Controller.lock(params.id)
controller.counter += 1
controller.save()
and
  • 2,024
  • 3
  • 24
  • 31