Help us understand the problem. What is going on with this article?

Spring BootのGraceful shutdown処理が内部でどう呼ばれているかソースコードリーディングしてみた結果

はじめに

Spring Boot 2.3からGraceful shutdownが導入されました。アプリケーションプロパティを設定するだけで簡単にGraceful shutdownを実現することができます。しかし簡単に実現できるだけに内部でどう呼ばれてどう実現しているのかは結構ブラックボックスになっていると思います。
そこで本記事ではSpring BootのGraceful shutdownが内部でどう呼ばれているかをソースコードリーディングして調べた結果を書いていきたいと思います。

結論

  • Spring BootのGraceful shutdownが内部でどう呼ばれているか
    • SpringのSmartLifecycleという仕組みによってApplicationContextのclose時に処理が呼ばれる

今回使用するバージョン・サーバ

Spring Boot 2.3.1を使用しています。また、サーバはJettyを使用します。

前提: Spring BootでのGraceful shutdown実現方法

ドキュメントに記載の通りですが、アプリケーションプロパティに以下のように設定するだけでGraceful shutdownを実現することができます。

application.yml
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 20s #サーバの最大シャットダウン時間 この時間までにリクエストが0にならなかったら強制的にサーバを停止する

Graceful shutdown中の具体的な挙動としては

  1. 新たに来たリクエストは受け付けない
  2. 処理中のリクエストを捌き切るまでサーバを停止させない

です。この「新たに来たリクエストは受け付けない」というのは使用するWebサーバによって挙動が異なります。

Webサーバ 挙動
Tomcat
Jetty
Reactor Netty
ネットワークレイヤーでコネクションを受け付けない
Undertow 503を返す

参考: Spring Boot Reference Documentation#boot-features-graceful-shutdown

Graceful shutdownが内部でどう呼ばれるか

上記からSpring BootのGraceful shutdownがどのように動くのか分かりました。では上記の処理はどこからどう呼ばれているのでしょうか。
ソースコードを読んで確認してみたいと思います。
※注意: 今回追うコードはSpring Boot 2.3.1を前提としています。内部の挙動は今後のバージョンアップで変わる可能性があるのでご注意ください。
また、サーバはJettyを使用します。

Graceful shutdownの実処理部分を特定する

まずGraceful shutdownの実処理部分を特定し、そこを起点に呼び出し元を探る作戦でいきたいと思います。
何はともあれGraceful shutdownを動かしてみましょう。
まずSpring Bootを起動します。

