xref: /PHP-7.2/sapi/fpm/fpm/fpm_children.c (revision 006355c9)
1 	/* $Id: fpm_children.c,v 1.32.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 <sys/wait.h>
8 #include <time.h>
9 #include <unistd.h>
10 #include <string.h>
11 #include <stdio.h>
12 
13 #include "fpm.h"
14 #include "fpm_children.h"
15 #include "fpm_signals.h"
16 #include "fpm_worker_pool.h"
17 #include "fpm_sockets.h"
18 #include "fpm_process_ctl.h"
19 #include "fpm_php.h"
20 #include "fpm_conf.h"
21 #include "fpm_cleanup.h"
22 #include "fpm_events.h"
23 #include "fpm_clock.h"
24 #include "fpm_stdio.h"
25 #include "fpm_unix.h"
26 #include "fpm_env.h"
27 #include "fpm_scoreboard.h"
28 #include "fpm_status.h"
29 #include "fpm_log.h"
30 
31 #include "zlog.h"
32 
33 static time_t *last_faults;
34 static int fault;
35 
fpm_children_cleanup(int which,void * arg)36 static void fpm_children_cleanup(int which, void *arg) /* {{{ */
37 {
38 	free(last_faults);
39 }
40 /* }}} */
41 
fpm_child_alloc()42 static struct fpm_child_s *fpm_child_alloc() /* {{{ */
43 {
44 	struct fpm_child_s *ret;
45 
46 	ret = malloc(sizeof(struct fpm_child_s));
47 
48 	if (!ret) {
49 		return 0;
50 	}
51 
52 	memset(ret, 0, sizeof(*ret));
53 	ret->scoreboard_i = -1;
54 	return ret;
55 }
56 /* }}} */
57 
fpm_child_free(struct fpm_child_s * child)58 static void fpm_child_free(struct fpm_child_s *child) /* {{{ */
59 {
60 	free(child);
61 }
62 /* }}} */
63 
fpm_child_close(struct fpm_child_s * child,int in_event_loop)64 static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{ */
65 {
66 	if (child->fd_stdout != -1) {
67 		if (in_event_loop) {
68 			fpm_event_fire(&child->ev_stdout);
69 		}
70 		if (child->fd_stdout != -1) {
71 			close(child->fd_stdout);
72 		}
73 	}
74 
75 	if (child->fd_stderr != -1) {
76 		if (in_event_loop) {
77 			fpm_event_fire(&child->ev_stderr);
78 		}
79 		if (child->fd_stderr != -1) {
80 			close(child->fd_stderr);
81 		}
82 	}
83 
84 	fpm_child_free(child);
85 }
86 /* }}} */
87 
fpm_child_link(struct fpm_child_s * child)88 static void fpm_child_link(struct fpm_child_s *child) /* {{{ */
89 {
90 	struct fpm_worker_pool_s *wp = child->wp;
91 
92 	++wp->running_children;
93 	++fpm_globals.running_children;
94 
95 	child->next = wp->children;
96 	if (child->next) {
97 		child->next->prev = child;
98 	}
99 	child->prev = 0;
100 	wp->children = child;
101 }
102 /* }}} */
103 
fpm_child_unlink(struct fpm_child_s * child)104 static void fpm_child_unlink(struct fpm_child_s *child) /* {{{ */
105 {
106 	--child->wp->running_children;
107 	--fpm_globals.running_children;
108 
109 	if (child->prev) {
110 		child->prev->next = child->next;
111 	} else {
112 		child->wp->children = child->next;
113 	}
114 
115 	if (child->next) {
116 		child->next->prev = child->prev;
117 	}
118 }
119 /* }}} */
120 
fpm_child_find(pid_t pid)121 static struct fpm_child_s *fpm_child_find(pid_t pid) /* {{{ */
122 {
123 	struct fpm_worker_pool_s *wp;
124 	struct fpm_child_s *child = 0;
125 
126 	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
127 
128 		for (child = wp->children; child; child = child->next) {
129 			if (child->pid == pid) {
130 				break;
131 			}
132 		}
133 
134 		if (child) break;
135 	}
136 
137 	if (!child) {
138 		return 0;
139 	}
140 
141 	return child;
142 }
143 /* }}} */
144 
fpm_child_init(struct fpm_worker_pool_s * wp)145 static void fpm_child_init(struct fpm_worker_pool_s *wp) /* {{{ */
146 {
147 	fpm_globals.max_requests = wp->config->pm_max_requests;
148 	fpm_globals.listening_socket = dup(wp->listening_socket);
149 
150 	if (0 > fpm_stdio_init_child(wp)  ||
151 	    0 > fpm_log_init_child(wp)    ||
152 	    0 > fpm_status_init_child(wp) ||
153 	    0 > fpm_unix_init_child(wp)   ||
154 	    0 > fpm_signals_init_child()  ||
155 	    0 > fpm_env_init_child(wp)    ||
156 	    0 > fpm_php_init_child(wp)) {
157 
158 		zlog(ZLOG_ERROR, "[pool %s] child failed to initialize", wp->config->name);
159 		exit(FPM_EXIT_SOFTWARE);
160 	}
161 }
162 /* }}} */
163 
fpm_children_free(struct fpm_child_s * child)164 int fpm_children_free(struct fpm_child_s *child) /* {{{ */
165 {
166 	struct fpm_child_s *next;
167 
168 	for (; child; child = next) {
169 		next = child->next;
170 		fpm_child_close(child, 0 /* in_event_loop */);
171 	}
172 
173 	return 0;
174 }
175 /* }}} */
176 
fpm_children_bury()177 void fpm_children_bury() /* {{{ */
178 {
179 	int status;
180 	pid_t pid;
181 	struct fpm_child_s *child;
182 
183 	while ( (pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
184 		char buf[128];
185 		int severity = ZLOG_NOTICE;
186 		int restart_child = 1;
187 
188 		child = fpm_child_find(pid);
189 
190 		if (WIFEXITED(status)) {
191 
192 			snprintf(buf, sizeof(buf), "with code %d", WEXITSTATUS(status));
193 
194 			/* if it's been killed because of dynamic process management
195 			 * don't restart it automaticaly
196 			 */
197 			if (child && child->idle_kill) {
198 				restart_child = 0;
199 			}
200 
201 			if (WEXITSTATUS(status) != FPM_EXIT_OK) {
202 				severity = ZLOG_WARNING;
203 			}
204 
205 		} else if (WIFSIGNALED(status)) {
206 			const char *signame = fpm_signal_names[WTERMSIG(status)];
207 #ifdef WCOREDUMP
208 			const char *have_core = WCOREDUMP(status) ? " - core dumped" : "";
209 #else
210 			const char* have_core = "";
211 #endif
212 
213 			if (signame == NULL) {
214 				signame = "";
215 			}
216 
217 			snprintf(buf, sizeof(buf), "on signal %d (%s%s)", WTERMSIG(status), signame, have_core);
218 
219 			/* if it's been killed because of dynamic process management
220 			 * don't restart it automaticaly
221 			 */
222 			if (child && child->idle_kill && WTERMSIG(status) == SIGQUIT) {
223 				restart_child = 0;
224 			}
225 
226 			if (WTERMSIG(status) != SIGQUIT) { /* possible request loss */
227 				severity = ZLOG_WARNING;
228 			}
229 		} else if (WIFSTOPPED(status)) {
230 
231 			zlog(ZLOG_NOTICE, "child %d stopped for tracing", (int) pid);
232 
233 			if (child && child->tracer) {
234 				child->tracer(child);
235 			}
236 
237 			continue;
238 		}
239 
240 		if (child) {
241 			struct fpm_worker_pool_s *wp = child->wp;
242 			struct timeval tv1, tv2;
243 
244 			fpm_child_unlink(child);
245 
246 			fpm_scoreboard_proc_free(wp->scoreboard, child->scoreboard_i);
247 
248 			fpm_clock_get(&tv1);
249 
250 			timersub(&tv1, &child->started, &tv2);
251 
252 			if (restart_child) {
253 				if (!fpm_pctl_can_spawn_children()) {
254 					severity = ZLOG_DEBUG;
255 				}
256 				zlog(severity, "[pool %s] child %d exited %s after %ld.%06d seconds from start", child->wp->config->name, (int) pid, buf, tv2.tv_sec, (int) tv2.tv_usec);
257 			} else {
258 				zlog(ZLOG_DEBUG, "[pool %s] child %d has been killed by the process management after %ld.%06d seconds from start", child->wp->config->name, (int) pid, tv2.tv_sec, (int) tv2.tv_usec);
259 			}
260 
261 			fpm_child_close(child, 1 /* in event_loop */);
262 
263 			fpm_pctl_child_exited();
264 
265 			if (last_faults && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS)) {
266 				time_t now = tv1.tv_sec;
267 				int restart_condition = 1;
268 				int i;
269 
270 				last_faults[fault++] = now;
271 
272 				if (fault == fpm_global_config.emergency_restart_threshold) {
273 					fault = 0;
274 				}
275 
276 				for (i = 0; i < fpm_global_config.emergency_restart_threshold; i++) {
277 					if (now - last_faults[i] > fpm_global_config.emergency_restart_interval) {
278 						restart_condition = 0;
279 						break;
280 					}
281 				}
282 
283 				if (restart_condition) {
284 
285 					zlog(ZLOG_WARNING, "failed processes threshold (%d in %d sec) is reached, initiating reload", fpm_global_config.emergency_restart_threshold, fpm_global_config.emergency_restart_interval);
286 
287 					fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET);
288 				}
289 			}
290 
291 			if (restart_child) {
292 				fpm_children_make(wp, 1 /* in event loop */, 1, 0);
293 
294 				if (fpm_globals.is_child) {
295 					break;
296 				}
297 			}
298 		} else {
299 			zlog(ZLOG_ALERT, "oops, unknown child (%d) exited %s. Please open a bug report (https://bugs.php.net).", pid, buf);
300 		}
301 	}
302 }
303 /* }}} */
304 
fpm_resources_prepare(struct fpm_worker_pool_s * wp)305 static struct fpm_child_s *fpm_resources_prepare(struct fpm_worker_pool_s *wp) /* {{{ */
306 {
307 	struct fpm_child_s *c;
308 
309 	c = fpm_child_alloc();
310 
311 	if (!c) {
312 		zlog(ZLOG_ERROR, "[pool %s] unable to malloc new child", wp->config->name);
313 		return 0;
314 	}
315 
316 	c->wp = wp;
317 	c->fd_stdout = -1; c->fd_stderr = -1;
318 
319 	if (0 > fpm_stdio_prepare_pipes(c)) {
320 		fpm_child_free(c);
321 		return 0;
322 	}
323 
324 	if (0 > fpm_scoreboard_proc_alloc(wp->scoreboard, &c->scoreboard_i)) {
325 		fpm_stdio_discard_pipes(c);
326 		fpm_child_free(c);
327 		return 0;
328 	}
329 
330 	return c;
331 }
332 /* }}} */
333 
fpm_resources_discard(struct fpm_child_s * child)334 static void fpm_resources_discard(struct fpm_child_s *child) /* {{{ */
335 {
336 	fpm_scoreboard_proc_free(child->wp->scoreboard, child->scoreboard_i);
337 	fpm_stdio_discard_pipes(child);
338 	fpm_child_free(child);
339 }
340 /* }}} */
341 
fpm_child_resources_use(struct fpm_child_s * child)342 static void fpm_child_resources_use(struct fpm_child_s *child) /* {{{ */
343 {
344 	struct fpm_worker_pool_s *wp;
345 	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
346 		if (wp == child->wp) {
347 			continue;
348 		}
349 		fpm_scoreboard_free(wp->scoreboard);
350 	}
351 
352 	fpm_scoreboard_child_use(child->wp->scoreboard, child->scoreboard_i, getpid());
353 	fpm_stdio_child_use_pipes(child);
354 	fpm_child_free(child);
355 }
356 /* }}} */
357 
fpm_parent_resources_use(struct fpm_child_s * child)358 static void fpm_parent_resources_use(struct fpm_child_s *child) /* {{{ */
359 {
360 	fpm_stdio_parent_use_pipes(child);
361 	fpm_child_link(child);
362 }
363 /* }}} */
364 
fpm_children_make(struct fpm_worker_pool_s * wp,int in_event_loop,int nb_to_spawn,int is_debug)365 int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn, int is_debug) /* {{{ */
366 {
367 	pid_t pid;
368 	struct fpm_child_s *child;
369 	int max;
370 	static int warned = 0;
371 
372 	if (wp->config->pm == PM_STYLE_DYNAMIC) {
373 		if (!in_event_loop) { /* starting */
374 			max = wp->config->pm_start_servers;
375 		} else {
376 			max = wp->running_children + nb_to_spawn;
377 		}
378 	} else if (wp->config->pm == PM_STYLE_ONDEMAND) {
379 		if (!in_event_loop) { /* starting */
380 			max = 0; /* do not create any child at startup */
381 		} else {
382 			max = wp->running_children + nb_to_spawn;
383 		}
384 	} else { /* PM_STYLE_STATIC */
385 		max = wp->config->pm_max_children;
386 	}
387 
388 	/*
389 	 * fork children while:
390 	 *   - fpm_pctl_can_spawn_children : FPM is running in a NORMAL state (aka not restart, stop or reload)
391 	 *   - wp->running_children < max  : there is less than the max process for the current pool
392 	 *   - (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max):
393 	 *     if fpm_global_config.process_max is set, FPM has not fork this number of processes (globaly)
394 	 */
395 	while (fpm_pctl_can_spawn_children() && wp->running_children < max && (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max)) {
396 
397 		warned = 0;
398 		child = fpm_resources_prepare(wp);
399 
400 		if (!child) {
401 			return 2;
402 		}
403 
404 		pid = fork();
405 
406 		switch (pid) {
407 
408 			case 0 :
409 				fpm_child_resources_use(child);
410 				fpm_globals.is_child = 1;
411 				fpm_child_init(wp);
412 				return 0;
413 
414 			case -1 :
415 				zlog(ZLOG_SYSERROR, "fork() failed");
416 
417 				fpm_resources_discard(child);
418 				return 2;
419 
420 			default :
421 				child->pid = pid;
422 				fpm_clock_get(&child->started);
423 				fpm_parent_resources_use(child);
424 
425 				zlog(is_debug ? ZLOG_DEBUG : ZLOG_NOTICE, "[pool %s] child %d started", wp->config->name, (int) pid);
426 		}
427 
428 	}
429 
430 	if (!warned && fpm_global_config.process_max > 0 && fpm_globals.running_children >= fpm_global_config.process_max) {
431                if (wp->running_children < max) {
432                        warned = 1;
433                        zlog(ZLOG_WARNING, "The maximum number of processes has been reached. Please review your configuration and consider raising 'process.max'");
434                }
435 	}
436 
437 	return 1; /* we are done */
438 }
439 /* }}} */
440 
fpm_children_create_initial(struct fpm_worker_pool_s * wp)441 int fpm_children_create_initial(struct fpm_worker_pool_s *wp) /* {{{ */
442 {
443 	if (wp->config->pm == PM_STYLE_ONDEMAND) {
444 		wp->ondemand_event = (struct fpm_event_s *)malloc(sizeof(struct fpm_event_s));
445 
446 		if (!wp->ondemand_event) {
447 			zlog(ZLOG_ERROR, "[pool %s] unable to malloc the ondemand socket event", wp->config->name);
448 			// FIXME handle crash
449 			return 1;
450 		}
451 
452 		memset(wp->ondemand_event, 0, sizeof(struct fpm_event_s));
453 		fpm_event_set(wp->ondemand_event, wp->listening_socket, FPM_EV_READ | FPM_EV_EDGE, fpm_pctl_on_socket_accept, wp);
454 		wp->socket_event_set = 1;
455 		fpm_event_add(wp->ondemand_event, 0);
456 
457 		return 1;
458 	}
459 	return fpm_children_make(wp, 0 /* not in event loop yet */, 0, 1);
460 }
461 /* }}} */
462 
fpm_children_init_main()463 int fpm_children_init_main() /* {{{ */
464 {
465 	if (fpm_global_config.emergency_restart_threshold &&
466 		fpm_global_config.emergency_restart_interval) {
467 
468 		last_faults = malloc(sizeof(time_t) * fpm_global_config.emergency_restart_threshold);
469 
470 		if (!last_faults) {
471 			return -1;
472 		}
473 
474 		memset(last_faults, 0, sizeof(time_t) * fpm_global_config.emergency_restart_threshold);
475 	}
476 
477 	if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_children_cleanup, 0)) {
478 		return -1;
479 	}
480 
481 	return 0;
482 }
483 /* }}} */
484