Java schedule a job to run at certain time for maximum period of time
Asked Answered
D

2

2

I'm trying to schedule a task that should run every 10 seconds. However this task should have a dynamical allowed period execution time. In other words, if the max allowed time is 5 seconds and if the task runs for more than 5 secs it should be killed/shutdown.

I have tried to use @Schedule with a cron time but no matter what I try I can't kill once it is running. However, an advice was given to me to not use the @Schedule and create a normal task with a ScheduledExecutorService but I have no idea how to do it.

My original method looks like that:

    @Scheduled(cron = "0/10 * * * * ?")
    @Transactional
    public void testMethod(Integer period) {
        ThreadPoolTaskScheduler scheduler;

        scheduler.setAwaitTerminationSeconds(period);
        scheduler.setWaitForTasksToCompleteOnShutdown(false);

        importantMethod();
        
    }

I have tried to rewrite it like so:

public void testMethod(){
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);            
  scheduler.scheduleAtFixedRate(importantMethod(), delay,
                                  period, TimeUnit.SECONDS);
}

However, I'm not sure how to set it up to have no delay to run every 10 seconds or every 5 mins and to only shut it down after the maximum allowed time is exceeded.

Any help will be greatly appreciated.

Dena answered 5/2, 2022 at 13:14 Comment(0)
X
1

As per the description, my assumptions:

  1. Task should run every 10 seconds
  2. Task should have maximum time-out of 5 seconds
  3. Max time-out should be configurable

configuration in application.properties

scheduler.timeout = 5000

Code


    @Value("${scheduler.timeout}")
    private Long timeout;
    
    @Scheduled(cron = "0/10 * * * * ?")
    public void taskScheduler() {
        System.out.println("Task Started: " + LocalDateTime.now());
        this.executeTask((Callable<Object>) () -> {
            TimeUnit.MILLISECONDS.sleep(3000);//Change values for testing: sleep time
            System.out.println("Task Worked: " + LocalDateTime.now());
            return true;
        }, timeout);
    }
    
    ExecutorService service = Executors.newFixedThreadPool(1);
    ScheduledExecutorService canceller = Executors.newSingleThreadScheduledExecutor();
    
    public <T> Future<T> executeTask(Callable<T> c, long timeoutMS) {
        final Future<T> future = service.submit(c);
        canceller.schedule(() -> {
            if (!future.isDone())
                System.out.println("Task Cancelled: " + LocalDateTime.now());
            future.cancel(true);
            return "done";
        }, timeoutMS, TimeUnit.MILLISECONDS);
        return future;
    }

Output
When task completed in 3 seconds (sleep time)

Task Started: 2022-02-06T22:42:30.010704200
Task Worked: 2022-02-06T22:42:33.014775100

When task completes in 6 seconds (sleep time)

Task Started: 2022-02-06T22:47:30.003249500
Task Cancelled: 2022-02-06T22:47:35.013195900
Xanthic answered 6/2, 2022 at 17:18 Comment(0)
H
0

I am not sure if this is the best way to do it, but here is a working way i came up with.

You would need three types of tasks:

  1. the task itself, which has to run periodically - the important task
  2. another task which will cancel it's execution, i'll call it kill task from now on.
  3. task to submit the important task for execution - the submit task, this is what you will schedule with the ScheduledExecutorService to run periodically

The idea is, when your important task starts execution, it will schedule the kill task to run 5 seconds later. In order to achieve that, it needs to know its' Future instance. And for that, it needs the submit task to do the submission and let it know of its' Future.

Here is important task:

import java.time.LocalTime;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ImportantTask implements Runnable {

    //that's just to make it easier to track what happens with each task
    private static long NEXT_ID = 1;

    //needed to schedule kill task
    private final ScheduledExecutorService executorService;
    private final long taskId;
    private Future<?> thisTaskFuture;
    //only needed to know when to cancel all tasks and end test
    private CountDownLatch countDownLatch;

    public ImportantTask(ScheduledExecutorService executorService) {
        this.executorService = executorService;
        this.taskId = NEXT_ID++;
    }

    @Override
    public void run() {
        //make half the tasks take too long
        long executionTimeSeconds = this.taskId % 2 == 0 ? 6 : 4;
        System.out.println(this.getLogMessage("started"));

        CancelFutureTask cancelFutureTask = new CancelFutureTask(this.thisTaskFuture);
        //this schedules the kill task for single execution, after 5 seconds delay
        this.executorService.schedule(cancelFutureTask, 5, TimeUnit.SECONDS);
        try {
            //simulating long execution
            Thread.sleep(executionTimeSeconds * 1_000);
        } catch (InterruptedException exc) {
            System.out.println(this.getLogMessage("interrupted"));
            //might need to check Thread.currentThread().isInterrupted(), depends on your exact case
            return;
        } finally {
            this.countDownLatch.countDown();
        }
        //the task is already finished, even if cancel is called, it has no effect
        System.out.println(this.getLogMessage("finished"));
    }

    private String getLogMessage(String taskStatus) {
        return String.format("Task with id - %d, %s at %s", this.taskId, taskStatus, LocalTime.now());
    }

    public void setThisTaskFuture(Future<?> thisTaskFuture) {
        this.thisTaskFuture = thisTaskFuture;
    }

    public void setCountDownLatch(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
}

The CountDownLatch is here only to make it easier to stop main method after few executions, you probably do not need it. Take note of the comments in the code.

The kill task is quite simple:

import java.util.concurrent.Future;

public class CancelFutureTask implements Runnable {

    private final Future<?> future;
    private final boolean mayInterrupt;

    public CancelFutureTask(Future<?> future, boolean mayInterrupt) {
        this.future = future;
        this.mayInterrupt = mayInterrupt;
    }

    public CancelFutureTask(Future<?> future) {
        this(future, true);
    }

    @Override
    public void run() {
        this.future.cancel(this.mayInterrupt);
    }
}

It just cancels a Future and will have no effect if the future has already finished execution.

And submit task:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;

public class SubmitNewImportantTask implements Runnable {

    private final ScheduledExecutorService executorService;
    private final CountDownLatch countDownLatch;

    public SubmitNewImportantTask(ScheduledExecutorService executorService, CountDownLatch countDownLatch) {
        this.executorService = executorService;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        ImportantTask importantTask = new ImportantTask(this.executorService);
        //obtain Future instance
        Future<?> future = this.executorService.submit(importantTask);
        //let the task know of its Future instance
        importantTask.setThisTaskFuture(future);
        importantTask.setCountDownLatch(this.countDownLatch);
    }
}

It submits important task for execution, obtains the Future instance and sets it in important task.

And the main method i used for testing.

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class FuturesMain {

    public static void main(String[] args) throws Exception {
        CountDownLatch count = new CountDownLatch(6);
        ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(10);
        SubmitNewImportantTask submitTask = new SubmitNewImportantTask(executorService, count);
        executorService.scheduleAtFixedRate(submitTask, 0, 10, TimeUnit.SECONDS);
        count.await();
        executorService.shutdown();
        boolean allTerminated = executorService.awaitTermination(10, TimeUnit.SECONDS);
        System.out.println("All terminated - " + allTerminated);
    }
}
Heller answered 6/2, 2022 at 13:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.