Currently, ExecutorConfigurationSupport#shutdown() encapsulate multiple scenarios for shutting down internal ExecutorService.

The single method shutdown() performs shutdown()/shutdownNow()(non-blocking) and awaitTermination()(blocking) based on its property.

I am writing a graceful shutdown logic for task executor/scheduler. The logic for graceful shutdown is to retrieve all task executor/schedulers and apply: - Call shutdown() to not accept anymore tasks - Wait currently running task for the duration of graceful period

With current available API, I need to do following:

Instant deadline = start.plus(gracefulShutdownTimeout);

// stop receiving anymore request while keep running active ones
for (ExecutorConfigurationSupport executorConfigurationSupport : this.executorConfigurationSupports) {
    executorConfigurationSupport.setWaitForTasksToCompleteOnShutdown(true);
    executorConfigurationSupport.shutdown(); // non-blocking
}

// Previously, executors are called "shutdown()"; so, no more new tasks are scheduled.
// Now, call "awaitTermination()" to wait current tasks to finish while
// the container is shutting down in parallel.
for (ExecutorConfigurationSupport executorConfigurationSupport : this.executorConfigurationSupports) {
    int awaitTerminationSeconds = Math.toIntExact(Duration.between(Instant.now(), deadline).getSeconds());
    executorConfigurationSupport.setAwaitTerminationSeconds(awaitTerminationSeconds);
    executorConfigurationSupport.shutdown();  // blocking
}

Since this calls shutdown() twice with different parameter in order to achieve shutdown() and awaitTermination() for underlying executor, it is not so intuitive. Also requires to know the detail about what ExecutorConfigurationSupport#shutdown() does.

Another workaround is to retrieve internal ExecutorService and call shutdown() and awaitTermination().

List<ExecutorService> executorServices = new ArrayList<>();

for (ExecutorConfigurationSupport executorConfigurationSupport : this.executorConfigurationSupports) {
    if (executorConfigurationSupport instanceof ThreadPoolTaskExecutor) {
        executorServices.add(((ThreadPoolTaskExecutor)executorConfigurationSupport).getThreadPoolExecutor());
    }
    else if (executorConfigurationSupport instanceof ThreadPoolTaskScheduler) {
        executorServices.add(((ThreadPoolTaskScheduler)executorConfigurationSupport).getScheduledExecutor());
    }
}

for(ExecutorService executorService : executorServices) {
    executorService.shutdown();
}

for(ExecutorService executorService : executorServices) {
    executorService.awaitTermination(...);
}

I think it would be nice to have some API refinement for task executor/scheduler to easily control underlying ExecutorService.

Simple solution is to to add a getter to ExecutorConfigurationSupport to expose internal ExecutorService. This way, in addition to existing shutdown(), if user needs to do more fine control on shutdown, getter can expose the ExecutorService. Another way is to provide blocking(awaitTermination) and non-blocking(shutdown/shutdownNow) methods on ExecutorConfigurationSupport instead or in addition to the current shutdown() method.

Comment From: jgslima

I can see this issue is related to other issue I created: #27090

Comment From: jhoeller

Along with #27090, we've introduced an initiateShutdown() method with non-blocking semantics which is what we trigger on ContextClosedEvent now but which can also be publicly called in scenarios like above, as an early signal before a full shutdown() step. That said, in standard application context scenarios, the implicit triggering from ContextClosedEvent should lead to a graceful shutdown already, delivering a round of early shutdown signals to the executors/schedulers before the lifecycle stop phase and ultimately a round of full shutdown() calls on the same executors/schedulers for actual destruction.