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