要約: リロードするとスリープが早く終了するため、fpm? のリロード操作を実装する方法を検討しました。
php-fpmのリロード処理
この記事はPHP7.0 fpmで解析しており、process_control_timeoutの設定が0ではありません。
リスタートシグナル
まず、 から、fpm のリロード操作が実際に USR2 シグナルを fpm プロセスに送信していることがわかります。
fpm のマスタープロセスでは、:
int fpm_signals_init_main() /* {{{ */{ struct sigactionact; // 。。。。。。 memset(&act, 0, sizeof(act)); act.sa_handler = sig_handler; sigfillset(&act.sa_mask); if (0 > sigaction(SIGTERM, &act, 0) || 0 > sigaction(SIGINT, &act, 0) || 0 > sigaction(SIGUSR1, &act, 0) || 0 > sigaction(SIGUSR2, &act, 0) || 0 > sigaction(SIGCHLD, &act, 0) || 0 > sigaction(SIGQUIT, &act, 0)) { zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()"); return -1; } return 0; }/* }}} */
を通じて信号処理関数を登録します。つまり、 を通じてすべての信号をブロックするように設定し、その後 sigaction を通じて対応する信号処理関数を設定します。
fpm をリロードすると、systemctl は fpm マスター プロセスに USR2 シグナルを送信し、次の関数を実行します。
static void sig_handler(int signo) /* {{{ */{ static const char sig_chars[NSIG + 1] = { [SIGTERM] = 'T', [SIGINT] = 'I', [SIGUSR1] = '1', [SIGUSR2] = '2', [SIGQUIT] = 'Q', [SIGCHLD] = 'C' }; char s; // *** s = sig_chars[signo]; zend_quiet_write(sp[1], &s, sizeof(s)); errno = saved_errno; }/* }}} */
重要なポイントは zend_quiet_write ( ) です。 sig_handler 関数は、文字列 2 を sp[1] に書き込みます。
ここで、sp[0] と sp[1] は作成されたローカル ソケットであることに注意してください。
マスターは再起動を開始します。信号が発生すると前の信号処理関数が呼び出されますが、プログラムのメイン ロジックは中断されません。では、fpm マスター プロセスはどのようにしてリロードを認識するのでしょうか?
答えは、マスター プロセスのイベント ループである にあります。
ループする前に、sp[0] を使用して struct fpm_event_s を監視対象の fd に追加する必要があります。
int fpm_event_set(struct fpm_event_s *ev, int fd, int flags, void (*callback)(struct fpm_event_s *, short, void *), void *arg) /* {{{ */{ if (!ev || !callback || fd < -1) { return -1; } memset(ev, 0, sizeof(struct fpm_event_s)); ev->fd = fd; ev->callback = callback; ev->arg = arg; ev->flags = flags; return 0; }/* }}} */
次に、コード内の ev であるこの struct fpm_event_s をfd fd を監視しました。
実際には、この追加プロセスは fpm のさまざまな非同期モデルにも関連しています (すべて fpm_event_module_s に対応する add メソッドによって実装されます)。たとえば、ev パラメーター全体が epoll_event の data.ptr に配置されます。 (ポールの追加を参照できます)
すべての fd (もちろんシグナル関連の fd だけではありません) が追加されたら、イベントが来るのを待つために使用できます。 (Epoll と Paul もそれぞれ wait メソッドを実装します)
わかりました。sig_handler に戻り、文字列 2 を sp[1] に書き込みます。 wait メソッドはシグナルを受信し、対応する ev を取得し、それを呼び出します。実際には、次のように呼び出されます:
static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg) /* {{{ */{ char c; int res, ret; int fd = ev->fd; do { res = read(fd, &c, 1); switch (c) { // 。。。。。。 case '2' : /* SIGUSR2 */ zlog(ZLOG_DEBUG, "received SIGUSR2"); zlog(ZLOG_NOTICE, "Reloading in progress ..."); fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET); break; } if (fpm_globals.is_child) { break; } } while (1); return; }/* }}} */
文字列 2 を受信した場合、
fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET)
実際には:
void fpm_pctl(int new_state, int action) /* {{{ */{ switch (action) { case FPM_PCTL_ACTION_SET : //。。。。。。 fpm_signal_sent = 0; fpm_state = new_state; zlog(ZLOG_DEBUG, "switching to '%s' state", fpm_state_names[fpm_state]); /* fall down */ case FPM_PCTL_ACTION_TIMEOUT : fpm_pctl_action_next(); break; //。。。。。 } }/* }}} */
つまり、fpm_state を FPM_PCTL_STATE_RELOADING に設定した後、ブレークはなく実行が続行されます。
static void fpm_pctl_action_next() /* {{{ */ { int sig, timeout; if (!fpm_globals.running_children) { fpm_pctl_action_last(); } if (fpm_signal_sent == 0) { if (fpm_state == FPM_PCTL_STATE_TERMINATING) { sig = SIGTERM; } else { sig = SIGQUIT; } timeout = fpm_global_config.process_control_timeout; } else { if (fpm_signal_sent == SIGQUIT) { sig = SIGTERM; } else { sig = SIGKILL; } timeout = 1; } fpm_pctl_kill_all(sig); fpm_signal_sent = sig; fpm_pctl_timeout_set(timeout); } /* }}} */
つまり、すべての子プロセスに SIGQUIT シグナルが送信されます。
ここにはもう 1 つありますが、これについては後で説明します。
子プロセスがシグナルを処理します。親プロセスがシグナルの送信を完了したら、子プロセスがシグナルを処理します。
子プロセスは、処理のために sig_soft_quit にのみ渡すことができます。子プロセスが初期化された後、SIGQUIT シグナルが受信され、sig_soft_quit によって処理されます。最後の呼び出し処理:
void fcgi_terminate(void){ in_shutdown = 1; }
は、in_shutdown を 1 に設定することです。
子プロセスが終了します。子プロセスのループ本体は fcgi_accept_request にあります。さらに判定は in_shutdown にあります。1 であれば直接終了します。
タイムアウト処理は前述しました。次の操作が実行されました。
fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_TIMEOUT);
この状況では、子プロセスが直接終了しました。
睡眠が中断されるのはなぜですか?システムが sleep を呼び出していることがわかります (php_sleep は sleep のマクロです):
/* {{{ proto void sleep(int seconds) Delay for a given number of seconds */PHP_FUNCTION(sleep) { zend_longnum; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &num) == FAILURE) { RETURN_FALSE; } php_sleep((unsigned int)num); }/* }}} */
sleep 関数が実行されると、プロセスのステータスは S になります:
interruptiblesleepこのときの信号 先ほどの SIGQUIT などの信号を即座にトリガーして処理し、それが終了するとスリープが実行されたことがわかります。
次のように書いたため:
<b>sleep</b>() makesthecallingthreadsleepuntil <i>seconds</i> secondshave elapsedor a signalarriveswhichis not ignored.
そのため、シグナルがスリープを中断したとしても、スリープをスキップして実行を継続するだけであることに注意してください。
以上がphp-fpmのリロード処理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。