Search code examples
hibernategrailsexceptionquartz-schedulergrails-2.0

Quartz job stops executing on StaleObjectStateException exception


I have a problem with quartz ( plugin :quartz2:2.1.6.2 but I have test even with plugin :quartz:1.0-RC7 but the problem does not change) on my Grails Project ( grails 2.2.1 ).

I have a job like this

class MyJob {

def concurrent = false

def execute(context){

        try {

            //....
            // works with domains .....
            myDomain.save(flush: true)
            // works with domains .....
            //....

            sessionFactory.currentSession.flush()

        } catch (org.springframework.dao.OptimisticLockingFailureException olfe) {
            println "Job failed by database exception "
        } catch ( org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException ole){
            println "Job failed by database exception "
        } catch ( org.hibernate.HibernateException hibe ){
            println "Job failed by database exception "
        }
    }

}

}

Sometimes a StaleObjectStateException occour in the execute method. This is ok for my logic, I'm using grails optimistic locking and this exceptions occour only once a week.

The problem is when this exceptions occour the Job stops to fire again.

I have tried co wrap the method code in a try catch and flush hibernate session inside to capture the exception but without fortune. The exception is not capture by any of my catchs.

Looking online I found this an old grails quartz bug but it's fixed, and in any case using the try{}catch must bypass the bug.

P.S. The job is scheduled from bootstrab by a call of this type

MyJob.schedule( 10000L )

The exception that stops the scheduling is

[194949896] core.ErrorLogger Unable to notify JobListener(s) of Job that was executed: (error will be ignored). trigger= DEFAULT.MT_3tbn6lewgiqa3 job= DEFAULT.MyJob
org.quartz.SchedulerException: JobListener 'persistenceContextJobListener' threw exception: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [MyDomain#42] [See nested exception: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [MyDomain#42]]
    at org.quartz.core.QuartzScheduler.notifyJobListenersWasExecuted(QuartzScheduler.java:1939)
    at org.quartz.core.JobRunShell.notifyJobListenersComplete(JobRunShell.java:361)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:235)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:557)
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [MyDomain#42]
    at grails.plugin.quartz2.PersistenceContextJobListener.jobWasExecuted(PersistenceContextJobListener.groovy:46)
    at org.quartz.core.QuartzScheduler.notifyJobListenersWasExecuted(QuartzScheduler.java:1937)
    ... 3 more

.....

events.PatchedDefaultFlushEventListener Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [MyJob#42]
    at MyJob.execute(MyJob.groovy:354)
    at grails.plugin.quartz2.GrailsArtefactJob.execute(GrailsArtefactJob.java:57)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:213)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:557)

Solution

  • I apologize for reviving an old post, but we encountered this issue with a legacy Grails app (Grails 2.2.3) recently and flushing the session in the execute method did not always solve the issue so I'll outline what we did to fix the problem.

    In our case, sometimes the exception would happen outside the context of the execute method, even when we explicitly flushed the session within the execute method. More specifically, the exception is thrown within the Quartz2 plugin's PersistenceContextJobListener code that flushes the session AFTER the execute method was finished executing. So after reviewing the Quartz2 plugin code we realized that that we needed to override the default PersistenceContextJobListener that wraps the job execute method and flushes the session.

    First, notice that there's no exception handling within the jobWasExecuted callback method of the PersistenceContextJobListener.

    https://github.com/9ci/grails-quartz2/blob/master/src/groovy/grails/plugin/quartz2/PersistenceContextJobListener.groovy#L44

    All you really need to do is implement your own job listener and wrap the jobWasExecuted code in a try/catch. See the following code snippets for an example of how to do this.

    https://gist.github.com/jmiranda/45084eb32f07f6e3d1934547cd4fbb9f https://gist.github.com/jmiranda/5148f0a67afc8950bad950793e9c2303

    We used the original Quartz plugin's SessionBinderJobListener as an example (err, we more or less copied it).

    https://github.com/grails-plugins/grails-quartz/blob/master/src/main/groovy/grails/plugins/quartz/listeners/SessionBinderJobListener.java

    Anyway, this should get you to the point where you are preventing triggered jobs from stopping altogether due to the uncaught StaleObjectStateException.