diff --git a/jbpm-services/jbpm-services-ejb/jbpm-services-ejb-timer/src/main/java/org/jbpm/services/ejb/timer/EJBTimerScheduler.java b/jbpm-services/jbpm-services-ejb/jbpm-services-ejb-timer/src/main/java/org/jbpm/services/ejb/timer/EJBTimerScheduler.java index 325b85de0a..88aafbe941 100644 --- a/jbpm-services/jbpm-services-ejb/jbpm-services-ejb-timer/src/main/java/org/jbpm/services/ejb/timer/EJBTimerScheduler.java +++ b/jbpm-services/jbpm-services-ejb/jbpm-services-ejb-timer/src/main/java/org/jbpm/services/ejb/timer/EJBTimerScheduler.java @@ -42,6 +42,8 @@ import javax.ejb.Timer; import javax.ejb.TimerConfig; import javax.ejb.TimerHandle; +import javax.ejb.TransactionAttribute; +import javax.ejb.TransactionAttributeType; import org.drools.core.time.JobHandle; import org.drools.core.time.impl.TimerJobInstance; @@ -109,49 +111,60 @@ public void executeTimerJob(Timer timer) { Thread.currentThread().interrupt(); } try { - ((Callable) timerJobInstance).call(); + invokeTransaction(this::executeTimerJobInstance, timerJobInstance); } catch (Exception e) { recoverTimerJobInstance(timerJob, timer, e); } } + private void executeTimerJobInstance(TimerJobInstance timerJobInstance) throws Exception { + ((Callable) timerJobInstance).call(); + } private void recoverTimerJobInstance(EjbTimerJob ejbTimerJob, Timer timer, Exception cause) { - - TimerJobInstance timerJobInstance = ejbTimerJob.getTimerJobInstance(); + Transaction tx; if (isSessionNotFound(cause)) { // if session is not found means the process has already finished. In this case we just need to remove // the timer and avoid any recovery as it should not trigger any more timers. - logger.warn("Trying to recover timer. Not possible due to process instance is not found. More likely already completed. Timer {} won't be recovered", timerJobInstance, cause); - if (!removeJob(timerJobInstance.getJobHandle(), timer)) { + tx = timerJobInstance -> { + logger.warn("Trying to recover timer. Not possible due to process instance is not found. More likely already completed. Timer {} won't be recovered", timerJobInstance, cause); + if (!removeJob(timerJobInstance.getJobHandle(), timer)) { logger.warn("Session not found for timer {}. Timer could not removed.", timerJobInstance); - } + } + }; } - else if (timerJobInstance.getTrigger().hasNextFireTime() != null) { + else if (ejbTimerJob.getTimerJobInstance().getTrigger().hasNextFireTime() != null) { // this is an interval trigger. Problem here is that the timer scheduled by DefaultTimerJobInstance is lost // because of the transaction, so we need to do this here. - - logger.warn("Execution of time failed Interval Trigger failed. Skipping {}", timerJobInstance); - if (removeJob(timerJobInstance.getJobHandle(), null)) { - internalSchedule(timerJobInstance); - } else { + tx = timerJobInstance -> { + logger.warn("Execution of time failed Interval Trigger failed. Skipping {}", timerJobInstance); + if (removeJob(timerJobInstance.getJobHandle(), null)) { + internalSchedule(timerJobInstance); + } else { logger.debug("Interval trigger {} was removed before rescheduling", timerJobInstance); - } - + } + }; } else { // if there is not next date to be fired, we need to apply policy otherwise will be lost - logger.warn("Execution of time failed. The timer will be retried {}", timerJobInstance); - ZonedDateTime nextRetry = ZonedDateTime.now().plus(TIMER_RETRY_INTERVAL, ChronoUnit.MILLIS); - EjbTimerJobRetry info = ejbTimerJob instanceof EjbTimerJobRetry ? ((EjbTimerJobRetry) ejbTimerJob).next() : new EjbTimerJobRetry(timerJobInstance); - if (TIMER_RETRY_LIMIT > 0 && info.getRetry() > TIMER_RETRY_LIMIT) { - logger.warn("The timer {} reached retry limit {}. It won't be retried again", timerJobInstance, TIMER_RETRY_LIMIT); - } else { - TimerConfig config = new TimerConfig(info, true); - Timer newTimer = timerService.createSingleActionTimer(Date.from(nextRetry.toInstant()), config); - ((GlobalJpaTimerJobInstance) timerJobInstance).setTimerInfo(newTimer.getHandle()); - ((GlobalJpaTimerJobInstance) timerJobInstance).setExternalTimerId(getPlatformTimerId(newTimer)); - } + tx = timerJobInstance -> { + logger.warn("Execution of time failed. The timer will be retried {}", timerJobInstance); + ZonedDateTime nextRetry = ZonedDateTime.now().plus(TIMER_RETRY_INTERVAL, ChronoUnit.MILLIS); + EjbTimerJobRetry info = ejbTimerJob instanceof EjbTimerJobRetry ? ((EjbTimerJobRetry) ejbTimerJob).next() : new EjbTimerJobRetry(timerJobInstance); + if (TIMER_RETRY_LIMIT > 0 && info.getRetry() > TIMER_RETRY_LIMIT) { + logger.warn("The timer {} reached retry limit {}. It won't be retried again", timerJobInstance, TIMER_RETRY_LIMIT); + } else { + TimerConfig config = new TimerConfig(info, true); + Timer newTimer = timerService.createSingleActionTimer(Date.from(nextRetry.toInstant()), config); + ((GlobalJpaTimerJobInstance) timerJobInstance).setTimerInfo(newTimer.getHandle()); + ((GlobalJpaTimerJobInstance) timerJobInstance).setExternalTimerId(getPlatformTimerId(newTimer)); + } + }; + } + try { + invokeTransaction (tx, ejbTimerJob.getTimerJobInstance()); + } catch (Exception e) { + logger.error("Failed to executed timer recovery", e); } } @@ -166,6 +179,25 @@ private boolean isSessionNotFound(Exception e) { return false; } + @FunctionalInterface + private interface Transaction { + void doWork(I item) throws Exception; + } + + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public void transaction(Transaction operation, I item) throws Exception { + try { + operation.doWork(item); + } catch (Exception e) { + ctx.setRollbackOnly(); + throw e; + } + } + + private void invokeTransaction (Transaction operation, I item) throws Exception { + ctx.getBusinessObject(EJBTimerScheduler.class).transaction(operation,item); + } + public void internalSchedule(TimerJobInstance timerJobInstance) { Serializable info = removeTransientFields(new EjbTimerJob(timerJobInstance)); TimerConfig config = new TimerConfig(info, true); @@ -191,7 +223,7 @@ private String getPlatformTimerId(Timer timer) { Method method = timer.getClass().getMethod("getId"); return (String) method.invoke(timer); } catch (Exception timerIdException) { - logger.trace("Failed to get the platform timer id", timerIdException); + logger.trace("Failed to get the platform timer id {}", timerIdException.getMessage(), timerIdException); return null; } }