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