Version Conventions:

  • SpringBoot 2.5.2
  • Spring 5.3.8
  • Embed Tomcat 9.0.48
  • Quartz 2.3.2

downtime

For JVMS, there are two kinds of outages, normal outages and forced outages

  • Normal downtime
    • All non-daemon threads exit
    • System.exit(0)
    • Ctrl + C
    • kill -15 (SIGTERM)
  • Forced outage
    • kill -9 (SIGKILL)
    • Runtime.halt()
    • Power off

When Tomcat is used, there is a non-daemon thread called container-0, which terminates after receiving the shutdown command and the JVM shuts down In addition, in SpringBoot SpringApplication a SpringApplicationShutdownHook registers a closed hook, ensure that the spring container is clean.

Runtime.getRuntime().addShutdownHook(new Thread(this."SpringApplicationShutdownHook"));
Copy the code

During Spring close calls lifecycleProcessor. OnClose (), it is necessary to understand the working process of the Lifecycle

Lifecycle

Spring introduced Lifecycle in 2.0, and SmartLifecycle in 3.0, which has more phase properties than Spring, specifying the order of startup and shutdown, and executing startup in the smallest order (integer.min_value to integer.max) _VALUE, Lifecycle will be in reverse order (from integer.max_value to integer.min_value) when Lifecycle is closed, and the phase defaults to 0 In addition, the latter has more callbacks to stop than the former. Stop threads do not have to wait for stops synchronously one by one, but can notify stop threads that they have finished with a callback method.

Take disabling stop as an example

private void stopBeans(a) {
   Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
   Map<Integer, LifecycleGroup> phases = new HashMap<>();
   Lifecycle ean (Lifecycle ean
   lifecycleBeans.forEach((beanName, bean) -> {
      int shutdownPhase = getPhase(bean);
      LifecycleGroup group = phases.get(shutdownPhase);
      if (group == null) {
         group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false);
         phases.put(shutdownPhase, group);
      }
      // Beans with the same phase belong to the same group
      group.add(beanName, bean);
   });
   if(! phases.isEmpty()) { List<Integer> keys =new ArrayList<>(phases.keySet());
      // Call stop in reverse order
      keys.sort(Collections.reverseOrder());
      for(Integer key : keys) { phases.get(key).stop(); }}}Copy the code

SmartLifecycle callback

Here is the code reading for the SmartLifecycle callback, counting the number of beans in the group that need to be stopped, calling one by one and waiting, or skipping until the timeout. The call to stop passes Runnable as a callback, and the implementer can run the shutdown logic with another thread, notifying Latch when it finishes

