Summary: reload will cause sleep to end early, so we explored how to implement the reload operation of fpm?
The reload process of php-fpm
This article is analyzed under PHP7.0 fpm, and the process_control_timeout setting is not 0.
Restart signal
First of all, we can know from , that the reload operation of fpm actually sends the USR2 signal to the fpm process.
In the master process of fpm, the signal processing function is registered through :
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; }/* }}} */
In short, set to block all signals through , and then set the corresponding signal processing function through sigaction.
When we reload fpm, systemctl sends the USR2 signal to the fpm master process and executes the function:
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; }/* }}} */
The key point is zend_quiet_write, which is . The sig_handler function writes a string 2 to sp[1].
It should be noted here that sp[0] and sp[1] are local sockets created through.
Master starts to restart. The previous signal processing function will be called when the signal occurs, but the main logic of the program will still not be disrupted. So how does the fpm master process know to reload?
The answer lies in , which is the event loop of the master process.
Before looping, we need to use sp[0] to add a struct fpm_event_s to the monitored 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; }/* }}} */
Then add this struct fpm_event_s, which is the ev in the code, to the monitored fd fd in.
Actually, this adding process is also related to different asynchronous models of fpm (all implemented by the add method corresponding to fpm_event_module_s). For example, the entire ev parameter is placed in the data.ptr of epoll_event. (You can refer to the add of poll)
When all fds are added (of course not just signal-related fds), we can use to wait for the event to come. (Epoll and poll also implement the wait method respectively)
Okay, go back to sig_handler and write a string 2 to sp[1]. The wait method receives the signal, gets the corresponding ev, and calls it. In fact, it is called, which is:
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; }/* }}} */
If string 2 is received, execute
fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET)
In fact:
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; //。。。。。 } }/* }}} */
That is, after setting fpm_state to FPM_PCTL_STATE_RELOADING, there is no break and the execution continues:
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); } /* }}} */
That is, sending the SIGQUIT signal to all child processes.
There is another one here, which will be discussed later.
The child process handles the signal. After the parent process has finished sending the signal, it is time for the child process to process it.
The child process can only be handed over to sig_soft_quit for processing. After the child process is initialized, the SIGQUIT signal is received, which is processed by sig_soft_quit. The final call processing:
void fcgi_terminate(void){ in_shutdown = 1; }
is to set in_shutdown to 1.
The child process exits. The loop body of the child process is in fcgi_accept_request. There is more judgment in_shutdown. If it is 1, it will exit directly:
Timeout processing is mentioned earlier. The following operations were performed:
fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_TIMEOUT);
Under this condition, the child process was directly exited.
Why is sleep interrupted? We can see that the system calls sleep (php_sleep is a macro of 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); }/* }}} */
When the sleep function is executed, the status of the process is S:
interruptiblesleep Once there is a signal at this time Trigger and process the signal immediately, such as the SIGQUIT we just mentioned. After it is over, it is found that sleep has been executed.
Because I wrote:
sleep() makesthecallingthreadsleepuntil seconds secondshave elapsedor a signalarriveswhichis not ignored.
It should be noted that, so even if the signal interrupts sleep, it just skips sleep and continues execution.
The above is the detailed content of The reload process of php-fpm. For more information, please follow other related articles on the PHP Chinese website!