コンテンツにスキップ

SIGTERM によるグレースフルストップ

wired_server_run は、待ち受けを始める前に SIGTERM(プロセスに終了を求めるシグナル)のハンドラーを登録します(HTTP/3 の仕様 RFC 9114 5.2)。 SIGTERM を受け取ると、新しい接続の受け入れは止まり、そのとき生きている接続すべてに GOAWAY(これ以上新しいリクエストを受け付けないと相手に伝えるフレーム)が一度送られ、 全接続が drain(処理中のやり取りを終えて閉じること)を終えるか、決められた猶予期間が過ぎた時点でプロセスが終了します。 --workers(wired_srvdriver_run の内部で呼ばれる wired_srvworkers_run)は、 その上にスーパーバイザー(ワーカープロセスを見張る親プロセス)を一枚重ねます。 受け取った一つの SIGTERM を生きているワーカーそれぞれに転送し、以後は再起動をしなくなるので、 プロセスグループ全体が揃って終了します。

このページでは、ワーカーを一つだけ起動し(--workers 1)、リクエストに一度応答したあと SIGTERM を送ります。

コード

main.c
#define WIRED_MAIN
#include "wired.h"
static int on_request(
void* ctx,
const wired_h3reqdrive_req* req,
u64 offset,
wired_obuf* body_out,
const char** content_type,
int* more,
u64* total_size) {
(void)ctx;
(void)req;
(void)offset;
(void)more;
(void)total_size;
*content_type = "text/plain";
wired_obuf_printf(body_out, "ok");
return 1;
}
int wired_main(int argc, char** argv) {
/* The same fixed demo identity as the HTTP/3 hello page. */
static wired_srvboot_demo_keys keys;
wired_srvboot_id id;
wired_srvboot_demo(&id, &keys, 0x50, "guide-h3");
/* --port, --workers, --cores, --ifindex, ... pick and configure a driver. */
wired_srvdriver_opt opt = {0};
if (!wired_srvdriver_parse(argc, argv, &opt)) {
wired_log_str("bad flags\n");
return 2;
}
wired_srvrun_handler h = {.cb = on_request};
wired_srvrun_obs obs = {0};
return wired_srvdriver_run(&id, h, obs, &opt) < 0 ? 1 : 0;
}

実行結果

Terminal window
$ ./ops-shutdown --port 4433 --workers 1
conn closed by peer (CONNECTION_CLOSE)
Terminal window
$ probe h3get 127.0.0.1:4433 /
GET / status=200 content-type=text/plain body="ok"
  • wired_srvdriver_parse/wired_srvdriver_run は、ops/handshake-knobs と同じ方法で argv からドライバーを選びます。--workers 1 を渡すと、子プロセスをちょうど一つだけ fork する wired_srvworkers_run が選ばれます。
  • スーパーバイザー自身の SIGTERM ハンドラーはフラグを立てるだけです(シグナルハンドラー内で安全に行える操作だけにとどめる async-signal-safe な作りです)。 reap ループ(終了した子プロセスを回収するループ)が、生きている各ワーカーの pid に SIGTERM を一度だけ転送し、 以後は終了したワーカーを再び fork しません。シャットダウン中に落ちたワーカーも、 もう再起動されません。
  • 各ワーカー自身が実行している wired_server_run はそのままです。 上で説明した「新規接続を止め、GOAWAY を送り、drain してから終了する」という ワーカー自身のグレースフルシャットダウンの経路は、スーパーバイザーが転送する この SIGTERM が引き金になって動きます。
  • このページでは 1 より大きい --workers は試していません。 ワーカーが複数になると、ポートの共有は SO_REUSEPORT(複数のプロセスが同じポートで 待ち受けられるようにするソケットオプション)のカーネル側ハッシュだけに頼ることになり、 ワーカーごとの振り分けは存在しません。 wired_srvworkers_run 自身のドキュメントも、この fork モデルでは ハンドシェイクが断続的に失敗するリスクがあると明記しています。 --workers 1 はそのリスクを避けつつ、スーパーバイザーが転送して reap するという 実際の経路はそのまま確かめられます。