PostgreSQLのシグナル周り

# 環境
PostgreSQL 10.5

# イントロ
非常に重いSQLが実行されてしまい、DBのパフォーマンスが著しく低下する時がありました。対応するバックエンドプロセスのクエリをキャンセルしようと思い、`pg_cancel_backend(pid int)`を使ったのですが、クエリはキャンセルされず、`pg_terminate_backend(pid int)`を使っても、プロセスは殺せず、そのクエリの実行をお願いしたプロセスを起動しなおしても、状況は変わらず、最終的にDBを再起動しました。`pg_cancel_backend()`はプロセスにsigintを、`pg_terminate_backend()`はsigtermを送っているのは知ってましたが、なぜ適切にクエリがキャンセルされたり、プロセスが殺されたりしないのだろう？と思って実装を見てみました。

# シグナル周りのコード
まず、PostgreSQLにはpostmasterと呼ばれるクライアントからの接続を受け付けて、必要に応じてバックエンドプロセスをforkするデーモンプロセスが存在します（もっと言うとPostgreSQLを構成する全てのプロセスはpostmasterからforkされます）。postmasterでは初期化の際にシグナルのブロックやシグナルハンドラーの登録も行います。バックエンドプロセスはpostmasterからforkするので、sigintとsigtermにバックエンドプロセス用のシグナルハンドラーを登録します。ソースコードで言うと全てのエントリーポイントである`main()`の最後の方で、`PostmasterMain(argc, argv)`が呼ばれます。この中で非常に多くの処理（オプションの処理やソケットの作成など）を行います。その一つがシグナル周りの処理です。シグナルを登録している部分は以下（本当はもっとたくさんのシグナルについて見ていますが、ここではsigtermとsigintについて主に見ていきましょう）。

```C
pqinitmask();
PG_SETMASK(&BlockSig);
												 * have children do same */
pqsignal_no_restart(SIGINT, pmdie); /* send SIGTERM and shut down */
pqsignal_no_restart(SIGQUIT, pmdie);	/* send SIGQUIT and die */
pqsignal_no_restart(SIGTERM, pmdie);	/* wait for children and shut down */
```

上から見ていきます。`pqinitmask()`ではグローバル変数である`BlockSig`（と`StartupBlockSig`も）を初期化しています。そして、`PG_SETMASK(mask)`は`sigprocmask(SIG_SETMASK, mask, NULL)`のマクロです。つまり、シグナルのブロックを行っています。`pqsignal_no_restart()`は`sigaction(2)`の第一引数となる`sigaction`構造体の`sa_flags`を0にして、かつ引数で渡された関数をシグナルハンドラーとして`sigaction(2)`を呼びます。一方、`pqsignal()`は`sa_flags`を`SA_RESTART`にして、引数で渡された関数をシグナルハンドラーとして`sigaction(2)`を呼びます。

`PostmasterMain(argc, argv)`は最終的にクライアントからの接続を待ち受けるループに入ります。それが`ServerLoop()`です。`ServerLoop()`の中では`select(2)`でクライアントからの接続を見ています。そして、以下の部分で新しいプロセスを生成し、以下そのプロセスがクエリを処理します。これがバックエンドプロセスです。


```C
Port	   *port;

port = ConnCreate(ListenSocket[i]);
if (port)
{
	BackendStartup(port);

	/*
	 * We no longer need the open socket or port structure
	 * in this process
	 */
	StreamClose(port->sock);
	ConnFree(port);
}
```

読み書き可能なソケットを確認した後に入ってくるパスです。ここで、`BackendStartup()`して、バックエンドプロセスを生成しています。実際に`fork(2)`しているところを見ます。

```C
pid = fork_process();
if (pid == 0)				/* child */
{
	free(bn);

	/* Detangle from postmaster */
	InitPostmasterChild();

	/* Close the postmaster's sockets */
	ClosePostmasterPorts(false);

	/* Perform additional initialization and collect startup packet */
	BackendInitialize(port);

	/* And run the backend */
	BackendRun(port);
}
```

