0

I'm working on a Spring Boot application that accepts requests and processes them using an ExecutorService. Each task submitted to the ExecutorService is non-iterative, long-running and is non-interruptible (in essence it submits the request in turn to several other services through a messaging broker, blocks for each of them, and waits for a reply).

I'm trying to implement graceful shutdown of the application in such a way that the application waits for a specified time for the ExecutorService to complete all running tasks and then shuts down. If, after the specified time has lapsed, there are unfinished tasks, I want the application to log the IDs of the tasks.

The relevant code is:

@Service
public class Service {

  private ExecutorService es = Executors.newFixedThreadPool(10);

  public handle(MyTask task) {
    es.execute(task);
  }

  @PreDestroy
  void destroy() {
    es.shutdown();
    try {
      boolean terminated = es.awaitTermination(60, TimeUnit.SECONDS);
      if (!terminated) {
      // I want to somehow log the IDs of MyTask's that haven't finished processing
      es.shutdownNow();
    } catch (InterruptedException e) {
      // Should not get here in the existing implementation as MyTask's are non- 
      // interruptible
    }
  }

How do I achieve this? I am contemplating two options but cannot wrap my head around how to implement either of them.

  1. Wrap MyTask into another Runnable that would check in a loop for interruption and, if interrupted, log the id of MyTask. So in essence make MyTask interruptible. How can this be implemented?
  2. Save submitted MyTasks in a collection and on shutdown poll the collection for those that haven't completed and log their IDs. The question here is how do I keep the collection current and remove those MyTasks that have been completed?

To reiterate, I'm implementing graceful shutdown of the whole application, not just the executor service. So the entire app shuts down after the specified period has lapsed. Hence the need to log all tasks that haven't finished processing by the executor service.

John Allison
  • 966
  • 1
  • 10
  • 30
  • 1
    You can not make an uninterruptible task interruptible by wrapping it in something. The wrapper can only do things before or after the wrapped task, which solves nothing. But for a starter, you could start using the list returned by `shutdownNow()`, to handle those tasks that have not been started yet. If your tasks are uninterruptible, the already started tasks will eventually complete, so why bother logging them. – Holger Sep 14 '22 at 14:35
  • @Holger - yes, I do have the piece of code that gets the result of `shutdownNow()` and logs those tasks. What I need is to log tasks that have been started but not finished processing when the `ExecutorService` was shut down. – John Allison Sep 14 '22 at 14:36
  • 2
    When you track running tasks, you may want to use [`ExecutorCompletionService`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ExecutorCompletionService.html) to remove the completed. – Holger Sep 14 '22 at 14:39
  • @Holger - as for the second part of your comment, how come those tasks get completed upon application shutdown? A task in essence is submitting some data for processing to ten different services in turn. If I shut the service down when only five out of ten services finished their job, the task won't get completed. – John Allison Sep 14 '22 at 14:40
  • 1
    Your code only shuts down the executor service, not the application. – Holger Sep 14 '22 at 14:44
  • The question states that I'm implementing graceful shutdown of the application. So it's the whole application, not just the executor service. I updated the question to call that out. – John Allison Sep 14 '22 at 14:46
  • 1
    Well yes, if the shutdown implies forcefully terminating the JVM, the tasks may not complete. On the other hand, they still may complete between the logging and the termination. For the logging, there is no way around tracking them. Either, the already mentioned completion service or you wrap the task inside a [`FutureTask`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/FutureTask.html) with overridden [`done()` method](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/FutureTask.html#done()). – Holger Sep 14 '22 at 14:53

1 Answers1

0

I think I might have found an option based on this post: implement an executor service that puts the task into a map and removes it once it's finished processing. Upon shutdown, we iterate over the map and log the tasks that are still there. Some of them might complete between logging and shutting the app down but that false positive is acceptable.

John Allison
  • 966
  • 1
  • 10
  • 30