はじめに
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を実現することができます。
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 20s #サーバの最大シャットダウン時間 この時間までにリクエストが0にならなかったら強制的にサーバを停止する
Graceful shutdown中の具体的な挙動としては
- 新たに来たリクエストは受け付けない
- 処理中のリクエストを捌き切るまでサーバを停止させない
です。この「新たに来たリクエストは受け付けない」というのは使用する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が行われていそうです。
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
if (this.gracefulShutdown == null) {
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
this.gracefulShutdown.shutDownGracefully(callback);
}
さらに見てみると、this.gracefulShutdown
インスタンスがnullかどうかによってGraceful shutdownを行うかどうかここで変わってきてそうで、this.gracefulShutdown
インスタンスがnullではない場合にGraceful shutdown処理が呼ばれていそうです。
このthis.gracefulShutdown.shutDownGracefully(callback);
のメソッドの中を確認してみると以下のようになっていました。
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メソッドから呼ばれていることが分かりました。
class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
...
@Override
public void stop(Runnable callback) {
this.running = false;
this.webServer.shutDownGracefully((result) -> callback.run());
}
}
このクラスに注目すると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が呼ばれるようになっています。
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が順々に呼ばれる
...
});
}
...
}
...
}
...
}
}
まとめ
今回Spring BootのGraceful shutdownがどのように実現されているか見てみました。SpringのSmartLifecycleの仕組みによって実現されているが分かりました。
ここまで追っていきましたが、実はこのことはドキュメントにもちゃんと記載されています。
ただ、ドキュメントを呼んだだけではSmartLifecycleが何者なのか全く分からなかったのでそういう点ではソースコードレベルまで追ってみたことでSmartLifecycleを理解できたので良かったと思います。