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