% ./mvnw spring-boot:run
...
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)
...
2020-07-30 02:35:10.626  INFO 15060 --- [           main] o.e.jetty.server.AbstractConnector       : Started ServerConnector@552ed807{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2020-07-30 02:35:10.628  INFO 15060 --- [           main] o.s.b.web.embedded.jetty.JettyWebServer  : Jetty started on port(s) 8080 (http/1.1) with context path '/'
2020-07-30 02:35:10.640  INFO 15060 --- [           main] c.k.s.SampleGracefulShutdownApplication  : Started SampleGracefulShutdownApplication in 1.517 seconds (JVM running for 1.795)

Jettyが動いているのが分かります。
これを今回はSpring Boot Actuatorのshutdownエンドポイントを用いて停止させます。

2020-07-30 02:35:14.253  INFO 15060 --- [     Thread-223] o.s.b.web.embedded.jetty.JettyWebServer  : Commencing graceful shutdown. Waiting for active requests to complete
2020-07-30 02:35:14.258  INFO 15060 --- [ jetty-shutdown] o.s.b.web.embedded.jetty.JettyWebServer  : Graceful shutdown complete
2020-07-30 02:35:14.264  INFO 15060 --- [     Thread-223] o.e.jetty.server.AbstractConnector       : Stopped ServerConnector@552ed807{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2020-07-30 02:35:14.265  INFO 15060 --- [     Thread-223] org.eclipse.jetty.server.session         : node0 Stopped scavenging
2020-07-30 02:35:14.267  INFO 15060 --- [     Thread-223] o.e.j.s.h.ContextHandler.application     : Destroying Spring FrameworkServlet 'dispatcherServlet'
2020-07-30 02:35:14.268  INFO 15060 --- [     Thread-223] o.e.jetty.server.handler.ContextHandler  : Stopped o.s.b.w.e.j.JettyEmbeddedWebAppContext@38ed139b{application,/,[file:///private/var/folders/ds/ky2m710d41ldw12zs6jhg4jh0000gn/T/jetty-docbase.10343674703387388779.8080/],UNAVAILABLE}
2020-07-30 02:35:14.270  INFO 15060 --- [     Thread-223] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

上記1行目のログを見るとGraceful shutdownを開始しており、2行目のログを見るとGraceful shutdownを完了させていることが分かります。
このログを吐いているクラスがどちらもo.s.b.web.embedded.jetty.JettyWebServerクラスです。
どうやらこのクラスはGraceful shutdownに関わっていそうなので次にこのクラスを調べてみます。

JettyWebServerクラスを見てみるとshutDownGracefullyというメソッドが存在します。このメソッド内部でGraceful shutdownが行われていそうです。

org.springframework.boot.web.embedded.jetty.JettyWebServer.java
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
    if (this.gracefulShutdown == null) {
        callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
        return;
    }
    this.gracefulShutdown.shutDownGracefully(callback);
}

https://github.com/spring-projects/spring-boot/blob/v2.3.1.RELEASE/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java#L286-L292

さらに見てみると、this.gracefulShutdownインスタンスがnullかどうかによってGraceful shutdownを行うかどうかここで変わってきてそうで、this.gracefulShutdownインスタンスがnullではない場合にGraceful shutdown処理が呼ばれていそうです。
このthis.gracefulShutdown.shutDownGracefully(callback);のメソッドの中を確認してみると以下のようになっていました。

org.springframework.boot.web.embedded.jetty.GracefulShutdown.java
void shutDownGracefully(GracefulShutdownCallback callback) {
    logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
    for (Connector connector : this.server.getConnectors()) {
        shutdown(connector);
    }
    this.shuttingDown = true;
    new Thread(() -> awaitShutdown(callback), "jetty-shutdown").start();

}

一行目のlogger.info()の内容は最初Spring Bootを停止させたときに書き出されていたログの内容と一致します。
したがって、このメソッドの内部でGraceful shutdownの実処理が行われていることがほぼ確実になりました。

ちなみにこれより内部のコードはこちらの記事で紹介しているので本記事では割愛します。

上記処理がどこから呼ばれているか

ここまででGraceful shutdownの実処理の部分にはたどり着くことができました。
ではこの処理がどこから呼ばれているのでしょうか。

最初に示したJettyWebServerクラスのshutDownGracefullyメソッドを呼んでいる箇所を追ってみると、以下のWebServerGracefulShutdownLifecycleクラスのstopメソッドから呼ばれていることが分かりました。

org.springframework.boot.web.servlet.context.WebServerGracefulShutdownLifecycle.java
class WebServerGracefulShutdownLifecycle implements SmartLifecycle {

    ...

    @Override
    public void stop(Runnable callback) {
        this.running = false;
        this.webServer.shutDownGracefully((result) -> callback.run());
    }
}

https://github.com/spring-projects/spring-boot/blob/v2.3.1.RELEASE/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/WebServerGracefulShutdownLifecycle.java#L49-L52

このクラスに注目するとSmartLifecycleインターフェースを実装していることが分かります。
実はこのSmartLifecycleが今回の肝です。
SmartLifecycleを実装したクラスはApplicationContextの起動時やシャットダウン時に任意の処理が呼ばれるようになります。具体的にはSmartLifecycleのstart, stopメソッドが呼ばれます。
今回のgraceful shutdownはこのSmartLifecycleのstopメソッドをオーバーライドして呼び出すことで実現していました。

ということでSpring BootのGraceful shutdownはどこから呼ばれるか、の答えはSmartLifecycleのstopメソッドから呼ばれる、ということになります。
ちなみにこのクラスのstopメソッドはJettyに限らずTomcatやReactor Nettyなどでも同様に呼ばれます。このstopメソッドの2行目ではthis.webServer.shutDownGracefully((result) -> callback.run());としており、ここでインジェクションされたwebServerインスタンスの種類、つまりWebサーバの種類によってこの後呼ばれるの処理内容が変わるような実装になっています。オブジェクト指向らしくていいですね。

また、これをさらに呼んでいるのはDefaultLifecycleProcessorクラスです。
このクラス内でSmartLifecycleのBeanのstopメソッドを順々に呼んでいきます。このクラスがApplicationContextのクローズに伴って呼ばれるため、結果的にSpring Bootクローズ時にGraceful shutdownが呼ばれるようになっています。

org.springframework.context.support.DefaultLifecycleProcessor.java
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) {
        ...
        try {
            if (bean.isRunning()) {
                if (bean instanceof SmartLifecycle) {
                    ...
                    ((SmartLifecycle) bean).stop(() -> { //ここでSmartLifecycleのstopが順々に呼ばれる
                    ...
                    });
                }
                ...
            }
            ...
        }
        ...
    }
}

https://github.com/spring-projects/spring-framework/blob/v5.2.7.RELEASE/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java#L238-L244

まとめ

今回Spring BootのGraceful shutdownがどのように実現されているか見てみました。SpringのSmartLifecycleの仕組みによって実現されているが分かりました。
ここまで追っていきましたが、実はこのことはドキュメントにもちゃんと記載されています。
ただ、ドキュメントを呼んだだけではSmartLifecycleが何者なのか全く分からなかったのでそういう点ではソースコードレベルまで追ってみたことでSmartLifecycleを理解できたので良かったと思います。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away