pidが0になるのは子プロセスなので、バックエンドプロセスが通るパスです。この中で`BackendInitialize()`でシグナル周りの処理があります。

```C
pqsignal(SIGTERM, startup_die);
pqsignal(SIGQUIT, startup_die);
InitializeTimeouts();		/* establishes SIGALRM handler */
PG_SETMASK(&StartupBlockSig);
```
`StartupBlockSig`はpgsignal.cを見れば分かりますが、`BlockSig`からsigtermとsigquitとsigalarmを除いたものです。なるほど、そうするとバックエンドプロセスは起動時には`startup_die`をシグナルハンドラーとして登録しています。これは`exit(2)`します。

次に`BackendRun()`を見てみます。この中では`PostgresMain()`が呼ばれるのですが、そこにクエリの処理中のシグナルハンドラーを登録している部分があります。以下の部分です。

```C
pqsignal(SIGINT, StatementCancelHandler);	/* cancel current query */
pqsignal(SIGTERM, die); /* cancel current query and exit */
```

それぞれのシグナルハンドラーは以下のような実装になっています。

```C
void
StatementCancelHandler(SIGNAL_ARGS)
{
	int			save_errno = errno;

	/*
	 * Don't joggle the elbow of proc_exit
	 */
	if (!proc_exit_inprogress)
	{
		InterruptPending = true;
		QueryCancelPending = true;
	}

	/* If we're still here, waken anything waiting on the process latch */
	SetLatch(MyLatch);

	errno = save_errno;
}
```

```C
void
die(SIGNAL_ARGS)
{
	int			save_errno = errno;

	/* Don't joggle the elbow of proc_exit */
	if (!proc_exit_inprogress)
	{
		InterruptPending = true;
		ProcDiePending = true;
	}

	/* If we're still here, waken anything waiting on the process latch */
	SetLatch(MyLatch);

	/*
	 * If we're in single user mode, we want to quit immediately - we can't
	 * rely on latches as they wouldn't work when stdin/stdout is a file.
	 * Rather ugly, but it's unlikely to be worthwhile to invest much more
	 * effort just for the benefit of single user mode.
	 */
	if (DoingCommandRead && whereToSendOutput != DestRemote)
		ProcessInterrupts();

	errno = save_errno;
}
```

色々やっていますが、重要なのはフラグ（`InterruptPending`, `QueryCancelPending`, `ProcDiePending`）を立ててる部分です。逆にクエリのキャンセルやプロセスを殺す処理などはありません。どういうことかと言うと、これがPostgreSQLのシグナルに対するハンドリングなのです。シグナルを受け取ると、シグナルハンドラーでやるのはフラグを立てるだけ。そして、そのフラグを定期的に見て（もちろんクエリをキャンセルしたり、プロセスを殺したりしても問題ない部分で）、シグナルに対応した処理を行います。

`PostgresMain()`ではクエリの実行も行うのですが、至る所で`CHECK_FOR_INTERRUPTS()`というマクロが呼ばれます。その定義は以下。

```C
#define CHECK_FOR_INTERRUPTS() \
do { \
	if (InterruptPending) \
		ProcessInterrupts(); \
} while(0)
#else
```

`InterruptPending`がtrueなら、`ProcessInterrupts()`を実行します。もしsigintやsigtermのシグナルハンドラーが実行されると`InterruptPending`はtrueになるので、`ProcessInterrupts()`が実行されます。sigtermのシグナルハンドラーが実行されると、`ProcDiePending`はtrueとなっていますが、`ProcessInterrupts()`内では、`ProcDiePending`はtrueとなっていると、ほぼ必ず`ereport()`がエラーレベルが`FATAL`で呼ばれます。`ereport()`がエラーレベルが`FATAL`で呼ばれると`exit(2)`するので、プロセスが殺されることになります。

# まとめ
pg_terminate_backend()でプロセスがすぐに殺されない理由は以下の二点です。

 + `sigprocmask(2)`でシグナルがブロックされている時がある
 + シグナルハンドラーが実際に実行されてもフラグを立てるだけなので、何らかの理由で処理が止まっている場合、実際の期待する処理が行われない。