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