A coder's blog.

Ideas, projects, problems and solutions - whatever is interesting.

Concurrency Issues (Grails 1.3.1)

[originally posted on 2010-05-20]

Concurrency issues, false optimistic locking and other pleasures

Problems with concurrency and database locking in Grails

Problem description: StaleObjectStateException

When a user reloads the fight page too quickly, he will get the following exception (Ubuntu 10.04, Windows Server 2003, MySQL 5, Grails 1.3.1)

1
2
3
4
5
6
7
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction 
    (or unsaved-value mapping was incorrect): [de.dewarim.goblin.Combat#1]
    at de.dewarim.goblin.FightService$$EnhancerByCGLIB$$342d3b0a.fight(generated)
    at de.dewarim.goblin.FightService$fight.call(Unknown Source)
    at de.dewarim.goblin.FightController$_closure3.doCall(FightController.groovy:118)
    at de.dewarim.goblin.FightController$_closure3.doCall(FightController.groovy)
    at java.lang.Thread.run(Thread.java:619)

The offending class is loaded in the FightController and passed on into the transactional FightService. My original expectation was: the service class is transactional and should begin a new transaction for each request, during which the combat object will be updated (it gets a few new combat messages added). And as transactions should be atomic, running one should prevent other requests from getting a hold of the object or even modifying it. But it seems like this is not working correctly, probably, because the combat object is loaded before it is passed into the FightService.

Miss’ Exception and her daughters

So I tried to it with programmatic transaction demarcation and would try the last action again if I encountered a HibernateOptimisticLockingFailureException. (The proper way according to some blog posts would be to try repeatedly, until either max_retries has been reached or the action succeeds.)

1
2
3
4
5
6
7
8
9
10
11
 Combat.withTransaction {
        ...
        String action = fightService.fight(combat, pc, mob)
        try{
            action = fightService.fight(combat, pc, mob)
        }
        catch(HibernateOptimisticLockingFailureException foo){
              action = fightService.fight(combat, pc, mob)
        }
        ...
    }

But this does not seem to help, although the error messages are sometimes different:

errors.GrailsExceptionResolver Deadlock found when trying to get lock; try restarting transaction
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; 
try restarting transaction
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:409)
    at com.mysql.jdbc.Util.getInstance(Util.java:384)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1066)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3562)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3494)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1960)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2114)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2696)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2105)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2398)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2316)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2301)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:102)
    at de.dewarim.goblin.FightController$_closure3.doCall(de.dewarim.goblin.FightController:99)
    at de.dewarim.goblin.FightController$_closure3.doCall(de.dewarim.goblin.FightController)
    at java.lang.Thread.run(Thread.java:619)

I searched and found

  • Pessimistic Locking with Grails (2008); which does point out that versioning must be disabled and that there are some problems with pessimistic locking.
  • GRAILS-3315, a major bug which has only been open for almost 2 years, confirms this problem and advises to just disable versioning: Seems you have to disable versioning altogether and cannot combine the approaches (Update: Bug has been closed on 2011-09-01: ‘static lock method requires a transaction’)

    static mapping { version: false }

But I do not like the idea of disabling versioning, because this would also mean disabling optimistic locking on this class completely. Nevertheless, I tried it on the Combat class. The result of just using Combat.lock(id) was:

util.JDBCExceptionReporter Deadlock found when trying to get lock;
try restarting transaction
events.PatchedDefaultFlushEventListener Could not synchronize database state with session
org.hibernate.exception.LockAcquisitionException: could not update: [de.dewarim.goblin.Combat#1]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:105)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)

Then, I tried it once more inside a programmatic transaction block, and finally, it seems to work:

1
2
3
4
5
6
7
    // Combat class contains: static mapping = {version:false}
    // inside controller:
    Combat.withTransaction {
            combat = Combat.lock(params.combat)
            ...
            fightService.fight(combat, ...)
    }

Now, I can just press F5 in the browser and the page is reloaded continually (with some growing lag as this time locking does work). The training puppet test quest, which requires several hundred reloads, no longer seems to throw deadlock errors.