shutting down ExecutorService in a spring boot Rest API

Viewed 7275

Am building a spring boot rest api application deployed on weblogic 12c. One of my requirement is to run some long running tasks on every incoming request. An incoming rest request could result into multiple asynchronous task executions.

Since I dont care for the response and nor any exceptions that will result from these tasks I chose to use the ExecutorService and not Callable or CompletableFuture.

ExecutorService executorService =
  Executors.newFixedThreadPool(2, new CustomizableThreadFactory("-abc-"));

Then for the incoming request that I receive in controller run two for loops and assign those tasks to the ExecutorService:

for (final String orderId : orderIds) {
        for (final String itemId : itemIds) {               
            exec.execute(new Runnable() {
                public void run() { 
                    try {           
                         //call database operation
                    }catch(Throwable t) {
                       logger.error("EXCEPTION with {} , {}" ,orderId,itemId 
                    )
                }   
            });
        }//for          
    }//for

My question is regarding shutting down of the ExecutorService. I am aware about graceful shutdown ( shutdown ) a hybrid shutdown ( awaitTermination ) or an abrupt shutdown ( shutdownNow )

what would be the preferred approach between the three for a rest api application ?

also is there any limit on how many thread pools can get created viz a viz as the number of ExecutorService thread pools getting created will be driven by the number of incoming requests

2 Answers

We currently have similar requirements, this is a difficult problem to solve as you want to use the right hammer if you will. There are very heavy weight solutions to orchestrating long running processes, for example SpringBatch.

Firstly though don't bother stop and starting the ExecutorService. The whole point of that class is to take the burden of Thread management off your hands, so you don't need to create and stop Threads yourself. So you don't need to manage the manager.

But be careful with your approach. Without using queues or another load balancing technique to smartly balance the long running processes across instances in your app. Or managing what happens when a Thread dies, you may get into a world of trouble. In general I would say nowadays it doesn't make much sense to interact directly with Threads or ThreadPools, and to use higher level solutions for this type of problem.

awaitTermination is usually a bit safer, while shutdownNow is more forceful. It's usually a good idea to use awaitTermination in a functional method, or even a runnable, if you would like the executor to shut down as soon as possible, but only after it has completed doing everything that it was created to do. In other words, when there are no active tasks that the executor is executing. Ex.)

ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors);
Observable.of(items).schedule(Schedulers.from(executor)).flatMap(item -> {
   ... // this block represents a task that the executor will execute in a worker thread
}).onSubscribe(onNext -> 
   logItem(onNext), throwable -> 
   throwable.printStackTrace(), /* onComplete */ () -> 
   executor.awaitTermination(60, TimeUnit.Seconds)
);
... // you need to shutdown asap because these other methods below are also doing some computation/io-intensive stuff

Now, when this method is finished, it will call awaitTermination, which will either close the pool immediately if it is not executing any tasks, or wait up to 60 seconds if tasks are still being executed.

Threads, or workers, will cease to be active for 60 seconds of inactivity in most cases, since that is usually the default.

On the other hand, if you want tasks to stop executing as soon as (to give some examples) an exception is thrown, there was a breach in security, or another module/service has failed, you might want to use shutdownNow() to stop all tasks immediately without the option of waiting.

My advice for choosing between the two would be to use shutdownNow in you catch block if you do not want tasks to continue to be executed if there is an exception - i.e., there is no longer a reason to return the list of items to the client given that one of the items did not get added to the list. Otherwise, I'd recommend using awaitTermination after your try-catch, set to one minute, to safely shut down the thread pool as soon as it has executed all the tasks you have given it. But only do that if you know that the executor will not responsible for executing any more tasks down the line.

The simple shutdown, if that is an option for you, is also a good method. shutdown will reject all incoming tasks but wait until current tasks are finished executing, according to the Oracle docs.

If your not sure when you need to close the executor, it might be a good idea to use an @PreDestroy method so that the executor will just before the destroy method has been called on your bean:

@PreDestroy
private void cleanup(){
   executor.shutdown();
}
Related