2
votes

We have a high concurrency Grails app, with several worker threads attempting update the same domain object, Message, but the pessimistic locking solution implemented in the service (with transactional set to false) using a static lock Message.lock(msg.id) results in many instances of HibernateOptimisticLockingFailureException.

    Facility.withTransaction {
      if (resp?.status == 200) {
        Message msgCopy = Message.lock(msg.id)
        msgCopy.state = State.SoftDeleted
        msgCopy.save(flush: true)
      }
    }

How can a static lock result in a HibernateOptimisticLockingFailure? My understanding is that static lock will read the latest persistent version. It this only a case when another thread has deleted it?

Full error:

[com.cds.healthdock.messaging.Message] with identifier [58653744]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.cds.healthdock.messaging.Message#58653744] at messaging.OutboxService$_pushMessageToPeer_closure8.doCall(OutboxService.groovy:442) at org.grails.datastore.gorm.GormStaticApi.withTransaction(GormStaticApi.groovy:815)

Any strategies (other than catching the exception) I should consider? isDirty() isEmpty()

1
I should add that from a design perspective around two threads attempting to update the same message for any fields including Status, if the response is 200, then the message should always get set to softDelete. Any other updates to the message become irrelevant and can be lost or overwritten. - Christopher Leuer

1 Answers

0
votes

Here is an excellent and very detailed article about this issue. I had exactly the same problem as you and I solved it by using pessimistic locking, i.e.

  • start a transaction
  • get the object and lock it via the static lock() method
  • update the object
  • commit the transaction

It looks like you're doing something similar, however I can't tell whether you're doing it within a transaction because on the one hand you said

the pessimistic locking solution implemented in the service (with transactional set to false)

But on the other hand, the code appears to be executed within withTransaction. The pessimistic locking approach must be executed within a transaction.