What is graceful shutdown

To put it simply, after sending a stop command to an application process, the ongoing service operations are not affected.

After the application receives the stop instruction, it should stop receiving access requests, wait for the received requests to complete and return successfully, and then actually stop the application.

In terms of the Java language ecology, the underlying technology is supported so that we can achieve elegant downtime of individual Web containers in the Java language.

About the kill command

In Linux, the kill command is responsible for killing processes and can be followed by a number representing the signal number.

Run the kill -l command to print all signal ids.

kill -l
HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ VTALRM PROF WINCH INFO USR1 USR2
Copy the code

We are familiar with the command is kill -9 pid, this can be understood as the operating system from the kernel level to kill a process.

Kill -15 PID sends a notification to tell the application to shut down. We sometimes kill a process with CTRL + C, which is equivalent to kill -2 PID, to tell the foreground process to terminate the process.

Demo

@SpringBootApplication
public class JunitSpringBootApplication {

	public static void main(String[] args) {
		 SpringApplication.run(JunitSpringBootApplication.class, args);
		 Runtime.getRuntime().addShutdownHook(new Thread(){
			 @Override
			 public void run(a) {
				 System.out.println(Execute shutdown hook); }}); }}Copy the code
@RestController
public class HiController implements DisposableBean {
    @Override
    public void destroy(a) throws Exception {
        System.out.println("destroy bean....."); }}Copy the code

Run the application as a Jar package.

Then use kill -15 PID and CTRL + C respectively

Run shutdown hook destroy bean.....Copy the code

The kill -9 pid command outputs nothing.

The source code

Runtime.getRuntime().addShutdownHook(new Thread(){
    @Override
    public void run(a) {
       System.out.println(Execute shutdown hook); }});Copy the code

Runtime.class

public void addShutdownHook(Thread hook) {
  SecurityManager sm = System.getSecurityManager();
  if(sm ! =null) {
    sm.checkPermission(new RuntimePermission("shutdownHooks"));
  }
  ApplicationShutdownHooks.add(hook);
}
Copy the code

ApplicationShutdownHooks.class

