Can a Quartz Scheduler execute a Runnable?
For example, I have the following code being running by a spring TaskScheduler:
[...]
#Autowired
#Qualifier(IntegrationConfiguration.TASK_SCHEDULER_INTEGRATION_NAME)
private TaskScheduler taskScheduler;
[...]
ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(new Runnable() {
#Override
public void run() {
try {
execucaoJobService.executa(jobName, execucaoJobDto, jobScheduleId);
} catch (JobExecutionException e) {
LOG.error("Job Execution fails", e);
}
}
}, new CronTrigger(cronExpression));
[...]
I wanna do something like the above code with Quartz, I know there is QuartzJobBean class, but this one
only works with static code, and I need to pass the cronExpression and other params dynamic.
You could define a job that takes a Runnable via the JobDataMap, and run that on execution.
The job would look like this:
public final class RunnableJob implements Job {
public static final String RUNNABLE_KEY = "RUNNABLE_KEY";
public RunnableJob() {
// Explicit constructor is required by Quartz.
}
#Override
public void execute(JobExecutionContext jobExecutionContext) {
final var runnable = (Runnable) jobExecutionContext.getJobDetail().getJobDataMap().get(RUNNABLE_KEY);
runnable.run();
}
}
Where you schedule your job it would look something like this:
final var cronTrigger = TriggerBuilder.newTrigger()
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
final var jobDetail = JobBuilder.newJob(RunnableJob.class)
.setJobData(new JobDataMap(Map.of(RunnableJob.RUNNABLE_KEY,
(Runnable) () -> {
// Do whatever you want to do
})))
.build();
scheduler.scheduleJob(jobDetail, cronTrigger);
I found this code: QuartzScheduledExecutorService.java that helps me with this problem. maybe it can help someone else in the future.
Related
Quartz is creating new instance of the class through the JobBuilder each time
JobBuilder.newJob(MyJob.class)
However, I only want one MyJob instance, and only trigger testFunction from execute function, how can I make it work?
I find through QuartzGuiceLib I can use some annotations to make it happen, through Spring I can change something in configuration file. But how can I implement it by pure Java without any framwork?
Below is the code snapshot:
public class MyJob implements Job {
public MyJob() {
testFunction();
try {
final Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
if (!scheduler.checkExists(JOB_KEY)) {
triggerScheduler(scheduler);
} else {
log.info("Job with key {{}} already exist", JOB_KEY);
}
} catch (SchedulerException e) {
log.error("Fail to start scheduler", e);
}
}
public void testFunction() {
}
private void triggerScheduler(final Scheduler scheduler) throws SchedulerException {
final JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity(JOB_KEY)
.build();
final Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myjob")
.withSchedule(
simpleSchedule()
.withIntervalInSeconds(60)
.repeatForever())
.build();
scheduler.start();
log.info("Scheduling job with key {{}}", jobDetail.getKey());
scheduler.scheduleJob(jobDetail, trigger);
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
testFunction();
}
}
It might be easier to keep the job and scheduler in two separate classes as below:
public class MyQuartzScheduler {
public static void main( String[] args ) throws Exception {
JobDetail job = JobBuilder.newJob(MyJob.class)
.withIdentity("dummyJobName", "group1").build();
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("MyJobTrigger", "group1")
.withSchedule(
CronScheduleBuilder.cronSchedule("0 * * * * ?"))
.build();
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
scheduler.scheduleJob(job, trigger);
}
}
And then your Job Class:
public class MyJob implements Job {
public void testFunction() {
System.out.println("Running Test!");
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
testFunction();
}
}
This is an adaptation taken from an mkyong tutorial article found at:
https://www.mkyong.com/java/quartz-2-scheduler-tutorial/
For the answer to your question though, Quartz does create a new instance per run:
https://stackoverflow.com/a/10463309/1410671
You could make another static class or Factory that your Job class would call which would use the same instance every call.
public class MyJob implements Job {
public void testFunction() {
MyClassWithStaticCounter.increaseCounter(1);
System.out.println(MyClassWithStaticCounter.getCounter());
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
testFunction();
}
}
And your class that has the static stuff:
public class MyClassWithStaticCounter {
private static int counter = 0;
public static void increaseCounter(int i){
counter += i;
}
public static int getCounter(){
return counter;
}
}
I have a code like this for which I would like to write unit test.
public class TestClass {
private final Executor executor;
private final Handler handler;
TestClass(Executor executor, Handler handler) {
this.executor = executor;
this.handler = handler;
}
void doSomething(String param1) {
executor.execute(new Runnable() {
#Override
public void run() {
//do something
handler.callHandler();
}
});
}
}
How can I use Mockito / Powermockito to verify if the callHandler() method is invoked.
Pass a mock Handler to the constructor of TestClass.
Then use Mockito.verify() to assert that callHandler() method was called.
Involving concurrency
You can stub an answer that counts down on a CountDownLatch to make the test wait for the handler to be hit. Waiting will involve setting a reasonable timeout, which can be tricky, you don't want it too high, or failure will make the test run much longer, and not too low so that you don't get false positives.
Handler handler = mock(Handler.class);
CountDownLatch finished = new CountDownLatch(1);
doAnswer(invocation -> {
finished.countDown();
return null;
}).when(handler).callHandler();
TestClass testClass = new TestClass(executor, handler);
testClass.doSomething("thisThing");
boolean ended = finished.await(10, TimeUnit.SECONDS);
assertThat(ended).isTrue();
verify(handler).callHandler();
Bypassing concurrency
If you're only trying to determine whether the handler is invoked you can use an Executor that executes on the same thread. This will make for a more stable test.
Handler handler = mock(Handler.class);
Executor executor = new Executor() {
#Override
public void execute(Runnable command) {
command.run();
}
};
TestClass testClass = new TestClass(executor, handler);
testClass.doSomething("thisThing");
verify(handler).callHandler();
Another way you can handle the concurrency issue is to mock the Executor to "do nothing" when called and use an ArgumentCaptor in your test to capture the Runnable it would have invoked. Once you have the Runnable you can manually invoke it in the same thread as your test.
Here's an example:
#Mock
private Executor executor;
#Mock
private Handler handler;
#Before
public void setup() {
openMocks(this);
doNothing().when(executor).execute(any());
}
#Test
public void runTest() {
TestClass testClass = new TestClass(executor, handler);
testClass.doSomething("the thing");
ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
verify(executor).execute(runnable.capture());
Runnable capturedRunnable = runnable.getValue();
capturedRunnable.run();
verify(handler).callHandler();
}
I am using spring boot and have one async method. To execute async I have below configuration, questions is what if all those 5 thread hangs for some reason , essentially it will lock the application and none of new task will be executed (it will just keep accepting). How we can set timeout for those working thread , lets say 120 seconds, so after that it timesout and execute new task. (Yes I am using fixed thread pool with unbounded queue to keep accepting tasks)
#EnableAsync
#Configuration
public class AsyncConfiguration implements AsyncConfigurer {
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(5);
taskExecutor.initialize();
return taskExecutor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
You can create another executor like:
static class TimeOutExecutorService extends CompletableExecutors.DelegatingCompletableExecutorService {
private final Duration timeout;
private final ScheduledExecutorService schedulerExecutor;
TimeOutExecutorService(ExecutorService delegate, Duration timeout) {
super(delegate);
this.timeout = timeout;
schedulerExecutor = Executors.newScheduledThreadPool(1);
}
#Override public <T> CompletableFuture<T> submit(Callable<T> task) {
CompletableFuture<T> cf = new CompletableFuture<>();
Future<?> future = delegate.submit(() -> {
try {
cf.complete(task.call());
} catch (CancellationException e) {
cf.cancel(true);
} catch (Throwable ex) {
cf.completeExceptionally(ex);
}
});
schedulerExecutor.schedule(() -> {
if (!cf.isDone()) {
cf.completeExceptionally(new TimeoutException("Timeout after " + timeout));
future.cancel(true);
}
}, timeout.toMillis(), TimeUnit.MILLISECONDS);
return cf;
}
}
Then, create a new bean named timed
#Bean(name = "timed")
public Executor timeoutExecutor() {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("timed-%d").build();
return TimedCompletables.timed(Executors.newFixedThreadPool(10, threadFactory), Duration.ofSeconds(2));
}
And, try to use this Executor to execute your async tasks.
Or, try to change your code from FixSizeThreadPool to build a own thread pool executor.
You can not submit some task with timeout. What you can do is when you submit your task your get a Future object. You can keep this reference in some Map and pole and see if the task keeps running past your timeout. If so you can use the method cancel() of class Future.
Alternatively, your own task when it starts running places its own current thread into some Map visible to your main (submitting) thread. Also if you see that your task didn't finish in time (again poling) you can try and interrupt your thread. In either case your submitted task should be able to react to interrupt() method of Thread class. I actually implemented this alternative way. If you go this way, test a LOT... :)
I think Future.get(timeout, unit) method can manage async timeout.
Following example can work on my local.
#SpringBootApplication
#EnableScheduling
#EnableAsync
public class AsyncTimeoutExampleAppliation {
private final MyService myService;
public AsyncTimeoutExampleAppliation(MyService myService) {
this.myService = myService;
}
public static void main(String[] args) {
SpringApplication.run(AsyncTimeoutExampleAppliation.class, args);
}
#PostConstruct
void postConstract(){
asyncCall();
}
public void asyncCall(){
try {
String result = myService.doSomething()
.get(10, TimeUnit.SECONDS);
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
#Service
public static class MyService {
#Async
public Future<String> doSomething() throws InterruptedException {
TimeUnit.SECONDS.sleep(60);
return CompletableFuture.completedFuture("Finished");
}
}
}
We can get TimeoutException 10 seconds after application starts.
I have following method for test:
public class classToTest{
#Autowired
private Alternator alternator;
public void methodToTest(){
Thread t = new Thread(new Runnable() {
public void run() {
while(true) {
if(alternator.get()) {
System.out.print("Hello");
alternator.set(false);
}
}
}
};
t.start()
}
}
I need to check that was invoked method
alternator.set(false);
How can I do it?
Instead of starting a thread directly, can you pass in an "Executor" instance?
For example...
public class ClassToTest{
#Autowired
private Alternator alternator;
#Autowired #Qualifier("myExecutor")
private java.util.concurrent.Executor executor;
public void methodToTest() {
Runnable runnable = new Runnable() {
public void run() {
while(true) {
if(alternator.get()) {
System.out.print("Hello");
alternator.set(false);
}
}
};
executor.execute(runnable);
}
}
Now you can test this easier...
public class ClassToTestTest {
...
#Before
public void setup() {
alternator = mock(Alternator.class);
executor = mock(Executor.class);
obj = new ClassToTest();
ReflectionTestUtils.setField(obj, "alternator", alternator);
ReflectionTestUtils.setField(obj, "executor", executor);
}
#Test
public void shouldStartRunnable() {
obj.methodToTest();
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
verify(executor).execute(runnableCaptor.capture());
Runnable runnable = runnableCaptor.getValue();
// Now test your actual "runnable"...
when(alternator.get()).thenReturn(true);
runnable.run();
verify(alternator).set(false);
}
}
(Have not tried to compile this, so I apologise if there are any mistakes!!)
Though Bret's post of passing in an executor is very much recommended, you can use the timeout() mock verification setting to test for asynchronous conditions.
verify(alternator, timeout(500)).set(false);
Note that this will necessarily increase the flakiness of your test (i.e. the likelihood that the test fails when the code passes). With a sensible timeout value, that flakiness should be negligible, but if you're making this a part of your core test infrastructure you may consider refactoring to allow for synchronous execution in the test.
I'm using the below snippet in Spring to schedule job executions. A job is found by querying the database. Once a new job is found (which has not yet been scheduled), it is scheduled programmatically.
My question is, is this the way to create Runnables for task execution? Is it accessing taskScheduler in the right way or should it access it by fetching an instance from application context?
#Service
public class TimeOfDayScheduler {
#Autowired
private JobExecutor jobExecutor;
#Autowired
private TaskScheduler taskScheduler;
#Scheduled(cron = "${scheduler.interval}")
#PostConstruct
public void findNewJobs() {
// Checks the database if any new jobs should be scheduled
// ...
// If found:
scheduleJob(somejob);
// ...
}
public void scheduleJob(final Job job) {
Runnable task = new Runnable() {
#Override
public void run() {
try {
jobExecutor.execute(job);
} catch (Exception e) {
logger.error("Scheduler error", e);
}
}
};
taskScheduler.schedule(task, new CronTrigger(job.getCronPattern())
}
}