この記事の要点
- Swift サーバーエコシステム向けの新しいオープンソースプロジェクト Swift Service Lifecycle(swift-service-lifecycle)が発表されました。サーバーアプリケーション(サービス)の起動・終了シーケンスの管理を助ける Swift パッケージです。
- スレッドプールの初期化やデータマイグレーション、キャッシュのウォームアップといった起動処理や、ファイルディスクリプタなどのリソース解放を伴う終了処理は失敗しやすく、正しく実装するのが難しい領域です。Service Lifecycle はこの共通の課題を、安全で再利用可能、かつフレームワークに依存しない形で扱えるようにします。
ServiceLifecycleに起動・終了タスクを登録しておくと、登録順に起動し、登録と逆順に終了します。INT/TERMシグナルを捕捉して終了シーケンスを開始するハンドラも既定で登録されます。
何が発表されたのか
Swift Service Lifecycle は、サーバーアプリケーション(サービスとも呼ばれます)の起動と終了の流れを管理するための Swift パッケージです。
多くのサービスには、起動・終了にまつわるワークフローのロジックがあります。起動時にはスレッドプールの初期化、データマイグレーションの実行、キャッシュのウォームアップなど、トラフィックやイベントを受け付ける前の状態初期化が必要です。終了時には、ファイルディスクリプタやその他のシステムリソースを正しく解放しないとリークしてしまうため、それらを片付ける処理が必要です。これらの処理は失敗の影響を受けやすく、正しく実装するのが難しいとされています。
従来は、サーバーアプリケーションやフレームワークがそれぞれ独自にこの課題へ対処する必要があり、ミスが起きやすい状況でした。Service Lifecycle は、この共通の必要性を安全・再利用可能・フレームワーク非依存な形でコード化します。任意のサーバーフレームワークと統合したり、サーバーアプリケーションの main に直接組み込んだりして使えるように設計されています。
何に使えるのか
推奨される使い方は、サーバーアプリケーションの main で ServiceLifecycle のインスタンスを作り、そこに LifecycleTasks を登録するというものです。start を呼ぶと、登録された順番にタスクが起動されます。
既定では、ServiceLifecycle は INT と TERM を捕捉する Signal ハンドラも登録します。これらは、最近のデプロイ環境で終了要求を伝えるために使われる典型的なシグナルです。シグナルを捕捉すると終了シーケンスが始まり、LifecycleTasks は登録とは逆の順番で終了されます。
次の例は、main で ServiceLifecycle を構成する一連の流れを示しています。
// パッケージをインポートする
import Lifecycle
// Lifecycle コンテナを初期化する
var lifecycle = ServiceLifecycle()
// アプリケーション終了時にシャットダウンすべきリソースを登録する
//
// この例では SwiftNIO の EventLoopGroup を登録し、
// シャットダウン時に呼ばれる syncShutdownGracefully を渡している
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
lifecycle.registerShutdown(
name: "eventLoopGroup",
.sync(eventLoopGroup.syncShutdownGracefully)
)
// アプリケーション起動時に開始し、終了時にシャットダウンすべき
// リソースを登録する
//
// この例では DatabaseMigrator を登録し、
// 起動時に呼ばれる migrate とシャットダウン時に呼ばれる shutdown を渡している
let migrator = DatabaseMigrator(eventLoopGroup: eventLoopGroup)
lifecycle.register(
name: "migrator",
start: .async(migrator.migrate),
shutdown: .async(migrator.shutdown)
)
// アプリケーションを起動する
//
// register で渡した起動ハンドラは、登録した順番で呼ばれる
lifecycle.start() { error in
// 起動完了ハンドラ
// 起動時にエラーが発生した場合はここで受け取れる
if let error = error {
logger.error("failed starting \(self) ☠️: \(error)")
} else {
logger.info("\(self) started successfully 🚀")
}
}
// アプリケーションの終了を待つ
//
// これはブロッキング操作で、通常は Signal を待つ
// Signal は lifecycle.init で設定でき、既定では INT と TERM
// register / registerShutdown で渡したシャットダウンハンドラは、
// 登録とは逆の順番で呼ばれる
lifecycle.wait()
registerShutdown は終了時の処理だけを登録し、register は起動と終了の両方の処理を登録します。起動ハンドラは登録順に、シャットダウンハンドラは逆順に呼ばれるため、依存関係のあるリソースを正しい順序で初期化・解放できます。
導入・今後の位置づけ
Service Lifecycle はオープンソースプロジェクトであり、コミュニティからの貢献を歓迎しているとされています。ソースコードは GitHub リポジトリで公開されており、フィードバックや議論は Swift フォーラムの server カテゴリで行えます。