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