1 /* (c) 2007,2008 Andrei Nigmatulin */
2
3 #include "fpm_config.h"
4
5 #include <sys/types.h>
6 #include <signal.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9
10 #include "fpm.h"
11 #include "fpm_clock.h"
12 #include "fpm_children.h"
13 #include "fpm_signals.h"
14 #include "fpm_events.h"
15 #include "fpm_process_ctl.h"
16 #include "fpm_cleanup.h"
17 #include "fpm_request.h"
18 #include "fpm_worker_pool.h"
19 #include "fpm_scoreboard.h"
20 #include "fpm_sockets.h"
21 #include "zlog.h"
22
23
24 static int fpm_state = FPM_PCTL_STATE_NORMAL;
25 static int fpm_signal_sent = 0;
26
27
28 static const char *fpm_state_names[] = {
29 [FPM_PCTL_STATE_NORMAL] = "normal",
30 [FPM_PCTL_STATE_RELOADING] = "reloading",
31 [FPM_PCTL_STATE_TERMINATING] = "terminating",
32 [FPM_PCTL_STATE_FINISHING] = "finishing"
33 };
34
35 static int saved_argc;
36 static char **saved_argv;
37
fpm_pctl_cleanup(int which,void * arg)38 static void fpm_pctl_cleanup(int which, void *arg) /* {{{ */
39 {
40 int i;
41 if (which != FPM_CLEANUP_PARENT_EXEC) {
42 for (i = 0; i < saved_argc; i++) {
43 free(saved_argv[i]);
44 }
45 free(saved_argv);
46 }
47 }
48 /* }}} */
49
50 static struct fpm_event_s pctl_event;
51
fpm_pctl_action(struct fpm_event_s * ev,short which,void * arg)52 static void fpm_pctl_action(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
53 {
54 fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_TIMEOUT);
55 }
56 /* }}} */
57
fpm_pctl_timeout_set(int sec)58 static int fpm_pctl_timeout_set(int sec) /* {{{ */
59 {
60 fpm_event_set_timer(&pctl_event, 0, &fpm_pctl_action, NULL);
61 fpm_event_add(&pctl_event, sec * 1000);
62 return 0;
63 }
64 /* }}} */
65
fpm_pctl_exit()66 static void fpm_pctl_exit() /* {{{ */
67 {
68 zlog(ZLOG_NOTICE, "exiting, bye-bye!");
69
70 fpm_conf_unlink_pid();
71 fpm_cleanups_run(FPM_CLEANUP_PARENT_EXIT_MAIN);
72 exit(FPM_EXIT_OK);
73 }
74 /* }}} */
75
76 #define optional_arg(c) (saved_argc > c ? ", \"" : ""), (saved_argc > c ? saved_argv[c] : ""), (saved_argc > c ? "\"" : "")
77
fpm_pctl_exec()78 static void fpm_pctl_exec() /* {{{ */
79 {
80 zlog(ZLOG_DEBUG, "Blocking some signals before reexec");
81 if (0 > fpm_signals_block()) {
82 zlog(ZLOG_WARNING, "concurrent reloads may be unstable");
83 }
84
85 zlog(ZLOG_NOTICE, "reloading: execvp(\"%s\", {\"%s\""
86 "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
87 "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
88 "})",
89 saved_argv[0], saved_argv[0],
90 optional_arg(1),
91 optional_arg(2),
92 optional_arg(3),
93 optional_arg(4),
94 optional_arg(5),
95 optional_arg(6),
96 optional_arg(7),
97 optional_arg(8),
98 optional_arg(9),
99 optional_arg(10)
100 );
101
102 fpm_cleanups_run(FPM_CLEANUP_PARENT_EXEC);
103 execvp(saved_argv[0], saved_argv);
104 zlog(ZLOG_SYSERROR, "failed to reload: execvp() failed");
105 exit(FPM_EXIT_SOFTWARE);
106 }
107 /* }}} */
108
fpm_pctl_action_last()109 static void fpm_pctl_action_last() /* {{{ */
110 {
111 switch (fpm_state) {
112 case FPM_PCTL_STATE_RELOADING:
113 fpm_pctl_exec();
114 break;
115
116 case FPM_PCTL_STATE_FINISHING:
117 case FPM_PCTL_STATE_TERMINATING:
118 fpm_pctl_exit();
119 break;
120 }
121 }
122 /* }}} */
123
fpm_pctl_kill(pid_t pid,int how)124 int fpm_pctl_kill(pid_t pid, int how) /* {{{ */
125 {
126 int s = 0;
127
128 switch (how) {
129 case FPM_PCTL_TERM :
130 s = SIGTERM;
131 break;
132 case FPM_PCTL_STOP :
133 s = SIGSTOP;
134 break;
135 case FPM_PCTL_CONT :
136 s = SIGCONT;
137 break;
138 case FPM_PCTL_QUIT :
139 s = SIGQUIT;
140 break;
141 default :
142 break;
143 }
144 return kill(pid, s);
145 }
146 /* }}} */
147
fpm_pctl_kill_all(int signo)148 void fpm_pctl_kill_all(int signo) /* {{{ */
149 {
150 struct fpm_worker_pool_s *wp;
151 int alive_children = 0;
152
153 for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
154 struct fpm_child_s *child;
155
156 for (child = wp->children; child; child = child->next) {
157 int res = kill(child->pid, signo);
158
159 zlog(ZLOG_DEBUG, "[pool %s] sending signal %d %s to child %d",
160 child->wp->config->name, signo,
161 fpm_signal_names[signo] ? fpm_signal_names[signo] : "", (int) child->pid);
162
163 if (res == 0) {
164 ++alive_children;
165 }
166 }
167 }
168
169 if (alive_children) {
170 zlog(ZLOG_DEBUG, "%d child(ren) still alive", alive_children);
171 }
172 }
173 /* }}} */
174
fpm_pctl_action_next()175 static void fpm_pctl_action_next() /* {{{ */
176 {
177 int sig, timeout;
178
179 if (!fpm_globals.running_children) {
180 fpm_pctl_action_last();
181 }
182
183 if (fpm_signal_sent == 0) {
184 if (fpm_state == FPM_PCTL_STATE_TERMINATING) {
185 sig = SIGTERM;
186 } else {
187 sig = SIGQUIT;
188 }
189 timeout = fpm_global_config.process_control_timeout;
190 } else {
191 if (fpm_signal_sent == SIGQUIT) {
192 sig = SIGTERM;
193 } else {
194 sig = SIGKILL;
195 }
196 timeout = 1;
197 }
198
199 fpm_pctl_kill_all(sig);
200 fpm_signal_sent = sig;
201 fpm_pctl_timeout_set(timeout);
202 }
203 /* }}} */
204
fpm_pctl(int new_state,int action)205 void fpm_pctl(int new_state, int action) /* {{{ */
206 {
207 switch (action) {
208 case FPM_PCTL_ACTION_SET :
209 if (fpm_state == new_state) { /* already in progress - just ignore duplicate signal */
210 return;
211 }
212
213 switch (fpm_state) { /* check which states can be overridden */
214 case FPM_PCTL_STATE_NORMAL :
215 /* 'normal' can be overridden by any other state */
216 break;
217 case FPM_PCTL_STATE_RELOADING :
218 /* 'reloading' can be overridden by 'finishing' */
219 if (new_state == FPM_PCTL_STATE_FINISHING) break;
220 case FPM_PCTL_STATE_FINISHING :
221 /* 'reloading' and 'finishing' can be overridden by 'terminating' */
222 if (new_state == FPM_PCTL_STATE_TERMINATING) break;
223 case FPM_PCTL_STATE_TERMINATING :
224 /* nothing can override 'terminating' state */
225 zlog(ZLOG_DEBUG, "not switching to '%s' state, because already in '%s' state",
226 fpm_state_names[new_state], fpm_state_names[fpm_state]);
227 return;
228 }
229
230 fpm_signal_sent = 0;
231 fpm_state = new_state;
232
233 zlog(ZLOG_DEBUG, "switching to '%s' state", fpm_state_names[fpm_state]);
234 /* fall down */
235
236 case FPM_PCTL_ACTION_TIMEOUT :
237 fpm_pctl_action_next();
238 break;
239 case FPM_PCTL_ACTION_LAST_CHILD_EXITED :
240 fpm_pctl_action_last();
241 break;
242
243 }
244 }
245 /* }}} */
246
fpm_pctl_can_spawn_children()247 int fpm_pctl_can_spawn_children() /* {{{ */
248 {
249 return fpm_state == FPM_PCTL_STATE_NORMAL;
250 }
251 /* }}} */
252
fpm_pctl_child_exited()253 int fpm_pctl_child_exited() /* {{{ */
254 {
255 if (fpm_state == FPM_PCTL_STATE_NORMAL) {
256 return 0;
257 }
258
259 if (!fpm_globals.running_children) {
260 fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_LAST_CHILD_EXITED);
261 }
262 return 0;
263 }
264 /* }}} */
265
fpm_pctl_init_main()266 int fpm_pctl_init_main() /* {{{ */
267 {
268 int i;
269
270 saved_argc = fpm_globals.argc;
271 saved_argv = malloc(sizeof(char *) * (saved_argc + 1));
272
273 if (!saved_argv) {
274 return -1;
275 }
276
277 for (i = 0; i < saved_argc; i++) {
278 saved_argv[i] = strdup(fpm_globals.argv[i]);
279
280 if (!saved_argv[i]) {
281 return -1;
282 }
283 }
284
285 saved_argv[i] = 0;
286
287 if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_pctl_cleanup, 0)) {
288 return -1;
289 }
290 return 0;
291 }
292 /* }}} */
293
fpm_pctl_check_request_timeout(struct timeval * now)294 static void fpm_pctl_check_request_timeout(struct timeval *now) /* {{{ */
295 {
296 struct fpm_worker_pool_s *wp;
297
298 for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
299 int track_finished = wp->config->request_terminate_timeout_track_finished;
300 int terminate_timeout = wp->config->request_terminate_timeout;
301 int slowlog_timeout = wp->config->request_slowlog_timeout;
302 struct fpm_child_s *child;
303
304 if (terminate_timeout || slowlog_timeout) {
305 for (child = wp->children; child; child = child->next) {
306 fpm_request_check_timed_out(child, now, terminate_timeout, slowlog_timeout, track_finished);
307 }
308 }
309 }
310 }
311 /* }}} */
312
fpm_pctl_perform_idle_server_maintenance(struct timeval * now)313 static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{ */
314 {
315 struct fpm_worker_pool_s *wp;
316
317 for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
318 struct fpm_child_s *child;
319 struct fpm_child_s *last_idle_child = NULL;
320 int idle = 0;
321 int active = 0;
322 int children_to_fork;
323 unsigned cur_lq = 0;
324
325 if (wp->config == NULL) continue;
326
327 for (child = wp->children; child; child = child->next) {
328 if (fpm_request_is_idle(child)) {
329 if (last_idle_child == NULL) {
330 last_idle_child = child;
331 } else {
332 if (timercmp(&child->started, &last_idle_child->started, <)) {
333 last_idle_child = child;
334 }
335 }
336 idle++;
337 } else {
338 active++;
339 }
340 }
341
342 /* update status structure for all PMs */
343 if (wp->listen_address_domain == FPM_AF_INET) {
344 if (0 > fpm_socket_get_listening_queue(wp->listening_socket, &cur_lq, NULL)) {
345 cur_lq = 0;
346 #if 0
347 } else {
348 if (cur_lq > 0) {
349 if (!wp->warn_lq) {
350 zlog(ZLOG_WARNING, "[pool %s] listening queue is not empty, #%d requests are waiting to be served, consider raising pm.max_children setting (%d)", wp->config->name, cur_lq, wp->config->pm_max_children);
351 wp->warn_lq = 1;
352 }
353 } else {
354 wp->warn_lq = 0;
355 }
356 #endif
357 }
358 }
359 fpm_scoreboard_update(idle, active, cur_lq, -1, -1, -1, 0, FPM_SCOREBOARD_ACTION_SET, wp->scoreboard);
360
361 /* this is specific to PM_STYLE_ONDEMAND */
362 if (wp->config->pm == PM_STYLE_ONDEMAND) {
363 struct timeval last, now;
364
365 zlog(ZLOG_DEBUG, "[pool %s] currently %d active children, %d spare children", wp->config->name, active, idle);
366
367 if (!last_idle_child) continue;
368
369 fpm_request_last_activity(last_idle_child, &last);
370 fpm_clock_get(&now);
371 if (last.tv_sec < now.tv_sec - wp->config->pm_process_idle_timeout) {
372 last_idle_child->idle_kill = 1;
373 fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
374 }
375
376 continue;
377 }
378
379 /* the rest is only used by PM_STYLE_DYNAMIC */
380 if (wp->config->pm != PM_STYLE_DYNAMIC) continue;
381
382 zlog(ZLOG_DEBUG, "[pool %s] currently %d active children, %d spare children, %d running children. Spawning rate %d", wp->config->name, active, idle, wp->running_children, wp->idle_spawn_rate);
383
384 if (idle > wp->config->pm_max_spare_servers && last_idle_child) {
385 last_idle_child->idle_kill = 1;
386 fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT);
387 wp->idle_spawn_rate = 1;
388 continue;
389 }
390
391 if (idle < wp->config->pm_min_spare_servers) {
392 if (wp->running_children >= wp->config->pm_max_children) {
393 if (!wp->warn_max_children) {
394 fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
395 zlog(ZLOG_WARNING, "[pool %s] server reached pm.max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
396 wp->warn_max_children = 1;
397 }
398 wp->idle_spawn_rate = 1;
399 continue;
400 }
401
402 if (wp->idle_spawn_rate >= 8) {
403 zlog(ZLOG_WARNING, "[pool %s] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning %d children, there are %d idle, and %d total children", wp->config->name, wp->idle_spawn_rate, idle, wp->running_children);
404 }
405
406 /* compute the number of idle process to spawn */
407 children_to_fork = MIN(wp->idle_spawn_rate, wp->config->pm_min_spare_servers - idle);
408
409 /* get sure it won't exceed max_children */
410 children_to_fork = MIN(children_to_fork, wp->config->pm_max_children - wp->running_children);
411 if (children_to_fork <= 0) {
412 if (!wp->warn_max_children) {
413 fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
414 zlog(ZLOG_WARNING, "[pool %s] server reached pm.max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
415 wp->warn_max_children = 1;
416 }
417 wp->idle_spawn_rate = 1;
418 continue;
419 }
420 wp->warn_max_children = 0;
421
422 fpm_children_make(wp, 1, children_to_fork, 1);
423
424 /* if it's a child, stop here without creating the next event
425 * this event is reserved to the master process
426 */
427 if (fpm_globals.is_child) {
428 return;
429 }
430
431 zlog(ZLOG_DEBUG, "[pool %s] %d child(ren) have been created dynamically", wp->config->name, children_to_fork);
432
433 /* Double the spawn rate for the next iteration */
434 if (wp->idle_spawn_rate < FPM_MAX_SPAWN_RATE) {
435 wp->idle_spawn_rate *= 2;
436 }
437 continue;
438 }
439 wp->idle_spawn_rate = 1;
440 }
441 }
442 /* }}} */
443
fpm_pctl_heartbeat(struct fpm_event_s * ev,short which,void * arg)444 void fpm_pctl_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
445 {
446 static struct fpm_event_s heartbeat;
447 struct timeval now;
448
449 if (fpm_globals.parent_pid != getpid()) {
450 return; /* sanity check */
451 }
452
453 if (which == FPM_EV_TIMEOUT) {
454 fpm_clock_get(&now);
455 fpm_pctl_check_request_timeout(&now);
456 return;
457 }
458
459 /* ensure heartbeat is not lower than FPM_PCTL_MIN_HEARTBEAT */
460 fpm_globals.heartbeat = MAX(fpm_globals.heartbeat, FPM_PCTL_MIN_HEARTBEAT);
461
462 /* first call without setting to initialize the timer */
463 zlog(ZLOG_DEBUG, "heartbeat have been set up with a timeout of %dms", fpm_globals.heartbeat);
464 fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_heartbeat, NULL);
465 fpm_event_add(&heartbeat, fpm_globals.heartbeat);
466 }
467 /* }}} */
468
fpm_pctl_perform_idle_server_maintenance_heartbeat(struct fpm_event_s * ev,short which,void * arg)469 void fpm_pctl_perform_idle_server_maintenance_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
470 {
471 static struct fpm_event_s heartbeat;
472 struct timeval now;
473
474 if (fpm_globals.parent_pid != getpid()) {
475 return; /* sanity check */
476 }
477
478 if (which == FPM_EV_TIMEOUT) {
479 fpm_clock_get(&now);
480 if (fpm_pctl_can_spawn_children()) {
481 fpm_pctl_perform_idle_server_maintenance(&now);
482
483 /* if it's a child, stop here without creating the next event
484 * this event is reserved to the master process
485 */
486 if (fpm_globals.is_child) {
487 return;
488 }
489 }
490 return;
491 }
492
493 /* first call without setting which to initialize the timer */
494 fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_perform_idle_server_maintenance_heartbeat, NULL);
495 fpm_event_add(&heartbeat, FPM_IDLE_SERVER_MAINTENANCE_HEARTBEAT);
496 }
497 /* }}} */
498
fpm_pctl_on_socket_accept(struct fpm_event_s * ev,short which,void * arg)499 void fpm_pctl_on_socket_accept(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
500 {
501 struct fpm_worker_pool_s *wp = (struct fpm_worker_pool_s *)arg;
502 struct fpm_child_s *child;
503
504
505 if (fpm_globals.parent_pid != getpid()) {
506 /* prevent a event race condition when child process
507 * have not set up its own event loop */
508 return;
509 }
510
511 wp->socket_event_set = 0;
512
513 /* zlog(ZLOG_DEBUG, "[pool %s] heartbeat running_children=%d", wp->config->name, wp->running_children);*/
514
515 if (wp->running_children >= wp->config->pm_max_children) {
516 if (!wp->warn_max_children) {
517 fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
518 zlog(ZLOG_WARNING, "[pool %s] server reached max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
519 wp->warn_max_children = 1;
520 }
521
522 return;
523 }
524
525 for (child = wp->children; child; child = child->next) {
526 /* if there is at least on idle child, it will handle the connection, stop here */
527 if (fpm_request_is_idle(child)) {
528 return;
529 }
530 }
531
532 wp->warn_max_children = 0;
533 fpm_children_make(wp, 1, 1, 1);
534
535 if (fpm_globals.is_child) {
536 return;
537 }
538
539 zlog(ZLOG_DEBUG, "[pool %s] got accept without idle child available .... I forked", wp->config->name);
540 }
541 /* }}} */
542