Akash jain
Akash jain

Reputation: 199

Run a script for a specified period of time in Java

I have a java code which I want to run. If the job does not complete within, say, 2 hours, then it should be killed automatically (basically, some kind of timed batch).

How to achieve this in Java?

Upvotes: 2

Views: 420

Answers (1)

miiiii
miiiii

Reputation: 1610

If you are on Java 9 or higher, you can do the timeout batching as below:-

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(this::longRunningTask)
                       .orTimeout(2, TimeUnit.SECONDS); 
 future.get(); // j.u.c.ExecutionException after waiting for 2 second

If it completes within the timeout limit, it will return the value (here an Integer object in response to future.get() method)

And, this batching is asynchronous (If you don't call get method explicitly.)

NOTE: This does not prevent the thread from completing the task, it just completes a future in main thread with a Timeout Exception so that main thread can continue. The background task/thread is still continues to finish. (look @Andreas comment)

Some Samples:-

final CompletableFuture<Void> future =
                   CompletableFuture.supplyAsync(this::longRunningTask)
                                    .orTimeout(2, TimeUnit.SECONDS);

future.get(); // j.u.c.ExecutionException after waiting for 2 second

And the longRunningTask() :-

private Void longRunningTask(){
    log.info("Thread name" + Thread.currentThread().getName());
    try {
        log.info("Going to sleep for 10 sec...");
        Thread.sleep(10*1000);
        log.info("Sleep Completed. Task Completed.");
    } catch (InterruptedException e) {
        log.info("Exception Occurred");
    }
    finally{
        log.info("Final Cleanup in progress.");
    }
    log.info("Finishing the long task.");
    return null;
}

If you run above code, it will give Execution Exception in main thread (where future.get() is called) but the longRunningTask will still print Sleep Completed. Task Completed. after completing 10 sec sleep.

If you carefully notice, the longRunnigThread is never interrupted (does not enter in catch block) so continues normally, but main thread gets an exception on get().

Workaround/Solution:

Use ExecutorService and submit the longRunnigTask future with this Exceutor, if timeout occurs, shutdown the executor or else, shutdown after successful get() in case of no timeout exception.

Sample:

    ExecutorService myWorkers = Executors.newFixedThreadPool(1);

    final CompletableFuture<Void> longTask = 
            CompletableFuture.supplyAsync(this::longRunningTask, myWorkers)
                .orTimeout(2, TimeUnit.SECONDS);

    try {
        longTask.get();
    } catch (InterruptedException | ExecutionException e) {
        log.info("EE... Kill the executor thread/s.");
        myWorkers.shutdownNow(); // this will interrupt the thread, catch the IntrExcep in thread and return the execution there
    }

and the slightly modified longRunnigTask

private Void longRunningTask(){
    log.info("Thread name" + Thread.currentThread().getName());
    try {
        log.info("Going to sleep for 10 sec...");
        Thread.sleep(10*1000);
        log.info("Sleep Completed. Task Completed.");
    } catch (InterruptedException e) {
        log.info("Exception Occurred");
        return null; // this will finish the thread exce releasing all locking resources. Can be GCed then.
    }
    finally{
        log.info("Final Cleanup in progress.");
    }
    log.info("Finishing the long task.");
    return null;
}

With this approach, it won't complete the task inf timeout is occurred (you won't see Sleep completed. Task completed. in logs..), and would see, exception occurred in the longRunningTask thread (because of interrupt caused by myWorker.shutdown).

Upvotes: 4

Related Questions