1@Configuration
2public class GracefulShutdownConfig {
3
4 private static final Logger log = LoggerFactory.getLogger(GracefulShutdownConfig.class);
5 private final AtomicBoolean shuttingDown = new AtomicBoolean(false);
6 private final AtomicLong activeRequests = new AtomicLong(0);
7
8 @EventListener(ContextClosedEvent.class)
9 public void onShutdown() {
10 log.info("Shutdown signal received");
11 shuttingDown.set(true);
12
13
14 try {
15 log.info("Waiting 15s for LB deregistration");
16 Thread.sleep(15_000);
17 } catch (InterruptedException e) {
18 Thread.currentThread().interrupt();
19 }
20
21
22 long deadline = System.currentTimeMillis() + 30_000;
23 while (activeRequests.get() > 0 && System.currentTimeMillis() < deadline) {
24 try {
25 Thread.sleep(100);
26 } catch (InterruptedException e) {
27 Thread.currentThread().interrupt();
28 break;
29 }
30 }
31
32 log.info("Shutdown complete. Remaining active requests: {}", activeRequests.get());
33 }
34
35 @Bean
36 public FilterRegistrationBean<ShutdownFilter> shutdownFilter() {
37 FilterRegistrationBean<ShutdownFilter> bean = new FilterRegistrationBean<>();
38 bean.setFilter(new ShutdownFilter(shuttingDown, activeRequests));
39 bean.addUrlPatterns("/*");
40 bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
41 return bean;
42 }
43}
44
45public class ShutdownFilter extends OncePerRequestFilter {
46
47 private final AtomicBoolean shuttingDown;
48 private final AtomicLong activeRequests;
49
50 public ShutdownFilter(AtomicBoolean shuttingDown, AtomicLong activeRequests) {
51 this.shuttingDown = shuttingDown;
52 this.activeRequests = activeRequests;
53 }
54
55 @Override
56 protected void doFilterInternal(
57 HttpServletRequest request,
58 HttpServletResponse response,
59 FilterChain chain) throws ServletException, IOException {
60
61
62 if (request.getRequestURI().startsWith("/actuator")) {
63 chain.doFilter(request, response);
64 return;
65 }
66
67 if (shuttingDown.get()) {
68 response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
69 response.setHeader("Connection", "close");
70 response.setHeader("Retry-After", "5");
71 response.getWriter().write("{\"error\":\"Service shutting down\"}");
72 return;
73 }
74
75 activeRequests.incrementAndGet();
76 try {
77 chain.doFilter(request, response);
78 } finally {
79 activeRequests.decrementAndGet();
80 }
81 }
82}
83