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