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