graceful shutdown 厨みたいになってますが、まあいいとして。。 embedded tomcat はちょっと見た感じ async request を綺麗に graceful shutdown する方法が見当たらなかった。
結局、各コントローラなりサーブレットなりで個々に graceful shutdown するのが良さそう。 例えば以下のように。
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import javax.annotation.PreDestroy;
import java.util.concurrent.*;
@SpringBootApplication
@Slf4j
public class SpringBootTomcatApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTomcatApplication.class, args);
}
@RestController
public static class MyController {
private final ExecutorService pool = Executors.newFixedThreadPool(10);
@GetMapping("/")
public String ok() {
return "OK";
}
@GetMapping("/sleep")
public DeferredResult<String> sleep() {
DeferredResult<String> objectDeferredResult = new DeferredResult<>();
pool.submit(() -> {
try {
log.info("Sleeping");
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("Send result");
objectDeferredResult.setResult("OK");
});
return objectDeferredResult;
}
/**
* Graceful shutdown.
*/
@PreDestroy
public void stop() {
log.info("STOP");
pool.shutdown();
try {
if (!pool.awaitTermination(7, TimeUnit.SECONDS)) {
log.info("shutdown failed");
pool.shutdownNow();
} else {
log.info("shutdown succeeded");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
jetty や undertow なら全体で処理できるからそれでもいいけど、まあ基本的には各コントローラでやるほうが綺麗かも。 あるいは処理が okhttp 等の場合だと、各 Service なりなんなりで @PreDestroy でやるのがいいかも。
【追記】
ThreadPoolTaskExecutor 使えばいいと @making さんと @kazuki43zoo さんから聞いたので、そうしてみたらすっきりしました。 (やってることは内部的には同じ)
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@SpringBootApplication
@Slf4j
public class SpringBootTomcatApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTomcatApplication.class, args);
}
@Bean(name = "async1")
public AsyncTaskExecutor mvcAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(10);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(7);
return executor;
}
@RestController
public static class MyController {
@Autowired
@Qualifier("async1")
AsyncTaskExecutor asyncTaskExecutor;
@GetMapping("/")
public String ok() {
return "OK";
}
@GetMapping("/sleep")
public DeferredResult<String> sleep() {
DeferredResult<String> objectDeferredResult = new DeferredResult<>();
asyncTaskExecutor.submit(() -> {
try {
log.info("Sleeping");
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("Send result");
objectDeferredResult.setResult("OK");
});
return objectDeferredResult;
}
}
}