Uploaded image for project: 'Spring Batch'
  1. Spring Batch
  2. BATCH-1767

OptimisticLockingFailureException updating step execution after commit failure

    Details

    • Type: Bug
    • Status: Open
    • Priority: Major
    • Resolution: Unresolved
    • Affects Version/s: 2.1.7
    • Fix Version/s: 3.1.0
    • Component/s: None
    • Labels:
      None
    • Environment:
      Spring 3.0.5
      Spring Batch 2.1.7
      Eclipselink 1.1.1

      Description

      It appears that if the commit fails, spring batch will get an OptimisticLockingFailureException when it tries to revert the changes to the step execution. In my particular case, I have a callback through EclipseLink to update history tables before a transaction is committed. If a failure occurs during this callback, the commit fails.

      From looking through the code and the attached log file, the step execution is updated and committed before the main transaction is committed. When the commit fails, the old values for the step execution (including version) are updated to the values before the chuck started. When control returns to AbstractStep.execute(), the OptimisticLockingFailureException is thrown when the step execution is updated with the failed status because the new version had already been committed to the database.

      2011-07-06 17:40:29,494 ERROR SimpleAsyncTaskExecutor-1 [org.springframework.batch.core.step.AbstractStep] Encountered an error saving batch meta data. This job is now in an unknown state and should not be restarted.
      org.springframework.dao.OptimisticLockingFailureException: Attempt to update step execution id=3225 with wrong version (35), where current version is 36
      at org.springframework.batch.core.repository.dao.JdbcStepExecutionDao.updateStepExecution(JdbcStepExecutionDao.java:185)
      at org.springframework.batch.core.repository.support.SimpleJobRepository.update(SimpleJobRepository.java:171)
      at sun.reflect.GeneratedMethodAccessor130.invoke(Unknown Source)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
      at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
      at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
      at $Proxy77.update(Unknown Source)
      at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:244)
      at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:135)
      at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:61)
      at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:60)
      at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:144)
      at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:124)
      at org.springframework.batch.core.job.flow.support.state.SplitState$1.call(SplitState.java:91)
      at org.springframework.batch.core.job.flow.support.state.SplitState$1.call(SplitState.java:89)
      at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
      at java.util.concurrent.FutureTask.run(FutureTask.java:138)
      at java.lang.Thread.run(Thread.java:662)

      1. BATCH-1767.log.gz
        17 kB
        Philippe Mouawad
      2. log-1.txt
        43 kB
        Quinton McCombs
      3. scenario.txt
        3 kB
        Kristof Buts

        Activity

        Hide
        david@davidkarlsen.com David J. M. Karlsen added a comment -

        Any movement on this issue?

        Show
        david@davidkarlsen.com David J. M. Karlsen added a comment - Any movement on this issue?
        Hide
        sarveshkatariya Sarvesh Katariya added a comment -

        Any updates/workaround on this issue ?

        Show
        sarveshkatariya Sarvesh Katariya added a comment - Any updates/workaround on this issue ?
        Hide
        matternikus Marcus Mattern added a comment -

        We did a simple workaround for us to avoid this problem.
        I only patched the afterCompletion method of inner class org.springframework.batch.core.step.tasklet.TaskletStep.ChunkTransactionCallback.

        {{ @Override
        public void afterCompletion(int status) {
        try {
        if (status != TransactionSynchronization.STATUS_COMMITTED) {
        if (stepExecutionUpdated) {
        // Wah! the commit failed. We need to rescue the step
        // execution data.
        logger.info("Commit failed while step execution data was already updated. "
        + "Reverting to old version.");
        //FIX Start
        // WORKAROUND für https://jira.springsource.org/browse/BATCH-1767
        // OptimisticLockingFailureException updating step execution after commit failure
        // Description:
        // - save version number of step execution that should be updated
        // - copy old step details like before
        // - set new version number instead of old copied number
        final Integer savedVersion = stepExecution.getVersion();
        //FIX End
        copy(oldVersion, stepExecution);
        //FIX Start
        stepExecution.setVersion(savedVersion);
        //FIX End
        if (status == TransactionSynchronization.STATUS_ROLLED_BACK)

        { rollback(stepExecution); }

        }
        chunkListener.afterChunkError(chunkContext);
        }

        if (status == TransactionSynchronization.STATUS_UNKNOWN)

        { logger.error("Rolling back with transaction in unknown state"); rollback(stepExecution); stepExecution.upgradeStatus(BatchStatus.UNKNOWN); stepExecution.setTerminateOnly(); }

        }
        finally {
        // Only release the lock if we acquired it, and release as late
        // as possible
        if (locked)

        { semaphore.release(); }

        locked = false;
        }
        }
        }}

        Since then our problem was gone.

        Marcus

        Show
        matternikus Marcus Mattern added a comment - We did a simple workaround for us to avoid this problem. I only patched the afterCompletion method of inner class org.springframework.batch.core.step.tasklet.TaskletStep.ChunkTransactionCallback. {{ @Override public void afterCompletion(int status) { try { if (status != TransactionSynchronization.STATUS_COMMITTED) { if (stepExecutionUpdated) { // Wah! the commit failed. We need to rescue the step // execution data. logger.info("Commit failed while step execution data was already updated. " + "Reverting to old version."); //FIX Start // WORKAROUND für https://jira.springsource.org/browse/BATCH-1767 // OptimisticLockingFailureException updating step execution after commit failure // Description: // - save version number of step execution that should be updated // - copy old step details like before // - set new version number instead of old copied number final Integer savedVersion = stepExecution.getVersion(); //FIX End copy(oldVersion, stepExecution); //FIX Start stepExecution.setVersion(savedVersion); //FIX End if (status == TransactionSynchronization.STATUS_ROLLED_BACK) { rollback(stepExecution); } } chunkListener.afterChunkError(chunkContext); } if (status == TransactionSynchronization.STATUS_UNKNOWN) { logger.error("Rolling back with transaction in unknown state"); rollback(stepExecution); stepExecution.upgradeStatus(BatchStatus.UNKNOWN); stepExecution.setTerminateOnly(); } } finally { // Only release the lock if we acquired it, and release as late // as possible if (locked) { semaphore.release(); } locked = false; } } }} Since then our problem was gone. Marcus
        Hide
        membersound member sound added a comment -

        Any updates on this fix? I got the same problem...

        Show
        membersound member sound added a comment - Any updates on this fix? I got the same problem...
        Hide
        plastictoast James Home added a comment - - edited

        @Michael

        Regarding the comment: "With regards to why the copy method restores the version number...why wouldn't it? The intent is that there was a rollback so the state should match what was there before the rollback."

        The thing is, it's not actually a rollback in the database sense. You're really just resetting the counts the their previous values. It's a new update in a db sense, so surely it should use the appropriate version number. The policy of 'rolling back' the version number in a db update seems to be incompatible with the principle of version based optimistic locking that's being used by the dao.

        Show
        plastictoast James Home added a comment - - edited @Michael Regarding the comment: "With regards to why the copy method restores the version number...why wouldn't it? The intent is that there was a rollback so the state should match what was there before the rollback." The thing is, it's not actually a rollback in the database sense. You're really just resetting the counts the their previous values. It's a new update in a db sense, so surely it should use the appropriate version number. The policy of 'rolling back' the version number in a db update seems to be incompatible with the principle of version based optimistic locking that's being used by the dao.

          People

          • Assignee:
            mminella Michael Minella
            Reporter:
            quintonm Quinton McCombs
          • Votes:
            18 Vote for this issue
            Watchers:
            24 Start watching this issue

            Dates

            • Created:
              Updated: