xref: /PHP-8.1/sapi/fpm/fpm/fpm_process_ctl.c (revision 10295373)
1 	/* (c) 2007,2008 Andrei Nigmatulin */
2 
3 #include "fpm_config.h"
4 
5 #include <sys/types.h>
6 #include <signal.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 
10 #include "fpm.h"
11 #include "fpm_clock.h"
12 #include "fpm_children.h"
13 #include "fpm_signals.h"
14 #include "fpm_events.h"
15 #include "fpm_process_ctl.h"
16 #include "fpm_cleanup.h"
17 #include "fpm_request.h"
18 #include "fpm_worker_pool.h"
19 #include "fpm_scoreboard.h"
20 #include "fpm_sockets.h"
21 #include "fpm_stdio.h"
22 #include "zlog.h"
23 
24 
25 static int fpm_state = FPM_PCTL_STATE_NORMAL;
26 static int fpm_signal_sent = 0;
27 
28 
29 static const char *fpm_state_names[] = {
30 	[FPM_PCTL_STATE_NORMAL] = "normal",
31 	[FPM_PCTL_STATE_RELOADING] = "reloading",
32 	[FPM_PCTL_STATE_TERMINATING] = "terminating",
33 	[FPM_PCTL_STATE_FINISHING] = "finishing"
34 };
35 
36 static int saved_argc;
37 static char **saved_argv;
38 
fpm_pctl_cleanup(int which,void * arg)39 static void fpm_pctl_cleanup(int which, void *arg) /* {{{ */
40 {
41 	int i;
42 	if (which != FPM_CLEANUP_PARENT_EXEC) {
43 		for (i = 0; i < saved_argc; i++) {
44 			free(saved_argv[i]);
45 		}
46 		free(saved_argv);
47 	}
48 }
49 /* }}} */
50 
51 static struct fpm_event_s pctl_event;
52 
fpm_pctl_action(struct fpm_event_s * ev,short which,void * arg)53 static void fpm_pctl_action(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
54 {
55 	fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_TIMEOUT);
56 }
57 /* }}} */
58 
fpm_pctl_timeout_set(int sec)59 static int fpm_pctl_timeout_set(int sec) /* {{{ */
60 {
61 	fpm_event_set_timer(&pctl_event, 0, &fpm_pctl_action, NULL);
62 	fpm_event_add(&pctl_event, sec * 1000);
63 	return 0;
64 }
65 /* }}} */
66 
fpm_pctl_exit(void)67 static void fpm_pctl_exit(void)
68 {
69 	zlog(ZLOG_NOTICE, "exiting, bye-bye!");
70 
71 	fpm_conf_unlink_pid();
72 	fpm_cleanups_run(FPM_CLEANUP_PARENT_EXIT_MAIN);
73 	exit(FPM_EXIT_OK);
74 }
75 
76 #define optional_arg(c) (saved_argc > c ? ", \"" : ""), (saved_argc > c ? saved_argv[c] : ""), (saved_argc > c ? "\"" : "")
77 
fpm_pctl_exec(void)78 static void fpm_pctl_exec(void)
79 {
80 	zlog(ZLOG_DEBUG, "Blocking some signals before reexec");
81 	if (0 > fpm_signals_block()) {
82 		zlog(ZLOG_WARNING, "concurrent reloads may be unstable");
83 	}
84 
85 	zlog(ZLOG_NOTICE, "reloading: execvp(\"%s\", {\"%s\""
86 			"%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
87 			"%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
88 		"})",
89 		saved_argv[0], saved_argv[0],
90 		optional_arg(1),
91 		optional_arg(2),
92 		optional_arg(3),
93 		optional_arg(4),
94 		optional_arg(5),
95 		optional_arg(6),
96 		optional_arg(7),
97 		optional_arg(8),
98 		optional_arg(9),
99 		optional_arg(10)
100 	);
101 
102 	fpm_cleanups_run(FPM_CLEANUP_PARENT_EXEC);
103 
104 	fpm_stdio_restore_original_stderr(1);
105 
106 	execvp(saved_argv[0], saved_argv);
107 	zlog(ZLOG_SYSERROR, "failed to reload: execvp() failed");
108 	exit(FPM_EXIT_SOFTWARE);
109 }
110 
fpm_pctl_action_last(void)111 static void fpm_pctl_action_last(void)
112 {
113 	switch (fpm_state) {
114 		case FPM_PCTL_STATE_RELOADING:
115 			fpm_pctl_exec();
116 			break;
117 
118 		case FPM_PCTL_STATE_FINISHING:
119 		case FPM_PCTL_STATE_TERMINATING:
120 			fpm_pctl_exit();
121 			break;
122 	}
123 }
124 
fpm_pctl_kill(pid_t pid,int how)125 int fpm_pctl_kill(pid_t pid, int how) /* {{{ */
126 {
127 	int s = 0;
128 
129 	switch (how) {
130 		case FPM_PCTL_TERM :
131 			s = SIGTERM;
132 			break;
133 		case FPM_PCTL_STOP :
134 			s = SIGSTOP;
135 			break;
136 		case FPM_PCTL_CONT :
137 			s = SIGCONT;
138 			break;
139 		case FPM_PCTL_QUIT :
140 			s = SIGQUIT;
141 			break;
142 		case FPM_PCTL_KILL:
143 			s = SIGKILL;
144 			break;
145 		default :
146 			break;
147 	}
148 	return kill(pid, s);
149 }
150 /* }}} */
151 
fpm_pctl_kill_all(int signo)152 void fpm_pctl_kill_all(int signo) /* {{{ */
153 {
154 	struct fpm_worker_pool_s *wp;
155 	int alive_children = 0;
156 
157 	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
158 		struct fpm_child_s *child;
159 
160 		for (child = wp->children; child; child = child->next) {
161 			int res = kill(child->pid, signo);
162 
163 			zlog(ZLOG_DEBUG, "[pool %s] sending signal %d %s to child %d",
164 				child->wp->config->name, signo,
165 				fpm_signal_names[signo] ? fpm_signal_names[signo] : "", (int) child->pid);
166 
167 			if (res == 0) {
168 				++alive_children;
169 			}
170 		}
171 	}
172 
173 	if (alive_children) {
174 		zlog(ZLOG_DEBUG, "%d child(ren) still alive", alive_children);
175 	}
176 }
177 /* }}} */
178 
fpm_pctl_action_next(void)179 static void fpm_pctl_action_next(void)
180 {
181 	int sig, timeout;
182 
183 	if (!fpm_globals.running_children) {
184 		fpm_pctl_action_last();
185 	}
186 
187 	if (fpm_signal_sent == 0) {
188 		if (fpm_state == FPM_PCTL_STATE_TERMINATING) {
189 			sig = SIGTERM;
190 		} else {
191 			sig = SIGQUIT;
192 		}
193 		timeout = fpm_global_config.process_control_timeout;
194 	} else {
195 		if (fpm_signal_sent == SIGQUIT) {
196 			sig = SIGTERM;
197 		} else {
198 			sig = SIGKILL;
199 		}
200 		timeout = 1;
201 	}
202 
203 	fpm_pctl_kill_all(sig);
204 	fpm_signal_sent = sig;
205 	fpm_pctl_timeout_set(timeout);
206 }
207 
fpm_pctl(int new_state,int action)208 void fpm_pctl(int new_state, int action) /* {{{ */
209 {
210 	switch (action) {
211 		case FPM_PCTL_ACTION_SET :
212 			if (fpm_state == new_state) { /* already in progress - just ignore duplicate signal */
213 				return;
214 			}
215 
216 			switch (fpm_state) { /* check which states can be overridden */
217 				case FPM_PCTL_STATE_NORMAL :
218 					/* 'normal' can be overridden by any other state */
219 					break;
220 				case FPM_PCTL_STATE_RELOADING :
221 					/* 'reloading' can be overridden by 'finishing' */
222 					if (new_state == FPM_PCTL_STATE_FINISHING) break;
223 					ZEND_FALLTHROUGH;
224 				case FPM_PCTL_STATE_FINISHING :
225 					/* 'reloading' and 'finishing' can be overridden by 'terminating' */
226 					if (new_state == FPM_PCTL_STATE_TERMINATING) break;
227 					ZEND_FALLTHROUGH;
228 				case FPM_PCTL_STATE_TERMINATING :
229 					/* nothing can override 'terminating' state */
230 					zlog(ZLOG_DEBUG, "not switching to '%s' state, because already in '%s' state",
231 						fpm_state_names[new_state], fpm_state_names[fpm_state]);
232 					return;
233 				/* TODO Add EMPTY_SWITCH_DEFAULT_CASE? */
234 			}
235 
236 			fpm_signal_sent = 0;
237 			fpm_state = new_state;
238 
239 			zlog(ZLOG_DEBUG, "switching to '%s' state", fpm_state_names[fpm_state]);
240 			ZEND_FALLTHROUGH;
241 
242 		case FPM_PCTL_ACTION_TIMEOUT :
243 			fpm_pctl_action_next();
244 			break;
245 		case FPM_PCTL_ACTION_LAST_CHILD_EXITED :
246 			fpm_pctl_action_last();
247 			break;
248 
249 	}
250 }
251 /* }}} */
252 
fpm_pctl_can_spawn_children(void)253 int fpm_pctl_can_spawn_children(void)
254 {
255 	return fpm_state == FPM_PCTL_STATE_NORMAL;
256 }
257 
fpm_pctl_child_exited(void)258 int fpm_pctl_child_exited(void)
259 {
260 	if (fpm_state == FPM_PCTL_STATE_NORMAL) {
261 		return 0;
262 	}
263 
264 	if (!fpm_globals.running_children) {
265 		fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_LAST_CHILD_EXITED);
266 	}
267 	return 0;
268 }
269 
fpm_pctl_init_main(void)270 int fpm_pctl_init_main(void)
271 {
272 	int i;
273 
274 	saved_argc = fpm_globals.argc;
275 	saved_argv = malloc(sizeof(char *) * (saved_argc + 1));
276 
277 	if (!saved_argv) {
278 		return -1;
279 	}
280 
281 	for (i = 0; i < saved_argc; i++) {
282 		saved_argv[i] = strdup(fpm_globals.argv[i]);
283 
284 		if (!saved_argv[i]) {
285 			return -1;
286 		}
287 	}
288 
289 	saved_argv[i] = 0;
290 
291 	if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_pctl_cleanup, 0)) {
292 		return -1;
293 	}
294 	return 0;
295 }
296 
fpm_pctl_check_request_timeout(struct timeval * now)297 static void fpm_pctl_check_request_timeout(struct timeval *now) /* {{{ */
298 {
299 	struct fpm_worker_pool_s *wp;
300 
301 	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
302 		int track_finished = wp->config->request_terminate_timeout_track_finished;
303 		int terminate_timeout = wp->config->request_terminate_timeout;
304 		int slowlog_timeout = wp->config->request_slowlog_timeout;
305 		struct fpm_child_s *child;
306 
307 		if (terminate_timeout || slowlog_timeout) {
308 			for (child = wp->children; child; child = child->next) {
309 				fpm_request_check_timed_out(child, now, terminate_timeout, slowlog_timeout, track_finished);
310 			}
311 		}
312 	}
313 }
314 /* }}} */
315 
fpm_pctl_kill_idle_child(struct fpm_child_s * child)316 static void fpm_pctl_kill_idle_child(struct fpm_child_s *child) /* {{{ */
317 {
318 	if (child->idle_kill) {
319 		fpm_pctl_kill(child->pid, FPM_PCTL_KILL);
320 	} else {
321 		child->idle_kill = true;
322 		fpm_pctl_kill(child->pid, FPM_PCTL_QUIT);
323 	}
324 }
325 /* }}} */
326 
fpm_pctl_perform_idle_server_maintenance(struct timeval * now)327 static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{ */
328 {
329 	struct fpm_worker_pool_s *wp;
330 
331 	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
332 		struct fpm_child_s *child;
333 		struct fpm_child_s *last_idle_child = NULL;
334 		int idle = 0;
335 		int active = 0;
336 		int children_to_fork;
337 		unsigned cur_lq = 0;
338 
339 		if (wp->config == NULL) continue;
340 
341 		/* update status structure for all PMs */
342 		if (wp->listen_address_domain == FPM_AF_INET) {
343 			if (0 > fpm_socket_get_listening_queue(wp->listening_socket, &cur_lq, NULL)) {
344 				cur_lq = 0;
345 #if 0
346 			} else {
347 				if (cur_lq > 0) {
348 					if (!wp->warn_lq) {
349 						zlog(ZLOG_WARNING, "[pool %s] listening queue is not empty, #%d requests are waiting to be served, consider raising pm.max_children setting (%d)", wp->config->name, cur_lq, wp->config->pm_max_children);
350 						wp->warn_lq = 1;
351 					}
352 				} else {
353 					wp->warn_lq = 0;
354 				}
355 #endif
356 			}
357 		}
358 
359 		fpm_scoreboard_update_begin(wp->scoreboard);
360 
361 		for (child = wp->children; child; child = child->next) {
362 			if (fpm_request_is_idle(child)) {
363 				if (last_idle_child == NULL) {
364 					last_idle_child = child;
365 				} else {
366 					if (timercmp(&child->started, &last_idle_child->started, <)) {
367 						last_idle_child = child;
368 					}
369 				}
370 				idle++;
371 			} else {
372 				active++;
373 			}
374 		}
375 
376 		fpm_scoreboard_update_commit(idle, active, cur_lq, -1, -1, -1, 0, FPM_SCOREBOARD_ACTION_SET, wp->scoreboard);
377 
378 		/* this is specific to PM_STYLE_ONDEMAND */
379 		if (wp->config->pm == PM_STYLE_ONDEMAND) {
380 			struct timeval last, now;
381 
382 			zlog(ZLOG_DEBUG, "[pool %s] currently %d active children, %d spare children", wp->config->name, active, idle);
383 
384 			if (!last_idle_child) continue;
385 
386 			fpm_request_last_activity(last_idle_child, &last);
387 			fpm_clock_get(&now);
388 			if (last.tv_sec < now.tv_sec - wp->config->pm_process_idle_timeout) {
389 				fpm_pctl_kill_idle_child(last_idle_child);
390 			}
391 
392 			continue;
393 		}
394 
395 		/* the rest is only used by PM_STYLE_DYNAMIC */
396 		if (wp->config->pm != PM_STYLE_DYNAMIC) continue;
397 
398 		zlog(ZLOG_DEBUG, "[pool %s] currently %d active children, %d spare children, %d running children. Spawning rate %d", wp->config->name, active, idle, wp->running_children, wp->idle_spawn_rate);
399 
400 		if (idle > wp->config->pm_max_spare_servers && last_idle_child) {
401 			fpm_pctl_kill_idle_child(last_idle_child);
402 			wp->idle_spawn_rate = 1;
403 			continue;
404 		}
405 
406 		if (idle < wp->config->pm_min_spare_servers) {
407 			if (wp->running_children >= wp->config->pm_max_children) {
408 				if (!wp->warn_max_children && !wp->shared) {
409 					fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
410 					zlog(ZLOG_WARNING, "[pool %s] server reached pm.max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
411 					wp->warn_max_children = 1;
412 				}
413 				wp->idle_spawn_rate = 1;
414 				continue;
415 			}
416 
417 			if (wp->idle_spawn_rate >= 8) {
418 				zlog(ZLOG_WARNING, "[pool %s] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning %d children, there are %d idle, and %d total children", wp->config->name, wp->idle_spawn_rate, idle, wp->running_children);
419 			}
420 
421 			/* compute the number of idle process to spawn */
422 			children_to_fork = MIN(wp->idle_spawn_rate, wp->config->pm_min_spare_servers - idle);
423 
424 			/* get sure it won't exceed max_children */
425 			children_to_fork = MIN(children_to_fork, wp->config->pm_max_children - wp->running_children);
426 			if (children_to_fork <= 0) {
427 				if (!wp->warn_max_children && !wp->shared) {
428 					fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
429 					zlog(ZLOG_WARNING, "[pool %s] server reached pm.max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
430 					wp->warn_max_children = 1;
431 				}
432 				wp->idle_spawn_rate = 1;
433 				continue;
434 			}
435 			wp->warn_max_children = 0;
436 
437 			fpm_children_make(wp, 1, children_to_fork, 1);
438 
439 			/* if it's a child, stop here without creating the next event
440 			 * this event is reserved to the master process
441 			 */
442 			if (fpm_globals.is_child) {
443 				return;
444 			}
445 
446 			zlog(ZLOG_DEBUG, "[pool %s] %d child(ren) have been created dynamically", wp->config->name, children_to_fork);
447 
448 			/* Double the spawn rate for the next iteration */
449 			if (wp->idle_spawn_rate < wp->config->pm_max_spawn_rate) {
450 				wp->idle_spawn_rate *= 2;
451 			}
452 			continue;
453 		}
454 		wp->idle_spawn_rate = 1;
455 	}
456 }
457 /* }}} */
458 
fpm_pctl_heartbeat(struct fpm_event_s * ev,short which,void * arg)459 void fpm_pctl_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
460 {
461 	static struct fpm_event_s heartbeat;
462 	struct timeval now;
463 
464 	if (fpm_globals.parent_pid != getpid()) {
465 		return; /* sanity check */
466 	}
467 
468 	if (which == FPM_EV_TIMEOUT) {
469 		fpm_clock_get(&now);
470 		fpm_pctl_check_request_timeout(&now);
471 		return;
472 	}
473 
474 	/* ensure heartbeat is not lower than FPM_PCTL_MIN_HEARTBEAT */
475 	fpm_globals.heartbeat = MAX(fpm_globals.heartbeat, FPM_PCTL_MIN_HEARTBEAT);
476 
477 	/* first call without setting to initialize the timer */
478 	zlog(ZLOG_DEBUG, "heartbeat have been set up with a timeout of %dms", fpm_globals.heartbeat);
479 	fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_heartbeat, NULL);
480 	fpm_event_add(&heartbeat, fpm_globals.heartbeat);
481 }
482 /* }}} */
483 
fpm_pctl_perform_idle_server_maintenance_heartbeat(struct fpm_event_s * ev,short which,void * arg)484 void fpm_pctl_perform_idle_server_maintenance_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
485 {
486 	static struct fpm_event_s heartbeat;
487 	struct timeval now;
488 
489 	if (fpm_globals.parent_pid != getpid()) {
490 		return; /* sanity check */
491 	}
492 
493 	if (which == FPM_EV_TIMEOUT) {
494 		fpm_clock_get(&now);
495 		if (fpm_pctl_can_spawn_children()) {
496 			fpm_pctl_perform_idle_server_maintenance(&now);
497 
498 			/* if it's a child, stop here without creating the next event
499 			 * this event is reserved to the master process
500 			 */
501 			if (fpm_globals.is_child) {
502 				return;
503 			}
504 		}
505 		return;
506 	}
507 
508 	/* first call without setting which to initialize the timer */
509 	fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_perform_idle_server_maintenance_heartbeat, NULL);
510 	fpm_event_add(&heartbeat, FPM_IDLE_SERVER_MAINTENANCE_HEARTBEAT);
511 }
512 /* }}} */
513 
fpm_pctl_on_socket_accept(struct fpm_event_s * ev,short which,void * arg)514 void fpm_pctl_on_socket_accept(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
515 {
516 	struct fpm_worker_pool_s *wp = (struct fpm_worker_pool_s *)arg;
517 	struct fpm_child_s *child;
518 
519 
520 	if (fpm_globals.parent_pid != getpid()) {
521 		/* prevent a event race condition when child process
522 		 * have not set up its own event loop */
523 		return;
524 	}
525 
526 	wp->socket_event_set = 0;
527 
528 /*	zlog(ZLOG_DEBUG, "[pool %s] heartbeat running_children=%d", wp->config->name, wp->running_children);*/
529 
530 	if (wp->running_children >= wp->config->pm_max_children) {
531 		if (!wp->warn_max_children && !wp->shared) {
532 			fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
533 			zlog(ZLOG_WARNING, "[pool %s] server reached max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
534 			wp->warn_max_children = 1;
535 		}
536 
537 		return;
538 	}
539 
540 	for (child = wp->children; child; child = child->next) {
541 		/* if there is at least on idle child, it will handle the connection, stop here */
542 		if (fpm_request_is_idle(child)) {
543 			return;
544 		}
545 	}
546 	wp->warn_max_children = 0;
547 	fpm_children_make(wp, 1, 1, 1);
548 
549 	if (fpm_globals.is_child) {
550 		return;
551 	}
552 
553 	zlog(ZLOG_DEBUG, "[pool %s] got accept without idle child available .... I forked", wp->config->name);
554 }
555 /* }}} */
556