private static IdentityHashMap<Thread, Thread> hooks;
static {
  try {
    Shutdown.add(1 /* shutdown hook invocation order */.false /* not registered if shutdown in progress */.new Runnable() {
                   public void run(a) {
                     // Execute the registered hooksrunHooks(); }}); hooks =new IdentityHashMap<>();
  } catch (IllegalStateException e) {
    // application shutdown hooks cannot be added if
    // shutdown is in progress.
    hooks = null; }}/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
static synchronized void add(Thread hook) {
  if(hooks == null)
    throw new IllegalStateException("Shutdown in progress");

  if (hook.isAlive())
    throw new IllegalArgumentException("Hook already running");

  if (hooks.containsKey(hook))
    throw new IllegalArgumentException("Hook previously registered");

  hooks.put(hook, hook);
}
// Get the corresponding thread from the Map, start execution, and wait for it to return
static void runHooks(a) {
  Collection<Thread> threads;
  synchronized(ApplicationShutdownHooks.class) {
    threads = hooks.keySet();
    hooks = null;
  }

  for (Thread hook : threads) {
    hook.start();
  }
  for (Thread hook : threads) {
    while (true) {
      try {
        hook.join();
        break;
      } catch (InterruptedException ignored) {
      }
    }
  }
}
Copy the code

Enter Shutdown. Add

private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
  synchronized (lock) {
    if(hooks[slot] ! =null)
      throw new InternalError("Shutdown hook at slot " + slot + " already registered");

    if(! registerShutdownInProgress) {if (state > RUNNING)
        throw new IllegalStateException("Shutdown in progress");
    } else {
      if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
        throw new IllegalStateException("Shutdown in progress"); } hooks[slot] = hook; }}Copy the code

You can see that the maximum number of runnables is 10, but we store multiple pre-close processing threads with a Map of ApplicationShutdownHooks.

Shutdown. The add operation Runnable

private static void runHooks(a) {
  for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
    try {
      Runnable hook;
      synchronized (lock) {
        // acquire the lock to make sure the hook registered during
        // shutdown is visible here.
        currentRunningHook = i;
        hook = hooks[i];
      }
      if(hook ! =null) hook.run();
    } catch(Throwable t) {
      if (t instanceof ThreadDeath) {
        ThreadDeath td = (ThreadDeath)t;
        throwtd; }}}}Copy the code

When using kill -15 PID or CTRL + C

/* Invoked by Runtime.exit, which does all the security checks. * Also invoked by handlers for system-provided termination events, * which should pass a nonzero status code. */
static void exit(int status) {
  boolean runMoreFinalizers = false;
  synchronized (lock) {
    if(status ! =0) runFinalizersOnExit = false;
    switch (state) {
      case RUNNING:       /* Initiate shutdown */
        state = HOOKS;
        break;
      case HOOKS:         /* Stall and halt */
        break;
      case FINALIZERS:
        if(status ! =0) {
          /* Halt immediately on nonzero status */
          halt(status);
        } else {
          /* Compatibility with old behavior: * Run more finalizers and then halt */
          runMoreFinalizers = runFinalizersOnExit;
        }
        break; }}if (runMoreFinalizers) {
    runAllFinalizers();
    halt(status);
  }
  synchronized (Shutdown.class) {
    /* Synchronize on the class object, causing any other thread * that attempts to initiate shutdown to stall indefinitely */
    // This method calls the runHooks methodsequence(); halt(status); }}Copy the code

This method will be executed. Currently there are two values, a Runnable array middle finger is ApplicationShutdownHooks. The class is placed inside, a DeleteOnExitHook placed in (and its main function is to delete some files).

Hooks register for Spring Boot

Go directly to the SpringApplication

Enter the Refresh method

org.springframework.context.support.AbstractApplicationContext#registerShutdownHook

protected void doClose(a) {
  // Check whether an actual close attempt is necessary...
  if (this.active.get() && this.closed.compareAndSet(false.true)) {
    LiveBeansView.unregisterApplicationContext(this);
      publishEvent(new ContextClosedEvent(this));

    // Stop all Lifecycle beans, to avoid delays during individual destruction.
    if (this.lifecycleProcessor ! =null) {
      try {
        this.lifecycleProcessor.onClose();
      }
      catch (Throwable ex) {
       
      }
    }
    // This is where the singleton bean and destroy methods are triggered
    destroyBeans();
    // Close the context and beanFactory
    closeBeanFactory();
    // Empty implementation, let subclasses extend
    onClose();
    // Switch to inactive.
    this.active.set(false); }}Copy the code

So there are several ways we can be called before the JVM shuts down

  • Listen for ContextClosedEvent event
  • Bean destruction annotations or Spring’s destruction interface
  • Rewrite of the onClose method

How do I stop receiving requests

We are only talking about Tomcat as a servlet container

Implement the following interface to obtain the Connector for Tomcat

@FunctionalInterface
public interface TomcatConnectorCustomizer {
   /**
    * Customize the connector.
    * @param connector the connector to customize
    */
   void customize(Connector connector);
}
Copy the code

Then listen for Spring’s shutdown event

@Component
public class GracefulShutdownTomcat implements TomcatConnectorCustomizer.ApplicationListener<ContextClosedEvent> {
    private final Logger log = LoggerFactory.getLogger(GracefulShutdownTomcat.class);
    private volatile Connector connector;
    private final int waitTime = 30;
    @Override
    public void customize(Connector connector) {
        this.connector = connector;
    }
    @Override
    public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
        this.connector.pause();
        Executor executor = this.connector.getProtocolHandler().getExecutor();
        if (executor instanceof ThreadPoolExecutor) {
            try {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                threadPoolExecutor.shutdown();
                if(! threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) { log.warn("Tomcat thread pool did not shut down gracefully within " + waitTime + " seconds. Proceeding with forceful shutdown"); }}catch(InterruptedException ex) { Thread.currentThread().interrupt(); }}}}Copy the code

After connector.pause(), the application will still accept a new request and hang until the thread pool is shutdown before returning connection peered.

It might be better to roll over, during low traffic periods, import traffic into some instances, stop the rest, shut down and deploy the new service, make sure it’s ok, and switch traffic over to deploy the other half.

How do I close a thread pool

When can a thread exit? Only the thread can know, of course.

So the path of Thread interrruption is not a path toward interrupting a Thread. Is to set the thread to an interrupt state.

1. If the thread is in a blocking state (such as calling a WAIT or IO wait), it immediately exits the block and throws InterruptedException. The thread can do some processing by catching InterruptedException and then exit.

2. If the thread is running, it continues to run without any impact, except that the thread’s interrupt flag is set to true. So the thread checks to see if it has been interrupted by calling the isInterrupted method in place and exits.

InterruptedException is thrown if the thread’s interrupt method is called first and then the thread calls the blocking method to enter the blocking state.

If a thread catches InterruptedException and continues to call the blocking method, it will no longer trigger InterruptedException.

Closing the thread pool

Thread pools provide two shutdown methods, shutdownNow and Shuwdown.

The shutdownNow method is interpreted as: the thread pool rejects the newly submitted task and immediately closes the thread pool. The task in the thread pool is no longer executed.

The shutdown method is interpreted as: the thread pool rejects the newly submitted task and closes the thread pool while waiting for all the tasks in the thread pool to complete.

public List<Runnable> shutdownNow(a) {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
Copy the code

advanceRunState(STOP); Set the thread pool state to STOP

interruptWorkers(); Iterate over all worker threads in the thread pool and call the threadinterrupt method

private void interruptWorkers(a) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally{ mainLock.unlock(); }}Copy the code

tasks = drainQueue(); Unexecuted tasks are removed from the queue and returned to the caller

private List<Runnable> drainQueue(a) {
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    q.drainTo(taskList);
    if(! q.isEmpty()) {for (Runnable r : q.toArray(new Runnable[0]) {if(q.remove(r)) taskList.add(r); }}return taskList;
}
Copy the code

How did the thread pool react after shutdownNow?

Thread pool code logic

try {
    while(task ! =null|| (task = getTask()) ! =null) {
        w.lock();
        // If pool is stopping, ensure thread is interrupted;
        // if not, ensure thread is not interrupted. This
        // requires a recheck in second case to deal with
        // shutdownNow race while clearing interrupt
        if((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && ! wt.isInterrupted()) wt.interrupt();try {
            beforeExecute(wt, task);
            Throwable thrown = null;
            try {
                task.run();
            } catch (RuntimeException x) {
                thrown = x; throw x;
            } catch (Error x) {
                thrown = x; throw x;
            } catch (Throwable x) {
                thrown = x; throw new Error(x);
            } finally{ afterExecute(task, thrown); }}finally {
            task = null;
            w.completedTasks++;
            w.unlock();
        }
    }
    completedAbruptly = false;
} finally {
    processWorkerExit(w, completedAbruptly);
}
Copy the code

A normal thread pool is executed in this for loop, and if the task is running, that is, task.run() is running, it continues to execute uninterrupted, even if the thread is identified as interrupt. However, if it is just blocked, InterruptedException is thrown. Throwing an exception causes the loop to end.

The getTask method also ends the loop when it returns null

Because showdownNow interrupts all worker threads, it will be interrupted if it is in the task queue blocking to get a task.

STOP = 536870912, SHUTDOWN=0. Since the thread pool was set to STOP at shutdownNow, null is bound to be returned in the first red box logic.

shutdown

public void shutdown(a) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
Copy the code

Set the state of the thread pool to SHUTDOWN

Set the flag bit for idle worker threads to Interrupt. How to tell if it is free, through Lock, because

When does an interrupt – or non-interrupt – thread exit? This depends on whether the getTask method returns null.

In the if judgment in getTask (the code in the red box above in the getTask code screenshot above), since the thread pool is changed to shutdown, there is no problem that shutdown is greater than or equal to shutdown. But SHUTDOWN is not greater than or equal to STOP, so the getTask method returns NULL only if the queue is empty, causing the thread to exit.

conclusion

  1. When we call shutdownNow for the thread pool,

If the thread is executing in the getTask method, it enters the if statement through the for loop, and getTask returns null, and the thread exits. Whether there are unfinished tasks in the thread pool or not.

If a thread is blocked executing a task submitted to the thread pool, an error is reported (if no InterruptedException is caught in the task). Otherwise, the thread completes the current task and exits with null returns via the getTask method

  1. When we call the shuwdown method of the thread pool,

If a thread is executing a task in the thread pool, even if the task is blocked, the thread is not interrupted but continues executing.

If the thread pool is blocked waiting to read a task from the queue, it will be woken up, but will continue to determine whether the queue is empty. If not, it will continue to read the task from the queue, and exit if it is empty.

One last thing to remember is that the thread pool is not closed immediately after shutdownNow and shutdown are called. If you want to wait for the thread pool to close, you also need to call awaitTermination to block the wait.

this.executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS));

www.jianshu.com/p/0c49eb23c…

www.cnkirito.moe/gracefully-…

W.cnblogs.com/qingquanzi/…