diff --git a/jbpm-flow/src/main/java/org/jbpm/process/core/timer/impl/QuartzSchedulerService.java b/jbpm-flow/src/main/java/org/jbpm/process/core/timer/impl/QuartzSchedulerService.java index 7ba9c3bc14..ea606161ec 100644 --- a/jbpm-flow/src/main/java/org/jbpm/process/core/timer/impl/QuartzSchedulerService.java +++ b/jbpm-flow/src/main/java/org/jbpm/process/core/timer/impl/QuartzSchedulerService.java @@ -48,6 +48,7 @@ import java.io.NotSerializableException; import java.io.Serializable; import java.util.Collection; +import java.util.Date; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -67,6 +68,8 @@ public class QuartzSchedulerService implements GlobalSchedulerService { private static final Integer FAILED_JOB_RETRIES = Integer.parseInt(System.getProperty("org.jbpm.timer.quartz.retries", "5")); private static final Integer FAILED_JOB_DELAY = Integer.parseInt(System.getProperty("org.jbpm.timer.quartz.delay", "1000")); + private static final Integer RESCHEDULE_DELAY = Integer.parseInt(System.getProperty("org.jbpm.timer.quartz.reschedule.delay", "500")); + private AtomicLong idCounter = new AtomicLong(); private TimerService globalTimerService; private SchedulerServiceInterceptor interceptor = new DelegateSchedulerServiceInterceptor(this); @@ -156,8 +159,16 @@ public void internalSchedule(TimerJobInstance timerJobInstance) { jobq.setRequestsRecovery(true); jobq.getJobDataMap().put("timerJobInstance", timerJobInstance); + // Amend nextFireTime not to schedule older than now + RESCHEDULE_DELAY + Date nextFireTime = timerJobInstance.getTrigger().hasNextFireTime(); + Date threshold = new Date(System.currentTimeMillis() + RESCHEDULE_DELAY); + if (nextFireTime.before(threshold)) { + logger.debug("nextFireTime [" + nextFireTime + "] is older than (now + RESCHEDULE_DELAY). Amending it to [" + threshold + "]"); + nextFireTime = threshold; + } + // Define a Trigger that will fire "now" - org.quartz.Trigger triggerq = new SimpleTrigger(quartzJobHandle.getJobName()+"_trigger", quartzJobHandle.getJobGroup(), timerJobInstance.getTrigger().hasNextFireTime()); + org.quartz.Trigger triggerq = new SimpleTrigger(quartzJobHandle.getJobName()+"_trigger", quartzJobHandle.getJobGroup(), nextFireTime); // Schedule the job with the trigger try { diff --git a/jbpm-test-coverage/src/test/java/org/jbpm/test/functional/timer/GlobalQuartzDBTimerServiceTest.java b/jbpm-test-coverage/src/test/java/org/jbpm/test/functional/timer/GlobalQuartzDBTimerServiceTest.java index aa6ea335dd..94a184e755 100644 --- a/jbpm-test-coverage/src/test/java/org/jbpm/test/functional/timer/GlobalQuartzDBTimerServiceTest.java +++ b/jbpm-test-coverage/src/test/java/org/jbpm/test/functional/timer/GlobalQuartzDBTimerServiceTest.java @@ -305,9 +305,6 @@ public void afterNodeLeft(ProcessNodeLeftEvent event) { countDownListener.waitTillCompleted(3000); assertEquals(2, timerExporations.size()); - - - manager.close(); } @Test(timeout=20000) @@ -355,4 +352,58 @@ public void testTimerRequiresRecoveryFlagSet() throws Exception { ksession.abortProcessInstance(processInstance.getId()); manager.disposeRuntimeEngine(runtime); } + + @Test(timeout=25000) + public void testContinueTimerWithMisfire() throws Exception { + // RHBPMS-4729 + System.setProperty("org.quartz.properties", "quartz-db-short-misfire.properties"); + + CountDownProcessEventListener countDownListener = new CountDownProcessEventListener("StartProcess", 2); + // prepare listener to assert results + final List timerExporations = new ArrayList(); + ProcessEventListener listener = new DefaultProcessEventListener(){ + @Override + public void beforeProcessStarted(ProcessStartedEvent event) { + timerExporations.add(event.getProcessInstance().getId()); + } + }; + + // No special configuration for TimerService in order to test RuntimeManager default + environment = RuntimeEnvironmentBuilder.Factory.get() + .newDefaultBuilder() + .entityManagerFactory(emf) + .addAsset(ResourceFactory.newClassPathResource("org/jbpm/test/functional/timer/TimerStart2.bpmn2"), ResourceType.BPMN2) + .registerableItemsFactory(new TestRegisterableItemsFactory(listener, countDownListener)) + .get(); + manager = getManager(environment, true); + + RuntimeEngine runtime = manager.getRuntimeEngine(ProcessInstanceIdContext.get()); + KieSession ksession = runtime.getKieSession(); + + countDownListener.waitTillCompleted(); + + manager.disposeRuntimeEngine(runtime); + manager.close(); + + System.out.println("==== manager.close() ===="); + + countDownListener.reset(3); + + // Simulate interval between shutdown and start so the Trigger is older than (now - misfireThreshold) + Thread.sleep(5000); + + // ---- restart ---- + environment = RuntimeEnvironmentBuilder.Factory.get() + .newDefaultBuilder() + .entityManagerFactory(emf) + .addAsset(ResourceFactory.newClassPathResource("org/jbpm/test/functional/timer/TimerStart2.bpmn2"), ResourceType.BPMN2) + .registerableItemsFactory(new TestRegisterableItemsFactory(listener, countDownListener)) + .get(); + manager = getManager(environment, true); + + countDownListener.waitTillCompleted(4000); + + assertEquals(5, timerExporations.size()); + } + } diff --git a/jbpm-test-coverage/src/test/resources/quartz-db-short-misfire.properties b/jbpm-test-coverage/src/test/resources/quartz-db-short-misfire.properties new file mode 100644 index 0000000000..449685fc98 --- /dev/null +++ b/jbpm-test-coverage/src/test/resources/quartz-db-short-misfire.properties @@ -0,0 +1,66 @@ +#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore + + +#============================================================================ +# Configure Main Scheduler Properties +#============================================================================ + +org.quartz.scheduler.instanceName = TestScheduler +org.quartz.scheduler.instanceId = instance_one +org.quartz.scheduler.skipUpdateCheck=true +org.quartz.scheduler.idleWaitTime=1000 +#============================================================================ +# Configure ThreadPool +#============================================================================ + +org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool +org.quartz.threadPool.threadCount = 15 +org.quartz.threadPool.threadPriority = 5 + + +#============================================================================ +# Configure JobStore +#============================================================================ + +org.quartz.jobStore.misfireThreshold = 3000 + +org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreCMT +org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +org.quartz.jobStore.useProperties=false +org.quartz.jobStore.dataSource=myDS +org.quartz.jobStore.nonManagedTXDataSource=notManagedDS +org.quartz.jobStore.tablePrefix=QRTZ_ +org.quartz.jobStore.isClustered=false + +#============================================================================ +# Other Example Delegates +#============================================================================ +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.CloudscapeDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DB2v6Delegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DB2v7Delegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DriverDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.HSQLDBDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.MSSQLDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PointbaseDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.WebLogicDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate +#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.WebLogicOracleDelegate + +#============================================================================ +# Configure Datasources +#============================================================================ +org.quartz.dataSource.myDS.jndiURL=jdbc/jbpm-ds +#this notManagedDS should be same as one defined in TimerBaseTest class +#org.quartz.dataSource.notManagedDS.driver=org.h2.Driver +#org.quartz.dataSource.notManagedDS.URL=jdbc:h2:mem:test;MVCC=true +#org.quartz.dataSource.notManagedDS.user=sa +#org.quartz.dataSource.notManagedDS.password= +#org.quartz.dataSource.notManagedDS.maxConnections=5 +org.quartz.dataSource.notManagedDS.connectionProvider.class=org.jbpm.test.functional.timer.addon.NonTransactionalConnectionProvider +org.quartz.dataSource.notManagedDS.driverClassName=org.h2.Driver +org.quartz.dataSource.notManagedDS.user=sa +org.quartz.dataSource.notManagedDS.password=sasa +org.quartz.dataSource.notManagedDS.url=jdbc:h2:mem:test;MVCC=true +