この記事の概要
この記事は、常時実行している処理のプロセスを修了した際に処理途中で停止を防ぐ方法を学びましたので記事として残します。
間違った情報を記載している可能性がありますので温かい目で読んでいただければと思います。
通常のプロセス停止
以下に擬似的に処理を100%で完了と表示されるサンプルコードをDeno(TypeScript)で記述しています。
console.log("Start");
while(true) {
for (let i = 1; i <= 10; i++) {
console.log(`${i*10}%`);
if( i === 10 ) {
console.log("Complete!");
}
// 1秒まつ
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
console.log("Exit");
今回常時実行を無限ループで表現しており、実際にこのコードを実行するとこのようなlogが表示されます。
何かしらDB操作やユーザーへのレスポンス、batch処理などの1つが完了するまでを100%と想像してください。
次に実行途中でCtrl+C
を入力するともちろん実行プロセスは停止されるので画像のように中途半端な40%で処理が停止しています。
MacのlocalであればSIGINT
というシグナルが返ってきていることがわかります。
これがもし、実際に動いているサービスのコードだとしたら何かしら(デプロイによるコンテナの切り替えなど)でランタイムが停止する時に実行中の処理が中途半端な状態で止まるかもしれません。
DB操作のようにトランザクション処理などがない機能であればユーザーへ影響が出るかもしれません。
シグナルを使ったプロセス停止ハンドリング
私は今まであまり意識したことがなかったのですが(ダメですね)
Unix系の環境ではプロセス停止時にシグナルが送信されているということで、それを使ったハンドリングに挑戦してみました。
Denoのドキュメントに従って以下のようにコードを修正しました。
SIGINT
を受信したらwhileを回すための変数をfalseに切り替えるという処理です。
console.log("Start");
let executable = true;
function sigIntHandler() {
console.log("SIGINT received");
executable = false;
}
Deno.addSignalListener("SIGINT", sigIntHandler);
while(executable) {
for (let i = 1; i <= 10; i++) {
console.log(`${i*10}%`);
if( i === 10 ) {
console.log("Complete!");
}
// 1秒まつ
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
Deno.removeSignalListener("SIGINT", sigIntHandler);
console.log("Exit");
実際に実行して、途中のタイミングで同じようにCtrl+C
を入力します。
SIGINT
を受信した後も処理は進み、100%とキリがいいところまで処理が完了した上で修了しました。
今回はlocalでの検証だったためSIGINT
でハンドリングしましたが環境によってハンドリングに使うシグナルの種類は変更が必要です。
例えばAWS ECSのコンテナシャットダウン時にはSIGTERM
が送信されます。
ECS のアプリケーションを正常にシャットダウンする方法
まとめ
- デプロイ時などアプリのプロセス停止時に実行中の処理が途中で終わっていいものかは意識したい
- シグナルを使った停止ハンドリングを使うことで実行途中の処理を完了させてからアプリを停止できる
今回上記のような学びを得ましたが、初めて触る概念であり今後も学習や実務の中で身につけていきたいと思います。
この記事をよんでくださった方、ありがとうございました。