xref: /php-src/sapi/fpm/fpm/fpm_children.c (revision 418cdc0b)
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_cloexec(void)170 static int fpm_child_cloexec(void)
171 {
172 	/* get listening socket attributes so it can be extended */
173 	int attrs = fcntl(fpm_globals.listening_socket, F_GETFD);
174 	if (0 > attrs) {
175 		zlog(ZLOG_WARNING, "failed to get attributes of listening socket, errno: %d", errno);
176 		return -1;
177 	}
178 
179 	/* set CLOEXEC to prevent the descriptor leaking to child processes */
180 	if (0 > fcntl(fpm_globals.listening_socket, F_SETFD, attrs | FD_CLOEXEC)) {
181 		zlog(ZLOG_WARNING, "failed to change attribute of listening socket");
182 		return -1;
183 	}
184 
185 	return 0;
186 }
187 
fpm_child_init(struct fpm_worker_pool_s * wp)188 static void fpm_child_init(struct fpm_worker_pool_s *wp) /* {{{ */
189 {
190 	fpm_globals.max_requests = wp->config->pm_max_requests;
191 	fpm_globals.listening_socket = dup(wp->listening_socket);
192 
193 	if (0 > fpm_stdio_init_child(wp)  ||
194 	    0 > fpm_log_init_child(wp)    ||
195 	    0 > fpm_status_init_child(wp) ||
196 	    0 > fpm_unix_init_child(wp)   ||
197 	    0 > fpm_signals_init_child()  ||
198 	    0 > fpm_env_init_child(wp)    ||
199 	    0 > fpm_php_init_child(wp)    ||
200 	    0 > fpm_child_cloexec()) {
201 
202 		zlog(ZLOG_ERROR, "[pool %s] child failed to initialize", wp->config->name);
203 		exit(FPM_EXIT_SOFTWARE);
204 	}
205 }
206 /* }}} */
207 
fpm_children_free(struct fpm_child_s * child)208 int fpm_children_free(struct fpm_child_s *child) /* {{{ */
209 {
210 	struct fpm_child_s *next;
211 
212 	for (; child; child = next) {
213 		next = child->next;
214 		fpm_child_close(child, 0 /* in_event_loop */);
215 	}
216 
217 	return 0;
218 }
219 /* }}} */
220 
fpm_children_bury(void)221 void fpm_children_bury(void)
222 {
223 	int status;
224 	pid_t pid;
225 	struct fpm_child_s *child;
226 
227 	while ( (pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
228 		char buf[128];
229 		int severity = ZLOG_NOTICE;
230 		int restart_child = 1;
231 
232 		child = fpm_child_find(pid);
233 
234 		if (WIFEXITED(status)) {
235 
236 			snprintf(buf, sizeof(buf), "with code %d", WEXITSTATUS(status));
237 
238 			/* if it's been killed because of dynamic process management
239 			 * don't restart it automatically
240 			 */
241 			if (child && child->idle_kill) {
242 				restart_child = 0;
243 			}
244 
245 			if (WEXITSTATUS(status) != FPM_EXIT_OK) {
246 				severity = ZLOG_WARNING;
247 			}
248 
249 		} else if (WIFSIGNALED(status)) {
250 			const char *signame = fpm_signal_names[WTERMSIG(status)];
251 #ifdef WCOREDUMP
252 			const char *have_core = WCOREDUMP(status) ? " - core dumped" : "";
253 #else
254 			const char* have_core = "";
255 #endif
256 
257 			if (signame == NULL) {
258 				signame = "";
259 			}
260 
261 			snprintf(buf, sizeof(buf), "on signal %d (%s%s)", WTERMSIG(status), signame, have_core);
262 
263 			/* if it's been killed because of dynamic process management
264 			 * don't restart it automatically
265 			 */
266 			if (child && child->idle_kill && WTERMSIG(status) == SIGQUIT) {
267 				restart_child = 0;
268 			}
269 
270 			if (WTERMSIG(status) != SIGQUIT) { /* possible request loss */
271 				severity = ZLOG_WARNING;
272 			}
273 		} else if (WIFSTOPPED(status)) {
274 
275 			zlog(ZLOG_NOTICE, "child %d stopped for tracing", (int) pid);
276 
277 			if (child && child->tracer) {
278 				child->tracer(child);
279 			}
280 
281 			continue;
282 		}
283 
284 		if (child) {
285 			struct fpm_worker_pool_s *wp = child->wp;
286 			struct timeval tv1, tv2;
287 
288 			fpm_child_unlink(child);
289 
290 			fpm_scoreboard_proc_free(child);
291 
292 			fpm_clock_get(&tv1);
293 
294 			timersub(&tv1, &child->started, &tv2);
295 
296 			if (restart_child) {
297 				if (!fpm_pctl_can_spawn_children()) {
298 					severity = ZLOG_DEBUG;
299 				}
300 				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);
301 			} else {
302 				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);
303 			}
304 
305 			fpm_child_close(child, 1 /* in event_loop */);
306 
307 			fpm_pctl_child_exited();
308 
309 			if (last_faults && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS)) {
310 				time_t now = tv1.tv_sec;
311 				int restart_condition = 1;
312 				int i;
313 
314 				last_faults[fault++] = now;
315 
316 				if (fault == fpm_global_config.emergency_restart_threshold) {
317 					fault = 0;
318 				}
319 
320 				for (i = 0; i < fpm_global_config.emergency_restart_threshold; i++) {
321 					if (now - last_faults[i] > fpm_global_config.emergency_restart_interval) {
322 						restart_condition = 0;
323 						break;
324 					}
325 				}
326 
327 				if (restart_condition) {
328 
329 					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);
330 
331 					fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET);
332 				}
333 			}
334 
335 			if (restart_child) {
336 				fpm_children_make(wp, 1 /* in event loop */, 1, 0);
337 
338 				if (fpm_globals.is_child) {
339 					break;
340 				}
341 			}
342 		} else if (fpm_globals.parent_pid == 1) {
343 			zlog(ZLOG_DEBUG, "unknown child (%d) exited %s - most likely an orphan process (master process is the init process)", pid, buf);
344 		} else {
345 			zlog(ZLOG_WARNING, "unknown child (%d) exited %s - potentially a bug or pre exec child (e.g. s6-notifyoncheck)", pid, buf);
346 		}
347 	}
348 }
349 
fpm_resources_prepare(struct fpm_worker_pool_s * wp)350 static struct fpm_child_s *fpm_resources_prepare(struct fpm_worker_pool_s *wp) /* {{{ */
351 {
352 	struct fpm_child_s *c;
353 
354 	c = fpm_child_alloc();
355 
356 	if (!c) {
357 		zlog(ZLOG_ERROR, "[pool %s] unable to malloc new child", wp->config->name);
358 		return 0;
359 	}
360 
361 	c->wp = wp;
362 	c->fd_stdout = -1; c->fd_stderr = -1;
363 
364 	if (0 > fpm_stdio_prepare_pipes(c)) {
365 		fpm_child_free(c);
366 		return 0;
367 	}
368 
369 	if (0 > fpm_scoreboard_proc_alloc(c)) {
370 		fpm_stdio_discard_pipes(c);
371 		fpm_child_free(c);
372 		return 0;
373 	}
374 
375 	return c;
376 }
377 /* }}} */
378 
fpm_resources_discard(struct fpm_child_s * child)379 static void fpm_resources_discard(struct fpm_child_s *child) /* {{{ */
380 {
381 	fpm_scoreboard_proc_free(child);
382 	fpm_stdio_discard_pipes(child);
383 	fpm_child_free(child);
384 }
385 /* }}} */
386 
fpm_child_resources_use(struct fpm_child_s * child)387 static void fpm_child_resources_use(struct fpm_child_s *child) /* {{{ */
388 {
389 	struct fpm_worker_pool_s *wp;
390 	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
391 		if (wp == child->wp || wp == child->wp->shared) {
392 			continue;
393 		}
394 		fpm_scoreboard_free(wp);
395 	}
396 
397 	fpm_scoreboard_child_use(child, getpid());
398 	fpm_stdio_child_use_pipes(child);
399 	fpm_child_free(child);
400 }
401 /* }}} */
402 
fpm_parent_resources_use(struct fpm_child_s * child)403 static void fpm_parent_resources_use(struct fpm_child_s *child) /* {{{ */
404 {
405 	fpm_stdio_parent_use_pipes(child);
406 	fpm_child_link(child);
407 }
408 /* }}} */
409 
fpm_children_make(struct fpm_worker_pool_s * wp,int in_event_loop,int nb_to_spawn,int is_debug)410 int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn, int is_debug) /* {{{ */
411 {
412 	pid_t pid;
413 	struct fpm_child_s *child;
414 	int max;
415 	static int warned = 0;
416 
417 	if (wp->config->pm == PM_STYLE_DYNAMIC) {
418 		if (!in_event_loop) { /* starting */
419 			max = wp->config->pm_start_servers;
420 		} else {
421 			max = wp->running_children + nb_to_spawn;
422 		}
423 	} else if (wp->config->pm == PM_STYLE_ONDEMAND) {
424 		if (!in_event_loop) { /* starting */
425 			max = 0; /* do not create any child at startup */
426 		} else {
427 			max = wp->running_children + nb_to_spawn;
428 		}
429 	} else { /* PM_STYLE_STATIC */
430 		max = wp->config->pm_max_children;
431 	}
432 
433 	/*
434 	 * fork children while:
435 	 *   - fpm_pctl_can_spawn_children : FPM is running in a NORMAL state (aka not restart, stop or reload)
436 	 *   - wp->running_children < max  : there is less than the max process for the current pool
437 	 *   - (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max):
438 	 *     if fpm_global_config.process_max is set, FPM has not fork this number of processes (globally)
439 	 */
440 	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)) {
441 
442 		warned = 0;
443 		child = fpm_resources_prepare(wp);
444 
445 		if (!child) {
446 			return 2;
447 		}
448 
449 		zlog(ZLOG_DEBUG, "blocking signals before child birth");
450 		if (0 > fpm_signals_child_block()) {
451 			zlog(ZLOG_WARNING, "child may miss signals");
452 		}
453 
454 		pid = fork();
455 
456 		switch (pid) {
457 
458 			case 0 :
459 				fpm_child_resources_use(child);
460 				fpm_globals.is_child = 1;
461 				fpm_child_init(wp);
462 				return 0;
463 
464 			case -1 :
465 				zlog(ZLOG_DEBUG, "unblocking signals");
466 				fpm_signals_unblock();
467 				zlog(ZLOG_SYSERROR, "fork() failed");
468 
469 				fpm_resources_discard(child);
470 				return 2;
471 
472 			default :
473 				zlog(ZLOG_DEBUG, "unblocking signals, child born");
474 				fpm_signals_unblock();
475 				child->pid = pid;
476 				fpm_clock_get(&child->started);
477 				fpm_parent_resources_use(child);
478 
479 				zlog(is_debug ? ZLOG_DEBUG : ZLOG_NOTICE, "[pool %s] child %d started", wp->config->name, (int) pid);
480 		}
481 
482 	}
483 
484 	if (!warned && fpm_global_config.process_max > 0 && fpm_globals.running_children >= fpm_global_config.process_max) {
485 		if (wp->running_children < max) {
486 			warned = 1;
487 			zlog(ZLOG_WARNING, "The maximum number of processes has been reached. Please review your configuration and consider raising 'process.max'");
488 		}
489 	}
490 
491 	return 1; /* we are done */
492 }
493 /* }}} */
494 
fpm_children_create_initial(struct fpm_worker_pool_s * wp)495 int fpm_children_create_initial(struct fpm_worker_pool_s *wp) /* {{{ */
496 {
497 	if (wp->config->pm == PM_STYLE_ONDEMAND) {
498 		wp->ondemand_event = (struct fpm_event_s *)malloc(sizeof(struct fpm_event_s));
499 
500 		if (!wp->ondemand_event) {
501 			zlog(ZLOG_ERROR, "[pool %s] unable to malloc the ondemand socket event", wp->config->name);
502 			// FIXME handle crash
503 			return 1;
504 		}
505 
506 		memset(wp->ondemand_event, 0, sizeof(struct fpm_event_s));
507 		fpm_event_set(wp->ondemand_event, wp->listening_socket, FPM_EV_READ | FPM_EV_EDGE, fpm_pctl_on_socket_accept, wp);
508 		wp->socket_event_set = 1;
509 		fpm_event_add(wp->ondemand_event, 0);
510 
511 		return 1;
512 	}
513 	return fpm_children_make(wp, 0 /* not in event loop yet */, 0, 1);
514 }
515 /* }}} */
516 
fpm_children_init_main(void)517 int fpm_children_init_main(void)
518 {
519 	if (fpm_global_config.emergency_restart_threshold &&
520 		fpm_global_config.emergency_restart_interval) {
521 
522 		last_faults = malloc(sizeof(time_t) * fpm_global_config.emergency_restart_threshold);
523 
524 		if (!last_faults) {
525 			return -1;
526 		}
527 
528 		memset(last_faults, 0, sizeof(time_t) * fpm_global_config.emergency_restart_threshold);
529 	}
530 
531 	if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_children_cleanup, 0)) {
532 		return -1;
533 	}
534 
535 	return 0;
536 }
537