public void stop(a) {
   this.members.sort(Collections.reverseOrder());
   // Calculate how many beans in the group need to be stopped
   CountDownLatch latch = new CountDownLatch(this.smartMemberCount);
   Set<String> countDownBeanNames = Collections.synchronizedSet(new LinkedHashSet<>());
   Set<String> lifecycleBeanNames = new HashSet<>(this.lifecycleBeans.keySet());
   // Call 'dostop' one by one within the same group
   for (LifecycleGroupMember member : this.members) {
      if (lifecycleBeanNames.contains(member.name)) {
         doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames);
      }
      else if (member.bean instanceof SmartLifecycle) {
         // Already removed: must have been a dependent bean from another phaselatch.countDown(); }}// Wait for all bean stops to end, or skip until timeout
   try {
      latch.await(this.timeout, TimeUnit.MILLISECONDS);
      if (latch.getCount() > 0 && !countDownBeanNames.isEmpty() && logger.isInfoEnabled()) {
         logger.info("Failed to shut down " + countDownBeanNames.size() + " bean" +
               (countDownBeanNames.size() > 1 ? "s" : "") + " with phase value " +
               this.phase + " within timeout of " + this.timeout + "ms: "+ countDownBeanNames); }}}private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
      final CountDownLatch latch, final Set<String> countDownBeanNames) {

   Lifecycle bean = lifecycleBeans.remove(beanName);
   if(bean ! =null) {
       // Priority is given to dependent beans
      String[] dependentBeans = getBeanFactory().getDependentBeans(beanName);
      for (String dependentBean : dependentBeans) {
         doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames);
      }
      try {
         if (bean.isRunning()) {
            if (bean instanceof SmartLifecycle) {
               countDownBeanNames.add(beanName);
               // Call stop, passing Runnable as a callback
               The implementer can use another thread to run the shutdown logic and notify latch when it completes
               ((SmartLifecycle) bean).stop(() -> {
                  latch.countDown();
                  countDownBeanNames.remove(beanName);
               });
            }
            else{ bean.stop(); }}else if (bean instanceof SmartLifecycle) {
            // Don't wait for beans that aren't running...latch.countDown(); }}}}Copy the code

Tomcat graceful shutdown

SpringBoot from 2.3 support graceful shutdown features, is provided by WebServerGracefulShutdownLifecycle ability, relevant and WebServerStartStopLifecycle

WebServerGracefulShutdownLifecycle phase is an Integer. MAX_VALUE, And WebServerStartStopLifecycle is an Integer. MAX_VALUE – 1, according to the above analysis, in the closing stages WebServerGracefulShutdownLifecycle before WebServerStartStopLifecycle execution.

WebServerGracefulShutdownLifecycle

The delegate calls webServer’s Shutdownexceptionmethod, which causes a thread tomcat-shutdown to override the shutdown logic (Netty and Jetty are also exceptionaltriggers). Connector.pause () : this phase is terminated when the Context’s thread of business processing is terminated.

WebServerStartStopLifecycle

If within the effective time is not elegant and downtime, directly into the WebServerStartStopLifecycle, calling abort () to terminate elegant downtime, and close the Tomcat, including the destruction of related HTTP – nio thread – 8080 – exec – *, etc., (call interru on these threads Pt ()), the daemon thread mentioned in the beginning, container-0, also ends with stopAwait=true.

public void stop(a) throws WebServerException {
   synchronized (this.monitor) {
      boolean wasStarted = this.started;
      try {
         this.started = false;
         try {
            if (this.gracefulShutdown ! =null) {
                // Force a stop
               this.gracefulShutdown.abort();
            }
            // Disable Tomcat, including the destruction of related threads http-nio-8080-exec-* etc
            stopTomcat();
            this.tomcat.destroy();
         }
         catch (LifecycleException ex) {
            // swallow and continue}}catch (Exception ex) {
         throw new WebServerException("Unable to stop embedded Tomcat", ex);
      }
      finally {
         if(wasStarted) { containerCounter.decrementAndGet(); }}}}Copy the code

Quartz elegant stop

The core of Quartz under Spring management is SchedulerFactoryBean, Also implement SmartLifecycle, and DisposableBean, at the shutdown stage scheduler.standby(), then scheduler.shutdown()

@Override
public void stop(a) throws SchedulingException {
   if (this.scheduler ! =null) {
      try {
         this.scheduler.standby();
      }
      catch (SchedulerException ex) {
         throw new SchedulingException("Could not stop Quartz Scheduler", ex); }}}@Override
public void destroy(a) throws SchedulerException {
   if (this.scheduler ! =null) {
      logger.info("Shutting down Quartz Scheduler");
      this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); }}Copy the code

In fact,shutdown() also calls standby()

public void shutdown(boolean waitForJobsToComplete) {
    
    if(shuttingDown || closed) {
        return;
    }
    shuttingDown = true;
    // standby is also called in shutdown
    standby();

    // Disable the Scheduler thread, for example, quartzScheduler_QuartzSchedulerThread
    schedThread.halt(waitForJobsToComplete);
    
    notifySchedulerListenersShuttingdown();
    // Turn off the worker thread, for example quartzScheduler_Worker-*
    resources.getThreadPool().shutdown(waitForJobsToComplete);
Copy the code

Note that if you use Quartz’s default SimpleThreadPool, which defaults to non-daemons, waitForJobsToComplete or waitForJobsToComplete is set, all worker threads must be terminated before exiting the JVM(forcing a halt if there is an infinite loop). If the SimpleThreadPool is set to a daemon thread, it may cause the worker thread to stop and the Job or Trigger state to be abnormal. In this case, you can set waitForJobsToComplete=true to avoid this