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