18

I am developing a Spring Boot application backed by embedded Tomcat and I need to develop a graceful shutdown with the following steps:

  1. stop processing new HTTP requests (stop web container)
  2. process all already accepted requests
  3. shutdown Spring ApplicationContext

*do the steps above sequentially (one by one)

How can I achieve this?

P.S. Spring Boot 1.5.20.RELEASE, Java 8

Matthew I.
  • 1,793
  • 2
  • 10
  • 21

3 Answers3

16

Graceful shutdown support was added in Spring Boot 2.3 (release in May, 2020). This allows active requests to complete before closing the context, and shutting down container.

When graceful shutdown is enabled, application will perform following steps sequentially upon shutdown:

  • stop accepting new requests
  • will wait for some configurable time to process already accepted requests
  • stop container
  • stop embedded server

From release notes:

Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. When enabled using server.shutdown=graceful, upon shutdown, the web server will no longer permit new requests and will wait for a grace period for active requests to complete. The grace period can be configured using spring.lifecycle.timeout-per-shutdown-phase.


  • To enable graceful shutdown, add server.shutdown=graceful to properties (by default it is set to immediate).
  • Grace period can be configured using spring.lifecycle.timeout-per-shutdown-phase property (example: spring.lifecycle.timeout-per-shutdown-phase=1m.

For Spring Boot < 2.3, you'll take to tinker with server's connector to stop accepting new requests as explained in this Spring GitHub issue.

narendra-choudhary
  • 4,582
  • 4
  • 38
  • 58
12

I have ended up with:

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;

public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {

  private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
  private volatile Connector connector;

  @Override
  public void customize(Connector connector) {
    this.connector = connector;
  }

  @Override
  public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
    log.info("Protocol handler is shutting down");

    this.connector.pause();
    Executor executor = this.connector.getProtocolHandler().getExecutor();
    if (executor instanceof ThreadPoolExecutor) {
      try {
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
        threadPoolExecutor.shutdown();

        if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS))
          log.warn("Tomcat thread pool did not shut down gracefully within 30 seconds. Proceeding with forceful shutdown");
        else
          log.info("Protocol handler shut down");

      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }
}

some more additional beans:

import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
...
  @Bean
  public GracefulShutdown gracefulShutdown() {
    return new GracefulShutdown();
  }

  @Bean
  public EmbeddedServletContainerFactory servletContainer(final GracefulShutdown gracefulShutdown) {
    TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
    factory.addConnectorCustomizers(gracefulShutdown);
    return factory;
  }
...
Matthew I.
  • 1,793
  • 2
  • 10
  • 21
  • 3
    Blog with a "similar" solution: https://blog.marcosbarbero.com/graceful-shutdown-spring-boot-apps/ And this is the link to spring boot issue for a graceful shutdown of servlet container: https://github.com/spring-projects/spring-boot/issues/4657 – Denis Aug 01 '19 at 08:39
  • 2
    Looking at this solution, my understanding is that it will force a wait of 30 secs after the shutdown has been asked and only after 30 secs, do a graceful shutdown. But the OP wants to stop requests to come in also. How can that be achieved by your solution? – old_soul_on_the_run Sep 21 '20 at 03:39
  • Is this possible to do in a spring mvc webapp, without spring-boot? – Antonio E. Apr 29 '21 at 15:19
1

Its Simple, Spring boot itself provide feature. https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-exit

void shoutdown(){
       System.out.println("====+= Shoutdown +++===");
       System.exit(SpringApplication.exit(apc, this.exitCodeGenerator()));
      
   }

You can see in output, all current threads being closed. Output:

====+= Shoutdown +++===

2020-06-09 11:21:45,543
DEBUG[main][c.u.j.c.EnableEncryptablePropertiesBeanFactoryPostProcessor]
Application Event Raised: ExitCodeEvent  2020-06-09 11:21:45,543
DEBUG[main][c.u.j.c.EnableEncryptablePropertiesBeanFactoryPostProcessor]
Application Event Raised: ExitCodeEvent  2020-06-09 11:21:45,546
DEBUG[main][c.u.j.c.EnableEncryptablePropertiesBeanFactoryPostProcessor]
Application Event Raised: ContextClosedEvent  2020-06-09 11:21:45,546
DEBUG[main][c.u.j.c.EnableEncryptablePropertiesBeanFactoryPostProcessor]
Application Event Raised: ContextClosedEvent  2020-06-09 11:21:45,547
INFO [main][o.a.kafka.clients.producer.KafkaProducer] [Producer
clientId=producer-1] Closing the Kafka producer with timeoutMillis =
30000 ms.  2020-06-09 11:21:45,548
DEBUG[kafka-producer-network-thread |
producer-1][o.a.k.clients.producer.internals.Sender] [Producer
clientId=producer-1] Beginning shutdown of Kafka producer I/O thread,
sending remaining records.  2020-06-09 11:21:45,551
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name connections-closed:  2020-06-09 11:21:45,554
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name connections-created:  2020-06-09 11:21:45,554
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name successful-authentication:  2020-06-09 11:21:45,558
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name failed-authentication:  2020-06-09 11:21:45,558
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name bytes-sent-received:  2020-06-09 11:21:45,559
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name bytes-sent:  2020-06-09 11:21:45,559
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name bytes-received:  2020-06-09 11:21:45,560
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name select-time:  2020-06-09 11:21:45,561
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name io-time:  2020-06-09 11:21:45,570
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node--1.bytes-sent  2020-06-09 11:21:45,570
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node--1.bytes-received  2020-06-09 11:21:45,570
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node--1.latency  2020-06-09 11:21:45,571
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node-0.bytes-sent  2020-06-09 11:21:45,571
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node-0.bytes-received  2020-06-09 11:21:45,573
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node-0.latency  2020-06-09 11:21:45,573
DEBUG[kafka-producer-network-thread |
producer-1][o.a.k.clients.producer.internals.Sender] [Producer
clientId=producer-1] Shutdown of Kafka producer I/O thread has
completed.  2020-06-09 11:21:45,607
DEBUG[main][o.a.kafka.clients.producer.KafkaProducer] [Producer
clientId=producer-1] Kafka producer has been closed  2020-06-09
11:21:45,611 DEBUG[main][o.hibernate.internal.SessionFactoryImpl]
HHH000031: Closing  2020-06-09 11:21:45,611
DEBUG[main][o.h.type.spi.TypeConfiguration$Scope] Un-scoping
TypeConfiguration
[org.hibernate.type.spi.TypeConfiguration$Scope@5dfd31f4] from
SessionFactory [org.hibernate.internal.SessionFactoryImpl@62a54948] 
2020-06-09 11:21:45,612
DEBUG[main][o.h.s.i.AbstractServiceRegistryImpl] Implicitly
destroying ServiceRegistry on de-registration of all child
ServiceRegistries  2020-06-09 11:21:45,613
DEBUG[main][o.h.b.r.i.BootstrapServiceRegistryImpl] Implicitly
destroying Boot-strap registry on de-registration of all child
ServiceRegistries  2020-06-09 11:21:45,613 INFO
[main][com.zaxxer.hikari.HikariDataSource] HikariPool-1 - Shutdown
initiated...  2020-06-09 11:21:45,754 INFO
[main][com.zaxxer.hikari.HikariDataSource] HikariPool-1 - Shutdown
completed.
narendra-choudhary
  • 4,582
  • 4
  • 38
  • 58