前々回と前回の記事では、プログラムの実行単位であるプロセスについて紹介し、Go言語から外部プロセスを実行する方法などを見ました。 今回の記事では、プロセスに対して送られるシグナルと呼ばれる仕組みを説明します。
シグナルには、大きく2つの用途があります。
- プロセス間通信:あるプロセスから、別のプロセスに対し、シグナルを送ることができます。場合によっては、あるプロセスから自分自身に対してシグナル送ることもできます。
- ソフトウェア割り込み:システムで発生したイベントは、シグナルとしてプロセスに送られます。シグナルを受け取ったプロセスは、現在行っているタスクを中断して、あらかじめ登録しておいた登録ルーチンを実行します。
これまでの記事で何度も登場したシステムコールは、ユーザ空間で動作しているプロセスからカーネル空間にはたらきかけるためのインタフェースでしたが、 その逆方向がシグナルだと考えることもできます。 (昔のシステムコールはソフトウェア割り込みとして実装されていたので、仕組み上もほぼおなじと言えます。)
とはいえ、システムコールでは種類を表すIDを含めて最大6つほどの引数を指定できるのに対し、ソフトウェア割り込みで送信できるのは種類を表す数値だけです。 また、システムコールを受け取るカーネルは常に起動していますが、シグナルを受け取るプロセスは一時停止していることもあるので、シグナルではそのあたりの挙動も考慮されています。 具体的には、プロセスが停止中でも強制処理されるシグナルもあれば、再開されるまでキューイングされるシグナルもあります1。
残念ながら、今回のコードのほとんどはWindowsでは動作しません。 プロセス間通信としてのシグナルはプロセスの強制終了のみが使え、それ以外だと Ctrl + C
のフックぐらいしかできませんので注意してください。
シグナルのライフサイクル
シグナルはさまざまなタイミングで発生(raise)します。 0除算エラーや、メモリの範囲外アクセス(セグメント違反)は、CPUレベルで発生し、それを受けてカーネルがシグナルを生成します。 アプリケーションプロセスで生成(generate)されるシグナルもあります。
生成されたシグナルは、対象となるプロセスに送信(send)されます。 プロセスは、シグナルを受け取ると、現在の処理を中断して受け取ったシグナルの処理を行います。
プロセスは、受け取ったシグナルを無視するか、捕捉して処理(handle)します。 デフォルトの処理は、無視か、プロセスの終了です。
プロセスがシグナルを受け取った場合の処理内容は、事前に登録してカスタマイズできます。 プロセスを終了しない場合は、シグナルを受け取る前に行っていたタスクを継続します。
なお、プロセス側でシグナルをハンドルするコードが自由に書けるといっても、シグナルにはそれぞれ決められた役割があります。 本来の役割からかけ離れた処理を実装すべきではありません。
シグナルの種類
シグナルにはすべてSIGから始まる名前がついています。 Unix系OSでは、次のコマンドを実行することで、すべてのシグナルを一覧できます。
$ man 7 signal ⏎
全部のシグナルを使うことはありませんので、本記事ではアプリケーション開発の文脈で優先度が高いものから順番にいくつかを紹介します。
ハンドルできないシグナル
シグナルのなかには、アプリケーションでハンドルができないものがあります。
- SIGKILL:プロセスを強制終了
- SIGSTOP:プロセスを一時停止して、バックグラウンドジョブにする
これらのシグナルは、それぞれ「SIG」を除外した文字列(KILL、STOP)をkill
コマンドにオプションとして指定することで、コマンドラインからプロセスに対して送信できます。
# プロセスIDを指定してSIGKILLシグナル送信
$ kill -KILL 35698 ⏎
# プロセス名を指定してSIGSTOPシグナル送信
$ pkill -STOP ./sample ⏎
SIGSTOPでは、ジョブがバックグラウンドに回っていったんターミナルが戻ってきて、別のプロセスを実行できるようになります。 fg
コマンドを使うと、再びフォアグラウンドジョブとして戻すことができます。
# バックグラウンドジョブになっているジョブを呼び戻して仮想端末に再接続
$ fg ./sample ⏎
サーバーアプリケーションでハンドルするシグナル
サーバーアプリケーション側で独自のハンドラを書くことが比較的多いシグナルとしては、次のようなものがあります。
- SIGTERM:
kill()
システムコールやkill
コマンドが送信するシグナルで、プロセスを終了させるもの - SIGHUP:通常は、後述するコンソールアプリケーション用のシグナルだが、「ターミナルを持たないデーモンでは絶対に受け取ることはない」ので、サーバアプリケーションでは別の意味で使われる。 具体的には、設定ファイルの再読込を外部から指示する用途で使われることがデファクトスタンダードとなっている
どちらもデフォルトの動作はプロセスの終了です。
コンソールアプリケーションでハンドルするシグナル
- SIGINT:ユーザーが
Ctrl + C
でプログラムを終了(ハンドルできるバージョンのSIGKILL) - SIGQUIT:ユーザーが
Ctrl + \
でコアダンプを生成し終了 - SIGTSTP:ユーザーが
Ctrl + Z
で停止させ、バックグラウンド動作をさせる(ハンドルできるバージョンのSIGSTOP) - SIGCONT:バックグラウンド動作から戻させる指令
- SIGWINCH:ウインドウサイズ変更
- SIGHUP:バックグラウンド動作になったり、プロセスが終了したりして、疑似ターミナルから切断されるときに呼ばれるシグナル
終了時にエスケープシーケンスをもとに戻しておく場合や、バックグラウンド動作から戻るときに画面の状態を戻す場合などに、これらのシグナルに対するハンドラを用意します。
たまに使うかもしれない、その他のシグナル
- SIGUSR1とSIGUSR2:ユーザー定義のシグナル。アプリケーションが任意の用途で使える
- SIGPWR:外部電源が切断し、無停電電源装置(UPS)が使われたものの、バッテリー残量が低下してシステムを終了する必要があるときにOSから送信されるシグナル。実際に使えるかどうかはOSによって異なる
Go言語におけるシグナルの種類
Go言語ではsyscall
パッケージ内でシグナルを定義しています。 例えば、syscall.SIGINT
のように定義されています。 なお、SIGINTとSIGKILLの2つに関してはos
パッケージで次のようにエイリアスが設定されていて、全OSで使えることが保証されています。
var (
Interrupt Signal = syscall.SIGINT
Kill Signal = syscall.SIGKILL
)
次のシグナルはPOSIX系OSで使えるシグナルの一覧です。
- ハンドル不可・外部からのシグナルは無視 :SIGFPE, SIGSEGV, SIGBUSが該当。 算術エラー、メモリ範囲外アクセス、その他のハードウェア例外を表す、致命度の高いシグナル。 Go言語では、自分のコード中で発生した場合には
panic
に変換して処理される。 外部から送付することはできず、ハンドラを定義しても呼ばれない - ハンドル不可 :SIGKILL, SIGSTOPが該当。 Go言語に限らず、C言語でもハンドルできないシグナル
- ハンドル可能・終了ステータス1 :SIGQUIT, SIGABRTが該当
- ハンドル可能・パニック、レジスタ一覧表示、終了ステータス2 :SIGILL, SIGTRAP, SIGEMT, SIGSYSが該当
- ハンドル可能・無視 :SIGPIPE, SIGALRM, SIGURG, SIGIO, SIGXCPU, SIGXFZ, SIGVTALRM, SIGWINCH, SIGINFO, SIGUSR1, SIGUSR2, SIGCHLD, SIGPROFが該当
- ハンドル可能・OSデフォルト動作(macOSは無視) :SIGTSTP, SIGCONT, SIGTTIN, SIGTTOUが該当
Windows環境とNaCl環境では、下記の表のように、定義されているシグナルがかなり少なくなっています。
シグナル名 | Windows | Nacl |
---|---|---|
SIGHUP | ◯ | |
SIGINT | ◯ | ◯ |
SIGQUIT | ◯ | ◯ |
SIGILL | ◯ | |
SIGTRAP | ◯ | ◯ |
SIGABRT | ◯ | |
SIGBUS | ◯ | |
SIGFPE | ◯ | |
SIGKILL | ◯ | ◯ |
SIGSEGV | ◯ | |
SIGPIPE | ◯ | |
SIGALRM | ◯ | |
SIGTERM | ◯ | |
SIGCHLD | ◯ |
シグナルのハンドラを書く
ここからは、実際にGo言語でシグナルを扱う方法を紹介します。
最初に注意点ですが、シグナルに関する設定はプロセス全体に及ぶグローバルな設定です。 また、シグナルはフォアグラウンドのプロセスに最初に送信されます。 したがって、自作のコードでシグナルのハンドラを書き、それをgo run
を使って実行すると、シグナルは自作コードのプロセスではなくgo
コマンドのプロセスに送信されてしまいます。 これを避けるため、シグナルをハンドルするコードはgo run
では実行せず、go build
コマンドで実行ファイルを作成してから実行してください。
手始めに、SIGINTとSIGTERMを受け取ってもすぐには終了せず、それぞれSIGINT
、SIGTERM
と表示するコードを下記に示します。
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
// サイズが1より大きいチャネルを作成
signals := make(chan os.Signal, 1)
// 最初のチャネル以降は、可変長引数で任意の数のシグナルを設定可能
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
s := <-signals
switch s {
case syscall.SIGINT:
fmt.Println("SIGINT")
case syscall.SIGTERM:
fmt.Println("SIGTERM")
}
}
上記のコードでは、チャネルを作成してそれをsignal.Notify()
という関数に渡しています。 この関数で指定されたシグナルがくると、最初に作成したチャネルを通じて、そのシグナルを受け取れます。 C言語でsignal()
やsigaction()
のようなシステムコールを使う場合にはハンドラ関数を指定しますが、Go言語でのシグナルの扱いはそれとはだいぶ趣が異なります。
なお、上記のコードではチャネルの受け取りで完全にブロックしますが、実用的なコードではsignal.Notify
以降の内容はgoroutineで並行で実行しつつ、サーバー起動やユーザーとの対話などのメインのコードを実行するスタイルが一般的です。
シグナルを無視する
シグナルを無視したい場合はsignal.Ignore()
を使います。 シグナルの列挙の仕方はsignal.Notify()
に近いのですが、受け取るわけではないのでチャネルは不要です。
次のコードは、最初の10秒間だけCtrl + C
を無視します。
package main
import (
"fmt"
"os/signal"
"syscall"
"time"
)
func main() {
fmt.Println("Ignore Ctrl + C for 10 second")
// 可変長引数で任意の数のシグナルを設定可能
signal.Ignore(syscall.SIGINT, syscall.SIGHUP)
time.Sleep(time.Second * 10)
}
シグナルのハンドラをデフォルトに戻す
あまり使うことがないかもしれませんが、シグナルのハンドラをデフォルトに戻すことができます。
// 可変長引数で任意の数のシグナルを設定可能
signal.Reset(syscall.SIGINT, syscall.SIGHUP)
シグナルの送付を停止させる
これ以上シグナルを受け取らないようにすることもできます。 あまり見かけませんが、終了処理を開始した直後に再度ハンドラが呼ばれないようにするためなどに使えるでしょう。
signal.Stop(signals)
これを呼び出すと、それ以降Notify()
で指定したシグナルを受け取らなくなるわけではなく、デフォルトに戻るようです。 Notify()
でSIGINT(Ctrl + C
)を指定していた場合、呼び出し後はブロックせずにデフォルトでプロセスを終了するようになります。
シグナルを他のプロセスに送る
他のプロセスにシグナルを送るには、前回紹介したos.Process
構造体を使います。 この構造体のSignal()
メソッドを使うことでシグナルを送信できます。 さらに、Kill()
という、SIGKILLを送信する専用のメソッドもあります。
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
if len(os.Args) < 2 {
fmt.Printf("usage: %s [pid]\n", os.Args[0])
return
}
// 第一引数で指定されたプロセスIDを数値に変換
pid, err := strconv.Atoi(os.Args[1])
if err != nil {
panic(err)
}
process, err := os.FindProcess(pid)
if err != nil {
panic(err)
}
// シグナルを送る
process.Signal(os.Kill)
// Killの場合は次のショートカットも利用可能
process.Kill()
}
os/exec
パッケージを使った高級なインタフェースでプロセスを起動した場合は、Process
フィールドにos.Process
構造体が格納されているので、この変数経由で送信できます。
cmd := exec.Command("child")
cmd.Start()
// シグナル送信
cmd.Process.Signal(os.Interrupt)
シグナルの応用例(Server::Starter)
サーバー系のプログラムは、CUIやGUIのツールとは異なり、複数のユーザーが同時にアクセスして利用できます。 そのため、バージョンアップや設定修正などで再起動をする際、正しく終了するのが難しいという問題があります。 いきなりシャットダウンしてしまうと、アクセス中のユーザーに正しく結果を返すことができません。 かといって、自然にユーザーが途切れるまで待つわけにもいきません。 複数台のサーバーを利用している場合には、さらに難しくなります。 この課題はグレイスフル・リスタート
と呼ばれています。
グレイスフル・リスタートを実現するための補助ツールとして広く利用されている仕組みに、奥一穂さんが作成したServer::Starterがあります。 Server::Starterは、サーバーの再起動が必要になったときに、「新しいサーバーを起動して新しいリクエストをそちらに流しつつ、古いサーバーのリクエストが完了したら正しく終了させる」ための仕組みです。 Server::Starterを利用できるようにサーバーを作れば、サービス停止時間ゼロでサーバーの再起動が可能になります。
Server::Starterは、もともとはPerlで実装されたものですが、シグナルと環境変数を使った汎用の仕組みです。 そのため、さまざまな言語で利用可能です。 たとえば、Sonotsさんが移植したRubyバージョンがあります。 Go言語については、牧大輔さんが移植したバージョンがあります。
- Perl版: http://search.cpan.org/~kazuho/Server-Starter-0.33/lib/Server/Starter.pm
- Ruby版: https://github.com/sonots/ruby-server-starter
- Go版: https://github.com/lestrrat/go-server-starter
Go版のServer::Starterは次のようにインストールします。
$ go get github.com/lestrrat/go-server-starter/cmd/start_server ⏎
ここでは、プロセスとシグナルの応用例として、Go版のServer::Starterを利用するサーバーを実装してみましょう。
Server::Starterの使い方
Server::Starterを利用可能なサーバーの実装を見る前に、Server::Starterの使い方を簡単に説明します。
server
というサーバープログラムを、Server::Starterで起動するには、次のようにstart_server
というコマンドを使います。
$ start_server --port 8080 --pid-file app.pid -- ./server ⏎
このコマンドは、次の3つのタスクを行います。
- ポート8080番を開く
- 現在のプロセスIDを
app.pid
ファイルに書き出す - 開いたポートを渡し、
server
を子プロセスとして起動する
server
は、start_server
コマンドの子プロセスとして、start_server
が開いたソケットを受け取り、そのソケットでサーバーとしてサービスを開始します。
親プロセスであるstart_server
が開いたソケットを、子プロセスであるserver
が使うために利用しているのは、前回の記事で紹介した、exec.Cmd
のExtraFiles
オプションです。 ExtraFiles
を使ってファイルディスクリプタを渡すと、そのファイルディスクリプタが3番以降の番号として割り当て済みの状態で子プロセスが起動します。 何番のポートを何番めのファイルディスクリプタで開いたかという情報は、SERVER_STARTER_PORT
環境変数で伝えます。
このあたりの実装については、Go版のServer::StarterのStarter
構造体のStartWorker()
メソッド2の中で見ることができます。 子プロセス側のコードも、Server::Starterが提供するTCPListener
構造体3にあります。
Server::Starterが子プロセスを再起動する仕組み
Server::Starterで起動したサーバープロセスを再起動するときは、シグナルを利用します。 デフォルトでは、SIGHUPを使って再起動を依頼します(ただし、どのシグナルを使うかはコマンドライン引数で変更できます)。
再起動したいときは、親プロセスであるServer::StarterにSIGHUPシグナルを送ります。 Unix系OSの場合は次のようにすればいいでしょう(起動時に--pid-file app.pid
というオプションを渡したので、親プロセスであるServer::StarterのプロセスIDはapp.pid
というファイルに格納されています)。
$ kill -HUP `cat app.pid` ⏎
多重起動していなければ、次のコマンドのようにプロセス名を指定してシグナルを送信してもかまいません。
$ pkill -HUP start_server ⏎
SIGHUPを受け取ったServer::Starterは、新しいプロセスを起動し、起動済みの子プロセスにはSIGTERMを送ります。 子プロセスであるサーバーが、「SIGTERMシグナルを受け取ったら、新規のリクエスト受け付けを停止し、現在処理中のリクエストが完了するまで待って終了する」という実装になっていれば、 再起動によるエラーに遭遇するユーザーを一人も出すことなく、ダウンタイムなしでサービスを更新できます。
Server::Starter対応のサーバーの実装例
サーバーをServer::Starterで起動するだけであれば、Server::Starterが提供するインタフェースを使ってnet.Listener
を取得し、そのポートを使って起動するようになっていれば問題ありません。 しかし、無停止でグレイスフル・リスタートするためには、「SIGTERMシグナルを受け取ったら、新規のリクエスト受け付けを停止し、現在処理中のリクエストが完了するまで待って終了する」という実装になっている必要があります。
ウェブサーバーであれば、そのようなグレイスフル・シャットダウンのためのメソッド(http.Server.Shutdown()
)が、Go言語のバージョン1.8から標準で提供されています。 サーバーをgoroutineで起動し、SIGTERMシグナルを受け取ったら外部から停止するメソッドを呼び出すようにすれば、簡単に実現できます。
次のコードがServer::Starter対応のウェブサーバーのための最小限4のコードです。
package main
import (
"context"
"fmt"
"github.com/lestrrat/go-server-starter/listener"
"net/http"
"os"
"os/signal"
"syscall"
)
func main() {
// シグナル初期化
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGTERM)
// Server::Starterからもらったソケットを確認
listeners, err := listener.ListenAll()
if err != nil {
panic(err)
}
// ウェブサーバーをgoroutineで起動
server := http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "server pid: %d %v\n", os.Getpid(), os.Environ())
}),
}
go server.Serve(listeners[0])
// SIGTERMを受け取ったら終了させる
<-signals
server.Shutdown(context.Background())
}
このようにSIGTERMをハンドルしてグレイスフル・シャットダウンする実装にすることには、Server::Starter対応だけでないメリットもあります。 たとえば、複数のコンテナをクラウド環境にデプロイしてサービスの環境を作り上げるオーケストレーションツールのKuberentesでも、サービスのローリングアップデート時にはSIGTERMシグナルが末端の各サービスに送られます。 そのため、SIGTERMをハンドルするようになっていれば、Kubernetesを使ってAWSやGKE、Azure上でもダウンタイムゼロでサービスが更新できることになります5。 なお、この場合はコンテナ内部での再起動になるため、ファイルディスクリプタを受け取る必要はありません。外部向けの公開ポートで起動し、シャットダウンを行儀よく行う実装にすればよいでしょう。
自由に再起動できるとはいっても、あくまでもウェブサービスなどのロジックに限定したレイヤーの話です。 データベース、メッセージキューなどの各種ストレージは、それほど簡単には再起動できません。 スキーマ変更を伴うような場合に、新旧の環境を自由自在に行き来きしたり、A/Bテスト的に新旧のバージョンを混在させることは、簡単ではないのです。
筆者が実際に話を聞いたことがあるのは、次のような方法による再起動です。
- 新しいバージョンに旧形式で書き出すためのカラムを用意し、古いバージョンでも使えるようにする
- MongoDBのようなKVSを使い、各データにバージョン番号を埋め込む。それと同時に、Erlangのホットデプロイのように新旧バージョンのデータのマイグレーションコードを完備して、データ構造の変更をアプリケーション側が任意で行えるようにする
ほかにもさまざまな方法が考えられると思いますが、メンテのコストや失敗時の影響の大きさを考えると、完全な無停止はあきらめてメンテナンス期間を設けるほうがリーズナブルだと言えます。
Go言語ランタイムにおけるシグナルの内部実装
マルチスレッドのプログラムだと、シグナルはその中のどれかのスレッドに届けられます。 マルチスレッドのプログラムでは、リソースアクセス時にロックを取得するスレッドがどれかわからないと容易にブロックしてプログラムがおかしくなってしまうため、シグナル処理用のスレッドとそれ以外のスレッドを分けるのが定石です。 Go言語でもそのようになっています。 主なコードはruntime/signal_unix.go
6にあります。
まず、各スレッドの初期化時に呼ばれる関数minitSignalMask()
ではシグナルのマスクを設定し、すべてのシグナルをブロック(受け取らない)ように設定しています。 この関数内部では、最後にsigprocmask()
関数を呼んでいます。 sigprocmask()
は、各OSごとにアセンブリ言語で実装されている関数7で、内部ではpthread_sigmask()
システムコールを呼び出しています。
シグナル処理用スレッドのみでシグナルを許可しているのは、ensureSigM()
関数です。 Go言語では、goroutineとOSスレッドを負荷状況に応じて柔軟に組み合わせられます(「N:Mモデル」と呼ばれています)が、この場合は特定のシグナルに限定したいので、goroutineが必ず特定のOSスレッドで実行されるように保証するruntime.LockOSThread()
関数を使っています。 signal
パッケージのシグナル設定のための関数を呼ぶと、runtime
パッケージのsignal_enable()
やsignal_disable()
といった関数が呼ばれます。 これらの関数は、ensureSigM()
が監視しているチャネルに更新情報を届けます。 ensureSigM()
は、これらの情報をsigprocmask()
関数を呼び出すことで、signal
パッケージがどのスレッドで実行されても問題なく、シグナル処理用のスレッドのシグナルのマスクを更新します。
シグナル受け取りにはsigaction()
システムコールが使われ、runtime/signal_sighandler.go
8のsighandler()
関数が呼ばれるようになっています。 この関数は、runtime/sigqueue.go
9のsigsend()
関数を呼び出し、共有のメモリ領域に受け取ったシグナル情報を書き出します。 ここまでがシグナル処理用スレッドで行われる処理です。 その後は同じファイル内のsignal_recv()
関数を呼び出すと、シグナル情報を返します。 signal
パッケージはこの関数を呼び出して、Notify()
で渡されたチャネルにシグナル情報を伝達します。
Windowsとシグナル
今までの連載で紹介してきたC言語の関数形式のシステムコールは、C言語の標準ライブラリではなくPOSIXのAPIということもあり、Windowsにはありません。 一方、signal()
はC言語の標準規格に入っているため、Windowsにも存在します10。 しかしWindowsで定義されているシグナルは、Ctrl + C
やプロセスの終了を除くと、プロセス内部の問題でCPUから発生される命令違反や数値演算エラーなどの例外に起因するソフトウェア割り込みが基本です。 とはいえ、C言語のISOの規格ではWindowsでも使えるシグナルだけが定義されており、「各実装はSIG
+ 大文字のシグナルを足してもよい」とされているため、WindowsもC言語の規格は完全に満たしています。
Windowsでは、signal()
でシグナル以外の例外もハンドルするために、構造化例外処理と呼ばれるAPIセットを提供しています11。 Go言語でも、Windowsにおけるシグナルの初期化コードでは、このAPIの一部であるAddVectoredContinueHandler()
12を使っています13。
Go言語のWindowsにおけるCtrl + C
のハンドリングでは、SetConsoleCtrlHandler()
14を使って、POSIX系OSにおける該当のシグナル(SIGINT)を模倣しています15。 この関数では強制的に処理されるシグナルしか扱えないため、その他のPOSIX系OSには存在しているシグナルのマスクを行うような処理は実装されていません16。
シグナル送信側のコードとしては、SIGKILL
時にTerminateProcess()
17を呼ぶコードのみがあります18。 これは強制終了なので、受け取り側でハンドリングはできません。
なお、GUIが前提のWindowsの世界では、他のプロセスを終了させるときはWM_CLOSE
イベントを送るのが作法とされています。 これはウインドウの閉じるボタンと同じイベントで、受け取った側では未保存のデータを保存するかどうかをユーザーに確認することができます。 プロセス間の通信はウインドウシステムのイベント側で行います。
まとめと次回予告
今回はプロセスの最後ということで、より強制力が高いプロセス間通信の手法であるシグナルを紹介しました。 シグナルにはたくさんの種類があり、用途ごとに使われ方が決まっています。 自分のプログラムで扱わなければならないシグナルはあまり多くはありませんが、 その中でも一番使う機会がありそうな用途として、グレイスフル・リスタートの原理とサービスの実装を紹介しました。
次回は、並行処理を取り上げます。
脚注
- 百日半狂乱「SIGSTOPで停止したプロセスにSIGTERMを送ってもプロセスが死なない?」: http://doi-t.hatenablog.com/entry/2013/12/07/023638↩
- https://github.com/lestrrat/go-server-starter/blob/master/starter.go#L458↩
- https://github.com/lestrrat/go-server-starter/blob/master/listener/listener.go#L68↩
- 本当に最低限の実装です。HTTP/2対応やGo 1.7環境と互換のある実装など、より実践的な内容はShogoさんのブログの「Go1.8のGraceful Shutdownとgo-gracedownの対応」を参照してください:https://shogo82148.github.io/blog/2017/01/21/golang-1-dot-8-graceful-shutdown/↩
- 筆者自身は大規模クラウドサービスでKubernetesを使ったことがありませんが、GoCon 2017のChris Broadfootのキーノートでそのような事例が紹介されました:https://github.com/broady/gocon-2017/blob/v0.15/main.go↩
- https://github.com/golang/go/blob/master/src/runtime/signal_unix.go↩
- https://github.com/golang/go/blob/master/src/runtime/sys_darwin_amd64.s#L228↩
- https://github.com/golang/go/blob/master/src/runtime/signal_sighandler.go↩
- https://github.com/golang/go/blob/master/src/runtime/sigqueue.go↩
- https://msdn.microsoft.com/en-us/library/xdkz3x12.aspx↩
- Oracle社のJavaのドキュメント「Windowsでの例外処理」がまとまっています: https://docs.oracle.com/javase/jp/8/docs/technotes/guides/troubleshoot/signals002.html↩
- https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms679273(v=vs.85).aspx↩
- https://github.com/golang/go/blob/master/src/runtime/signal_windows.go↩
- https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms686016(v=vs.85).aspx↩
- https://github.com/golang/go/blob/master/src/runtime/os_windows.go#L280↩
- https://github.com/golang/go/blob/master/src/runtime/signal_windows.go#L197↩
- https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms686714(v=vs.85).aspx↩
- https://github.com/golang/go/blob/master/src/os/exec_windows.go#L57↩
この連載の記事
-
第20回
プログラミング+
Go言語とコンテナ -
第19回
プログラミング+
Go言語のメモリ管理 -
第18回
プログラミング+
Go言語と並列処理(3) -
第17回
プログラミング+
Go言語と並列処理(2) -
第16回
プログラミング+
Go言語と並列処理 -
第14回
プログラミング+
Go言語で知るプロセス(2) -
第13回
プログラミング+
Go言語で知るプロセス(1) -
第12回
プログラミング+
ファイルシステムと、その上のGo言語の関数たち(3) -
第11回
プログラミング+
ファイルシステムと、その上のGo言語の関数たち(2) -
第10回
プログラミング+
ファイルシステムと、その上のGo言語の関数たち(1) - この連載の一覧へ