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