package com.bikeemotion.quartz;
import com.bikeemotion.quartz.jobstore.hazelcast.HazelcastJobStore;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.joda.time.DateTime;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobBuilder;
import static org.quartz.JobBuilder.newJob;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import static org.quartz.JobKey.jobKey;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.Scheduler;
import static org.quartz.Scheduler.DEFAULT_GROUP;
import org.quartz.SchedulerContext;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import static org.quartz.TriggerBuilder.newTrigger;
import org.quartz.TriggerKey;
import static org.quartz.TriggerKey.triggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import java.util.UUID;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import static org.testng.AssertJUnit.assertNotNull;
@Test(suiteName = "QuartzTest")
public class QuartzTest extends AbstractTest {
public static int jobExecs = 0;
private static final String BARRIER = "BARRIER";
private static final String DATE_STAMPS = "DATE_STAMPS";
private static final String JOB_THREAD = "JOB_THREAD";
Scheduler scheduler;
@BeforeClass
public void setUp()
throws SchedulerException, InterruptedException {
}
@AfterClass
public void tearDown()
throws SchedulerException {
hazelcastInstance.shutdown();
scheduler.shutdown();
Hazelcast.shutdownAll();
}
@BeforeMethod
public void prepare()
throws SchedulerException {
Config config = new Config();
config.setProperty("hazelcast.logging.type", "slf4j");
config.getGroupConfig().setName(UUID.randomUUID().toString());
hazelcastInstance = Hazelcast.newHazelcastInstance(config);
HazelcastJobStore.setHazelcastClient(hazelcastInstance);
final Properties props = new Properties();
props.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, "BikeemotionScheduler");
props.setProperty(StdSchedulerFactory.PROP_SCHED_JMX_EXPORT, "true");
props.setProperty(StdSchedulerFactory.PROP_JOB_STORE_CLASS, HazelcastJobStore.class.getName());
props.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadCount", "10");
props.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadPriority", "5");
scheduler = new StdSchedulerFactory(props).getScheduler();
scheduler.start();
MyJob.count = 0;
MyJob.jobKeys.clear();
MyJob.triggerKeys.clear();
MyNoConcurrentJob.count = 0;
MyNoConcurrentJob.jobKeys.clear();
MyNoConcurrentJob.triggerKeys.clear();
}
@AfterMethod
public void cleanUp()
throws SchedulerException {
if (scheduler != null && scheduler.isStarted()) {
scheduler.shutdown();
}
}
@Test
public void testSchedule()
throws Exception {
JobDetail job1 = buildJob("Job1", DEFAULT_GROUP, MyJob.class);
JobDetail job2 = buildJob("Job2", DEFAULT_GROUP, MyJob.class);
JobDetail job3 = buildJob("Job3", DEFAULT_GROUP, MyJob.class);
scheduler.scheduleJob(job1, buildTrigger("key1", DEFAULT_GROUP, job1, DateTime.now().plusMillis(100).getMillis()));
scheduler.scheduleJob(job2, buildTrigger("key2", DEFAULT_GROUP, job2, DateTime.now().plusMillis(500).getMillis()));
scheduler.scheduleJob(job3, buildTrigger("key3", DEFAULT_GROUP, job3, DateTime.now().plusMillis(750).getMillis()));
Thread.sleep(800);
assertEquals(MyJob.count, 3);
assertTrue(MyJob.jobKeys.contains(job1.getKey().getName()));
assertTrue(MyJob.jobKeys.contains(job2.getKey().getName()));
assertTrue(MyJob.jobKeys.contains(job3.getKey().getName()));
}
@Test
public void testScheduleDelete()
throws Exception {
JobDetail job1 = buildJob("testScheduleDelete", DEFAULT_GROUP, MyJob.class);
scheduler.scheduleJob(job1,
buildTrigger("k21", DEFAULT_GROUP, job1, DateTime.now().plusMillis(150000).getMillis()));
assertTrue(scheduler.deleteJob(job1.getKey()));
scheduler.scheduleJob(job1, buildTrigger("k21", DEFAULT_GROUP, job1, DateTime.now().plusMillis(150).getMillis()));
Thread.sleep(160);
assertEquals(MyJob.count, 1);
assertTrue(MyJob.jobKeys.contains(job1.getKey().getName()));
}
@Test
public void testScheduleAtSameTime()
throws Exception {
JobDetail job1 = buildJob("testScheduleAtSameTime1", DEFAULT_GROUP, MyJob.class);
JobDetail job2 = buildJob("testScheduleAtSameTime2", DEFAULT_GROUP, MyJob.class);
JobDetail job3 = buildJob("testScheduleAtSameTime3", DEFAULT_GROUP, MyJob.class);
scheduler.scheduleJob(job1, buildTrigger("k21", DEFAULT_GROUP, job1, DateTime.now().plusMillis(100).getMillis()));
scheduler.scheduleJob(job2, buildTrigger("k22", DEFAULT_GROUP, job2, DateTime.now().plusMillis(100).getMillis()));
scheduler.scheduleJob(job3, buildTrigger("k23", DEFAULT_GROUP, job3, DateTime.now().plusMillis(100).getMillis()));
Thread.sleep(350);
assertEquals(MyJob.count, 3);
assertTrue(MyJob.jobKeys.contains(job1.getKey().getName()));
assertTrue(MyJob.jobKeys.contains(job2.getKey().getName()));
assertTrue(MyJob.jobKeys.contains(job3.getKey().getName()));
}
@Test(invocationCount = 3)
public void testScheduleOutOfOrder()
throws Exception {
JobDetail job1 = buildJob("Job1", DEFAULT_GROUP, MyJob.class);
scheduler.scheduleJob(job1, buildTrigger("key1", DEFAULT_GROUP, job1, DateTime.now().plusMillis(200).getMillis()));
Thread.sleep(5);
scheduler.scheduleJob(buildTrigger("key2", DEFAULT_GROUP, job1, DateTime.now().plusMillis(100).getMillis()));
Thread.sleep(5);
scheduler.scheduleJob(buildTrigger("key3", DEFAULT_GROUP, job1, DateTime.now().plusMillis(300).getMillis()));
Thread.sleep(5);
Thread.sleep(350);
assertEquals(MyJob.count, 3);
assertEquals(MyJob.triggerKeys.poll(), "key2");
assertEquals(MyJob.triggerKeys.poll(), "key1");
assertEquals(MyJob.triggerKeys.poll(), "key3");
}
@Test
public void testScheduleJobWithRepeatTime()
throws Exception {
JobDetail job1 = buildJob("Job1", DEFAULT_GROUP, MyJob.class);
final SimpleTriggerImpl o = (SimpleTriggerImpl) buildTrigger("key1", DEFAULT_GROUP, job1);
o.setRepeatInterval(100);
o.setRepeatCount(10);
scheduler.scheduleJob(job1, o);
Thread.sleep(750);
assertEquals(MyJob.count, 8);
assertEquals(MyJob.triggerKeys.poll(), "key1");
}
@Test
public void testScheduleJobWithRepeatTimeWithConcurrentExecutionDisallowed()
throws Exception {
JobDetail job1 = buildJob("CJob1", DEFAULT_GROUP, MyNoConcurrentJob.class);
final SimpleTriggerImpl o = (SimpleTriggerImpl) buildTrigger("Ckey1", DEFAULT_GROUP, job1);
o.setRepeatInterval(100);
o.setRepeatCount(10);
MyNoConcurrentJob.waitTime = 300;
scheduler.scheduleJob(job1, o);
Thread.sleep(850);
// since MyNoCocurrent job takes 300 ms to finish
assertEquals(MyNoConcurrentJob.count, 3);
assertEquals(MyNoConcurrentJob.triggerKeys.poll(), "Ckey1");
}
@Test
public void testScheduleJobWithRepeatTimeWithConcurrentExecutionDisallowed_withFastJob()
throws Exception {
JobDetail job1 = buildJob("CJob2", DEFAULT_GROUP, MyNoConcurrentJob.class);
final SimpleTriggerImpl o = (SimpleTriggerImpl) buildTrigger("Ckey2", DEFAULT_GROUP, job1);
o.setRepeatInterval(100);
o.setRepeatCount(10);
MyNoConcurrentJob.waitTime = 10;
scheduler.scheduleJob(job1, o);
Thread.sleep(750);
// since MyNoCocurrent job takes 300 ms to finish
assertEquals(MyNoConcurrentJob.count, 8);
assertEquals(MyNoConcurrentJob.triggerKeys.poll(), "Ckey2");
}
@Test
public void testBasicStorageFunctions()
throws Exception {
// test basic storage functions of scheduler...
JobDetail job = newJob()
.ofType(MyJob.class)
.withIdentity("j1")
.storeDurably()
.build();
assertFalse(scheduler.checkExists(jobKey("j1")), "Unexpected existence of job named 'j1'.");
scheduler.addJob(job, false);
assertTrue(scheduler.checkExists(jobKey("j1")),
"Expected existence of job named 'j1' but checkExists return false.");
job = scheduler.getJobDetail(jobKey("j1"));
assertNotNull("Stored job not found!", job);
scheduler.deleteJob(jobKey("j1"));
Trigger trigger = newTrigger()
.withIdentity("t1")
.forJob(job)
.startNow()
.withSchedule(simpleSchedule()
.repeatForever()
.withIntervalInSeconds(5))
.build();
assertFalse(scheduler.checkExists(triggerKey("t1")), "Unexpected existence of trigger named '11'.");
scheduler.scheduleJob(job, trigger);
//give time to hazelcast store the trigger
Thread.sleep(25);
assertTrue(scheduler.checkExists(triggerKey("t1")),
"Expected existence of trigger named 't1' but checkExists return false.");
job = scheduler.getJobDetail(jobKey("j1"));
assertNotNull("Stored job not found!", job);
trigger = scheduler.getTrigger(triggerKey("t1"));
assertNotNull("Stored trigger not found!", trigger);
job = newJob()
.ofType(MyJob.class)
.withIdentity("j2", "g1")
.build();
trigger = newTrigger()
.withIdentity("t2", "g1")
.forJob(job)
.startNow()
.withSchedule(simpleSchedule()
.repeatForever()
.withIntervalInSeconds(5))
.build();
scheduler.scheduleJob(job, trigger);
job = newJob()
.ofType(MyJob.class)
.withIdentity("j3", "g1")
.build();
trigger = newTrigger()
.withIdentity("t3", "g1")
.forJob(job)
.startNow()
.withSchedule(simpleSchedule()
.repeatForever()
.withIntervalInSeconds(5))
.build();
scheduler.scheduleJob(job, trigger);
//give time to hazelcast store the trigger
Thread.sleep(25);
List<String> jobGroups = scheduler.getJobGroupNames();
List<String> triggerGroups = scheduler.getTriggerGroupNames();
assertTrue(jobGroups.size() == 2, "Job group list size expected to be = 2 ");
assertTrue(triggerGroups.size() == 2, "Trigger group list size expected to be = 2 ");
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(JobKey.DEFAULT_GROUP));
Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(TriggerKey.DEFAULT_GROUP));
assertTrue(jobKeys.size() == 1, "Number of jobs expected in default group was 1 ");
assertTrue(triggerKeys.size() == 1, "Number of triggers expected in default group was 1 ");
jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals("g1"));
triggerKeys = scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals("g1"));
assertTrue(jobKeys.size() == 2, "Number of jobs expected in 'g1' group was 2 ");
assertTrue(triggerKeys.size() == 2, "Number of triggers expected in 'g1' group was 2 ");
Trigger.TriggerState s = scheduler.getTriggerState(triggerKey("t2", "g1"));
assertTrue(s.equals(Trigger.TriggerState.NORMAL), "State of trigger t2 expected to be NORMAL ");
scheduler.pauseTrigger(triggerKey("t2", "g1"));
s = scheduler.getTriggerState(triggerKey("t2", "g1"));
assertEquals(s, Trigger.TriggerState.PAUSED);
scheduler.resumeTrigger(triggerKey("t2", "g1"));
s = scheduler.getTriggerState(triggerKey("t2", "g1"));
assertTrue(s.equals(Trigger.TriggerState.NORMAL), "State of trigger t2 expected to be NORMAL ");
Set<String> pausedGroups = scheduler.getPausedTriggerGroups();
assertTrue(pausedGroups.isEmpty(), "Size of paused trigger groups list expected to be 0 ");
scheduler.pauseTriggers(GroupMatcher.triggerGroupEquals("g1"));
// test that adding a trigger to a paused group causes the new trigger to be paused also...
job = newJob()
.ofType(MyJob.class)
.withIdentity("j4", "g1")
.build();
trigger = newTrigger()
.withIdentity("t4", "g1")
.forJob(job)
.startNow()
.withSchedule(simpleSchedule()
.repeatForever()
.withIntervalInSeconds(5))
.build();
scheduler.scheduleJob(job, trigger);
//give time to hazelcast store the trigger
Thread.sleep(25);
pausedGroups = scheduler.getPausedTriggerGroups();
assertTrue(pausedGroups.size() == 1, "Size of paused trigger groups list expected to be 1 ");
s = scheduler.getTriggerState(triggerKey("t2", "g1"));
assertEquals(s, Trigger.TriggerState.PAUSED);
s = scheduler.getTriggerState(triggerKey("t4", "g1"));
assertEquals(s, Trigger.TriggerState.PAUSED);
scheduler.resumeTriggers(GroupMatcher.triggerGroupEquals("g1"));
s = scheduler.getTriggerState(triggerKey("t2", "g1"));
assertTrue(s.equals(Trigger.TriggerState.NORMAL), "State of trigger t2 expected to be NORMAL ");
s = scheduler.getTriggerState(triggerKey("t4", "g1"));
assertTrue(s.equals(Trigger.TriggerState.NORMAL), "State of trigger t4 expected to be NORMAL ");
pausedGroups = scheduler.getPausedTriggerGroups();
assertTrue(pausedGroups.isEmpty(), "Size of paused trigger groups list expected to be 0 ");
assertFalse(scheduler.unscheduleJob(triggerKey("foasldfksajdflk")),
"Scheduler should have returned 'false' from attempt to unschedule non-existing trigger. ");
assertTrue(scheduler.unscheduleJob(triggerKey("t3", "g1")),
"Scheduler should have returned 'true' from attempt to unschedule existing trigger. ");
jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals("g1"));
triggerKeys = scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals("g1"));
assertTrue(jobKeys.size() == 2, "Number of jobs expected in 'g1' group was 1 "); // job should have been deleted also, because it is non-durable
assertTrue(triggerKeys.size() == 2, "Number of triggers expected in 'g1' group was 1 ");
assertTrue(scheduler.unscheduleJob(triggerKey("t1")),
"Scheduler should have returned 'true' from attempt to unschedule existing trigger. ");
jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(JobKey.DEFAULT_GROUP));
triggerKeys = scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(TriggerKey.DEFAULT_GROUP));
assertTrue(jobKeys.size() == 1, "Number of jobs expected in default group was 1 "); // job should have been left in place, because it is non-durable
assertTrue(triggerKeys.isEmpty(), "Number of triggers expected in default group was 0 ");
}
@Test
public void testDurableStorageFunctions()
throws Exception {
// test basic storage functions of scheduler...
JobDetail job = newJob()
.ofType(MyJob.class)
.withIdentity("j1")
.storeDurably()
.build();
assertFalse(scheduler.checkExists(jobKey("j1")), "Unexpected existence of job named 'j1'.");
scheduler.addJob(job, false);
assertTrue(scheduler.checkExists(jobKey("j1")), "Unexpected non-existence of job named 'j1'.");
JobDetail nonDurableJob = newJob()
.ofType(MyJob.class)
.withIdentity("j2")
.build();
try {
scheduler.addJob(nonDurableJob, false);
fail("Storage of non-durable job should not have succeeded.");
} catch (SchedulerException expected) {
assertFalse(scheduler.checkExists(jobKey("j2")), "Unexpected existence of job named 'j2'.");
}
scheduler.addJob(nonDurableJob, false, true);
assertTrue(scheduler.checkExists(jobKey("j2")), "Unexpected non-existence of job named 'j2'.");
}
@Test
public void testShutdownWithSleepReturnsAfterAllThreadsAreStopped()
throws Exception {
Map<Thread, StackTraceElement[]> allThreadsStart = Thread.getAllStackTraces();
Thread.sleep(500L);
Map<Thread, StackTraceElement[]> allThreadsRunning = Thread.getAllStackTraces();
cleanUp();
Thread.sleep(1500L);
Map<Thread, StackTraceElement[]> allThreadsEnd = Thread.getAllStackTraces();
Set<Thread> endingThreads = new HashSet<>(allThreadsEnd.keySet());
// remove all pre-existing threads from the set
for (Thread t : allThreadsStart.keySet()) {
allThreadsEnd.remove(t);
}
// remove threads that are known artifacts of the test
for (Thread t : endingThreads) {
if (t.getName().contains("derby") && t.getThreadGroup().getName().contains("derby")) {
allThreadsEnd.remove(t);
}
if (t.getThreadGroup() != null && t.getThreadGroup().getName().equals("system")) {
allThreadsEnd.remove(t);
}
if (t.getThreadGroup() != null && t.getThreadGroup().getName().equals("main")) {
allThreadsEnd.remove(t);
}
}
if (allThreadsEnd.size() > 0) {
// log the additional threads
for (Thread t : allThreadsEnd.keySet()) {
System.out.println("*** Found additional thread: " + t.getName() + " (of type " + t.getClass().getName()
+ ") in group: " + t.getThreadGroup().getName() + " with parent group: "
+ (t.getThreadGroup().getParent() == null ? "-none-" : t.getThreadGroup().getParent().getName()));
}
// log all threads that were running before shutdown
for (Thread t : allThreadsRunning.keySet()) {
System.out.println("- Test runtime thread: "
+ t.getName()
+ " (of type "
+ t.getClass().getName()
+ ") in group: "
+ (t.getThreadGroup() == null ? "-none-" : (t.getThreadGroup().getName() + " with parent group: " + (t
.getThreadGroup().getParent() == null ? "-none-" : t.getThreadGroup().getParent().getName()))));
}
}
Thread.sleep(2000L);
assertTrue(allThreadsEnd.isEmpty(), "Found unexpected new threads (see console output for listing)");
}
@Test
public void testAbilityToFireImmediatelyWhenStartedBefore()
throws Exception {
List<Long> jobExecTimestamps = Collections.synchronizedList(new ArrayList<Long>());
CyclicBarrier barrier = new CyclicBarrier(2);
scheduler.getContext().put(BARRIER, barrier);
scheduler.getContext().put(DATE_STAMPS, jobExecTimestamps);
scheduler.start();
Thread.yield();
JobDetail job1 = JobBuilder.newJob(TestJobWithSync.class).withIdentity("job1").build();
Trigger trigger1 = TriggerBuilder.newTrigger().forJob(job1).build();
long sTime = System.currentTimeMillis();
scheduler.scheduleJob(job1, trigger1);
barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
long fTime = jobExecTimestamps.get(0);
assertTrue((fTime - sTime < 7000L), "Immediate trigger did not fire within a reasonable amount of time."); // This is dangerously subjective! but what else to do?
}
@Test
public void testAbilityToFireImmediatelyWhenStartedBeforeWithTriggerJob()
throws Exception {
List<Long> jobExecTimestamps = Collections.synchronizedList(new ArrayList<Long>());
CyclicBarrier barrier = new CyclicBarrier(2);
scheduler.getContext().put(BARRIER, barrier);
scheduler.getContext().put(DATE_STAMPS, jobExecTimestamps);
scheduler.start();
Thread.yield();
JobDetail job1 = JobBuilder.newJob(TestJobWithSync.class).withIdentity("job1").storeDurably().build();
scheduler.addJob(job1, false);
long sTime = System.currentTimeMillis();
scheduler.triggerJob(job1.getKey());
barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
long fTime = jobExecTimestamps.get(0);
assertTrue((fTime - sTime < 7000L), "Immediate trigger did not fire within a reasonable amount of time."); // This is dangerously subjective! but what else to do?
}
@Test
public void testAbilityToFireImmediatelyWhenStartedAfter()
throws Exception {
List<Long> jobExecTimestamps = Collections.synchronizedList(new ArrayList<Long>());
CyclicBarrier barrier = new CyclicBarrier(2);
scheduler.getContext().put(BARRIER, barrier);
scheduler.getContext().put(DATE_STAMPS, jobExecTimestamps);
JobDetail job1 = JobBuilder.newJob(TestJobWithSync.class).withIdentity("job1").build();
Trigger trigger1 = TriggerBuilder.newTrigger().forJob(job1).build();
long sTime = System.currentTimeMillis();
scheduler.scheduleJob(job1, trigger1);
scheduler.start();
barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
long fTime = jobExecTimestamps.get(0);
assertTrue((fTime - sTime < 7000L), "Immediate trigger did not fire within a reasonable amount of time."); // This is dangerously subjective! but what else to do?
}
@Test
public void testScheduleMultipleTriggersForAJob()
throws Exception {
JobDetail job = newJob(MyJob.class).withIdentity("job1", "group1").build();
Trigger trigger1 = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(
SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)
.repeatForever())
.build();
Trigger trigger2 = newTrigger()
.withIdentity("trigger2", "group1")
.startNow()
.withSchedule(
SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)
.repeatForever())
.build();
Set<Trigger> triggersForJob = new HashSet<Trigger>();
triggersForJob.add(trigger1);
triggersForJob.add(trigger2);
scheduler.scheduleJob(job, triggersForJob, true);
Thread.sleep(100);
List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(job.getKey());
assertEquals(triggersOfJob.size(), 2);
assertTrue(triggersOfJob.contains(trigger1));
assertTrue(triggersOfJob.contains(trigger2));
}
@Test
public void testShutdownWithoutWaitIsUnclean()
throws Exception {
CyclicBarrier barrier = new CyclicBarrier(2);
try {
scheduler.getContext().put(BARRIER, barrier);
scheduler.start();
scheduler.addJob(newJob().ofType(UncleanShutdownJob.class).withIdentity("job").storeDurably().build(), false);
scheduler.scheduleJob(newTrigger().forJob("job").startNow().build());
while (scheduler.getCurrentlyExecutingJobs().isEmpty()) {
Thread.sleep(50);
}
} finally {
scheduler.shutdown(false);
}
barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
Thread jobThread = (Thread) scheduler.getContext().get(JOB_THREAD);
jobThread.join(TimeUnit.SECONDS.toMillis(TEST_TIMEOUT_SECONDS));
}
public static class UncleanShutdownJob implements Job {
@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
try {
SchedulerContext schedulerContext = context.getScheduler().getContext();
schedulerContext.put(JOB_THREAD, Thread.currentThread());
CyclicBarrier barrier = (CyclicBarrier) schedulerContext.get(BARRIER);
barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (Throwable e) {
e.printStackTrace();
throw new AssertionError("Await on barrier was interrupted: " + e.toString());
}
}
}
@Test
public void testShutdownWithWaitIsClean()
throws Exception {
final AtomicBoolean shutdown = new AtomicBoolean(false);
List<Long> jobExecTimestamps = Collections.synchronizedList(new ArrayList<Long>());
CyclicBarrier barrier = new CyclicBarrier(2);
try {
scheduler.getContext().put(BARRIER, barrier);
scheduler.getContext().put(DATE_STAMPS, jobExecTimestamps);
scheduler.start();
scheduler.addJob(newJob().ofType(TestJobWithSync.class).withIdentity("job").storeDurably().build(), false);
scheduler.scheduleJob(newTrigger().forJob("job").startNow().build());
while (scheduler.getCurrentlyExecutingJobs().isEmpty()) {
Thread.sleep(50);
}
} finally {
Thread t = new Thread() {
@Override
public void run() {
try {
scheduler.shutdown(true);
shutdown.set(true);
} catch (SchedulerException ex) {
throw new RuntimeException(ex);
}
}
};
t.start();
Thread.sleep(1000);
assertFalse(shutdown.get());
barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
t.join();
}
}
public static final long TEST_TIMEOUT_SECONDS = 125;
public static class TestJobWithSync implements Job {
@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
try {
@SuppressWarnings("unchecked")
List<Long> jobExecTimestamps = (List<Long>) context.getScheduler().getContext().get(DATE_STAMPS);
CyclicBarrier barrier = (CyclicBarrier) context.getScheduler().getContext().get(BARRIER);
jobExecTimestamps.add(System.currentTimeMillis());
barrier.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (Throwable e) {
e.printStackTrace();
throw new AssertionError("Await on barrier was interrupted: " + e.toString());
}
}
}
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public static class TestAnnotatedJob implements Job {
@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
}
}
}