/src/haproxy/src/mworker.c
Line | Count | Source |
1 | | /* |
2 | | * Master Worker |
3 | | * |
4 | | * Copyright HAProxy Technologies 2019 - William Lallemand <wlallemand@haproxy.com> |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU General Public License |
8 | | * as published by the Free Software Foundation; either version |
9 | | * 2 of the License, or (at your option) any later version. |
10 | | * |
11 | | */ |
12 | | |
13 | | #define _GNU_SOURCE |
14 | | |
15 | | #include <errno.h> |
16 | | #include <fcntl.h> |
17 | | #include <signal.h> |
18 | | #include <stdlib.h> |
19 | | #include <string.h> |
20 | | #include <sys/wait.h> |
21 | | #include <unistd.h> |
22 | | |
23 | | #include <haproxy/api.h> |
24 | | #include <haproxy/cfgparse.h> |
25 | | #include <haproxy/cli.h> |
26 | | #include <haproxy/errors.h> |
27 | | #include <haproxy/fd.h> |
28 | | #include <haproxy/global.h> |
29 | | #include <haproxy/log.h> |
30 | | #include <haproxy/list.h> |
31 | | #include <haproxy/listener.h> |
32 | | #include <haproxy/mworker.h> |
33 | | #include <haproxy/peers.h> |
34 | | #include <haproxy/proto_sockpair.h> |
35 | | #include <haproxy/proxy.h> |
36 | | #include <haproxy/ring.h> |
37 | | #include <haproxy/sc_strm.h> |
38 | | #include <haproxy/signal.h> |
39 | | #include <haproxy/ssl_sock.h> |
40 | | #include <haproxy/stconn.h> |
41 | | #include <haproxy/stream.h> |
42 | | #include <haproxy/systemd.h> |
43 | | #include <haproxy/tools.h> |
44 | | #include <haproxy/version.h> |
45 | | |
46 | | |
47 | | static int exitcode = -1; |
48 | | int max_reloads = 50; /* max number of reloads a worker can have until they are killed */ |
49 | | int load_status; /* worker process startup status: 1 - loaded successfully; 0 - load failed */ |
50 | | struct mworker_proc *proc_self = NULL; /* process structure of current process */ |
51 | | struct list mworker_cli_conf = LIST_HEAD_INIT(mworker_cli_conf); /* master CLI configuration (-S flag) */ |
52 | | |
53 | | /* ----- children processes handling ----- */ |
54 | | |
55 | | /* |
56 | | * Send signal to every known children. |
57 | | */ |
58 | | |
59 | | static void mworker_kill(int sig) |
60 | 0 | { |
61 | 0 | struct mworker_proc *child; |
62 | |
|
63 | 0 | list_for_each_entry(child, &proc_list, list) { |
64 | | /* careful there, we must be sure that the pid > 0, we don't want to emit a kill -1 */ |
65 | 0 | if ((child->options & PROC_O_TYPE_WORKER) && (child->pid > 0)) |
66 | 0 | kill(child->pid, sig); |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | | void mworker_kill_max_reloads(int sig) |
71 | 0 | { |
72 | 0 | struct mworker_proc *child; |
73 | |
|
74 | 0 | list_for_each_entry(child, &proc_list, list) { |
75 | 0 | if (max_reloads != -1 && (child->options & PROC_O_TYPE_WORKER) && |
76 | 0 | (child->pid > 0) && (child->reloads > max_reloads)) |
77 | 0 | kill(child->pid, sig); |
78 | 0 | } |
79 | 0 | } |
80 | | |
81 | | /* return 1 if a pid is a current child otherwise 0 */ |
82 | | int mworker_current_child(int pid) |
83 | 0 | { |
84 | 0 | struct mworker_proc *child; |
85 | |
|
86 | 0 | list_for_each_entry(child, &proc_list, list) { |
87 | 0 | if ((child->options & PROC_O_TYPE_WORKER) && (!(child->options & PROC_O_LEAVING)) && (child->pid == pid)) |
88 | 0 | return 1; |
89 | 0 | } |
90 | 0 | return 0; |
91 | 0 | } |
92 | | |
93 | | /* |
94 | | * Return the number of new and old children (including workers and external |
95 | | * processes) |
96 | | */ |
97 | | int mworker_child_nb() |
98 | 0 | { |
99 | 0 | struct mworker_proc *child; |
100 | 0 | int ret = 0; |
101 | |
|
102 | 0 | list_for_each_entry(child, &proc_list, list) { |
103 | 0 | if (child->options & PROC_O_TYPE_WORKER) |
104 | 0 | ret++; |
105 | 0 | } |
106 | |
|
107 | 0 | return ret; |
108 | 0 | } |
109 | | |
110 | | |
111 | | /* |
112 | | * serialize the proc list and put it in the environment |
113 | | */ |
114 | | void mworker_proc_list_to_env() |
115 | 0 | { |
116 | 0 | char *msg = NULL; |
117 | 0 | struct mworker_proc *child; |
118 | |
|
119 | 0 | list_for_each_entry(child, &proc_list, list) { |
120 | 0 | char type = '?'; |
121 | |
|
122 | 0 | if (child->options & PROC_O_TYPE_MASTER) |
123 | 0 | type = 'm'; |
124 | 0 | else if (child->options & PROC_O_TYPE_WORKER) |
125 | 0 | type = 'w'; |
126 | |
|
127 | 0 | if (child->pid > -1) |
128 | 0 | memprintf(&msg, "%s|type=%c;fd=%d;cfd=%d;pid=%d;reloads=%d;failedreloads=%d;timestamp=%d;id=%s;version=%s", msg ? msg : "", type, child->ipc_fd[0], child->ipc_fd[1], child->pid, child->reloads, child->failedreloads, child->timestamp, child->id ? child->id : "", child->version ? child->version : ""); |
129 | 0 | } |
130 | 0 | if (msg) |
131 | 0 | setenv("HAPROXY_PROCESSES", msg, 1); |
132 | 0 | } |
133 | | |
134 | | struct mworker_proc *mworker_proc_new() |
135 | 0 | { |
136 | 0 | struct mworker_proc *child; |
137 | |
|
138 | 0 | child = calloc(1, sizeof(*child)); |
139 | 0 | if (!child) |
140 | 0 | return NULL; |
141 | | |
142 | 0 | child->failedreloads = 0; |
143 | 0 | child->reloads = 0; |
144 | 0 | child->pid = -1; |
145 | 0 | child->ipc_fd[0] = -1; |
146 | 0 | child->ipc_fd[1] = -1; |
147 | 0 | child->timestamp = -1; |
148 | |
|
149 | 0 | return child; |
150 | 0 | } |
151 | | |
152 | | |
153 | | /* |
154 | | * unserialize the proc list from the environment |
155 | | * Return < 0 upon error. |
156 | | */ |
157 | | int mworker_env_to_proc_list() |
158 | 0 | { |
159 | 0 | char *env, *msg, *omsg = NULL, *token = NULL, *s1; |
160 | 0 | struct mworker_proc *child; |
161 | 0 | int err = 0; |
162 | |
|
163 | 0 | env = getenv("HAPROXY_PROCESSES"); |
164 | 0 | if (!env) |
165 | 0 | goto no_env; |
166 | | |
167 | 0 | omsg = msg = strdup(env); |
168 | 0 | if (!msg) { |
169 | 0 | ha_alert("Out of memory while trying to allocate a worker process structure."); |
170 | 0 | err = -1; |
171 | 0 | goto out; |
172 | 0 | } |
173 | | |
174 | 0 | while ((token = strtok_r(msg, "|", &s1))) { |
175 | 0 | char *subtoken = NULL; |
176 | 0 | char *s2 = NULL; |
177 | |
|
178 | 0 | msg = NULL; |
179 | |
|
180 | 0 | child = mworker_proc_new(); |
181 | 0 | if (!child) { |
182 | 0 | ha_alert("out of memory while trying to allocate a worker process structure."); |
183 | 0 | err = -1; |
184 | 0 | goto out; |
185 | 0 | } |
186 | | |
187 | 0 | while ((subtoken = strtok_r(token, ";", &s2))) { |
188 | |
|
189 | 0 | token = NULL; |
190 | |
|
191 | 0 | if (strncmp(subtoken, "type=", 5) == 0) { |
192 | 0 | char type; |
193 | |
|
194 | 0 | type = *(subtoken+5); |
195 | 0 | if (type == 'm') { /* we are in the master, assign it */ |
196 | 0 | proc_self = child; |
197 | 0 | child->options |= PROC_O_TYPE_MASTER; |
198 | 0 | } else if (type == 'w') { |
199 | 0 | child->options |= PROC_O_TYPE_WORKER; |
200 | 0 | } |
201 | |
|
202 | 0 | } else if (strncmp(subtoken, "fd=", 3) == 0) { |
203 | 0 | child->ipc_fd[0] = atoi(subtoken+3); |
204 | 0 | if (child->ipc_fd[0] > -1) |
205 | 0 | global.maxsock++; |
206 | 0 | } else if (strncmp(subtoken, "cfd=", 4) == 0) { |
207 | 0 | child->ipc_fd[1] = atoi(subtoken+4); |
208 | 0 | if (child->ipc_fd[1] > -1) |
209 | 0 | global.maxsock++; |
210 | 0 | } else if (strncmp(subtoken, "pid=", 4) == 0) { |
211 | 0 | child->pid = atoi(subtoken+4); |
212 | 0 | } else if (strncmp(subtoken, "reloads=", 8) == 0) { |
213 | | /* we only increment the number of asked reload */ |
214 | 0 | child->reloads = atoi(subtoken+8); |
215 | 0 | } else if (strncmp(subtoken, "failedreloads=", 14) == 0) { |
216 | 0 | child->failedreloads = atoi(subtoken+14); |
217 | 0 | } else if (strncmp(subtoken, "timestamp=", 10) == 0) { |
218 | 0 | child->timestamp = atoi(subtoken+10); |
219 | 0 | } else if (strncmp(subtoken, "id=", 3) == 0) { |
220 | 0 | child->id = strdup(subtoken+3); |
221 | 0 | } else if (strncmp(subtoken, "version=", 8) == 0) { |
222 | 0 | child->version = strdup(subtoken+8); |
223 | 0 | } |
224 | 0 | } |
225 | 0 | if (child->pid > 0) { |
226 | 0 | struct list *insert_pt = &proc_list; |
227 | 0 | struct mworker_proc *pos; |
228 | | |
229 | | /* insert at the right position in ASC reload order; |
230 | | * search from the tail since items are sorted most of |
231 | | * the time |
232 | | */ |
233 | 0 | list_for_each_entry_rev(pos, &proc_list, list) { |
234 | 0 | if (pos->reloads <= child->reloads) { |
235 | 0 | insert_pt = &pos->list; |
236 | 0 | break; |
237 | 0 | } |
238 | 0 | } |
239 | 0 | LIST_INSERT(insert_pt, &child->list); |
240 | 0 | } else { |
241 | 0 | mworker_free_child(child); |
242 | 0 | } |
243 | 0 | } |
244 | | |
245 | | /* set the leaving processes once we know which number of reloads are the current processes */ |
246 | | |
247 | 0 | list_for_each_entry(child, &proc_list, list) { |
248 | 0 | if (child->reloads > 0 && !(child->options & PROC_O_TYPE_MASTER)) |
249 | 0 | child->options |= PROC_O_LEAVING; |
250 | 0 | } |
251 | |
|
252 | 0 | unsetenv("HAPROXY_PROCESSES"); |
253 | |
|
254 | 0 | no_env: |
255 | | /* couldn't find the master element, exiting */ |
256 | 0 | if (!proc_self) { |
257 | 0 | err = -1; |
258 | 0 | ha_alert("Failed to deserialize data for the master process. Unrecoverable error, exiting.\n"); |
259 | 0 | goto out; |
260 | 0 | } |
261 | | |
262 | 0 | out: |
263 | 0 | free(omsg); |
264 | 0 | return err; |
265 | 0 | } |
266 | | |
267 | | /* Signal blocking and unblocking */ |
268 | | |
269 | | void mworker_block_signals() |
270 | 0 | { |
271 | 0 | sigset_t set; |
272 | |
|
273 | 0 | sigemptyset(&set); |
274 | 0 | sigaddset(&set, SIGUSR1); |
275 | 0 | sigaddset(&set, SIGUSR2); |
276 | 0 | sigaddset(&set, SIGTTIN); |
277 | 0 | sigaddset(&set, SIGTTOU); |
278 | 0 | sigaddset(&set, SIGHUP); |
279 | 0 | sigaddset(&set, SIGCHLD); |
280 | 0 | ha_sigmask(SIG_SETMASK, &set, NULL); |
281 | 0 | } |
282 | | |
283 | | void mworker_unblock_sigchld() |
284 | 0 | { |
285 | 0 | sigset_t set; |
286 | |
|
287 | 0 | signal_register_fct(SIGCHLD, mworker_catch_sigchld, SIGCHLD); |
288 | |
|
289 | 0 | sigemptyset(&set); |
290 | 0 | sigaddset(&set, SIGCHLD); |
291 | |
|
292 | 0 | ha_sigmask(SIG_UNBLOCK, &set, NULL); |
293 | 0 | } |
294 | | |
295 | | void mworker_unblock_signals() |
296 | 0 | { |
297 | 0 | signal_unregister(SIGTTIN); |
298 | 0 | signal_unregister(SIGTTOU); |
299 | 0 | signal_unregister(SIGUSR1); |
300 | 0 | signal_unregister(SIGHUP); |
301 | 0 | signal_unregister(SIGQUIT); |
302 | |
|
303 | 0 | signal_register_fct(SIGTERM, mworker_catch_sigterm, SIGTERM); |
304 | 0 | signal_register_fct(SIGUSR1, mworker_catch_sigterm, SIGUSR1); |
305 | 0 | signal_register_fct(SIGTTIN, mworker_broadcast_signal, SIGTTIN); |
306 | 0 | signal_register_fct(SIGTTOU, mworker_broadcast_signal, SIGTTOU); |
307 | 0 | signal_register_fct(SIGINT, mworker_catch_sigterm, SIGINT); |
308 | 0 | signal_register_fct(SIGHUP, mworker_catch_sighup, SIGHUP); |
309 | 0 | signal_register_fct(SIGUSR2, mworker_catch_sighup, SIGUSR2); |
310 | 0 | signal_register_fct(SIGCHLD, mworker_catch_sigchld, SIGCHLD); |
311 | |
|
312 | 0 | haproxy_unblock_signals(); |
313 | 0 | } |
314 | | |
315 | | /* ----- mworker signal handlers ----- */ |
316 | | |
317 | | /* broadcast the configured signal to the workers */ |
318 | | void mworker_broadcast_signal(struct sig_handler *sh) |
319 | 0 | { |
320 | 0 | mworker_kill(sh->arg); |
321 | 0 | } |
322 | | |
323 | | /* |
324 | | * When called, this function reexec haproxy with -sf followed by current |
325 | | * children PIDs and possibly old children PIDs if they didn't leave yet. |
326 | | */ |
327 | | static void mworker_reexec(int hardreload) |
328 | 0 | { |
329 | 0 | char **next_argv = NULL; |
330 | 0 | int old_argc = 0; /* previous number of argument */ |
331 | 0 | int next_argc = 0; |
332 | 0 | int i = 0; |
333 | 0 | char *msg = NULL; |
334 | 0 | struct rlimit limit; |
335 | 0 | struct mworker_proc *current_child = NULL; |
336 | 0 | int x_off = 0; /* disable -x by putting -x /dev/null */ |
337 | |
|
338 | 0 | mworker_block_signals(); |
339 | | |
340 | | /* restore initial environment (before parsing the config) and do re-exec. |
341 | | * The initial process environment should be restored here, preceded by |
342 | | * clean_env(), which do the same job as clearenv(). |
343 | | * Otherwise, after the re-exec we will start the new worker in the |
344 | | * environment modified by '*env' keywords from the previous configuration, |
345 | | * i.e. existed before the reload. |
346 | | */ |
347 | 0 | if (clean_env() != 0) { |
348 | 0 | ha_alert("Master encountered a non-recoverable error, exiting.\n"); |
349 | 0 | exit(EXIT_FAILURE); |
350 | 0 | } |
351 | | |
352 | 0 | if (restore_env() != 0) { |
353 | 0 | ha_alert("Master encountered a non-recoverable error, exiting.\n"); |
354 | 0 | exit(EXIT_FAILURE); |
355 | 0 | } |
356 | | |
357 | 0 | setenv("HAPROXY_MWORKER_REEXEC", "1", 1); |
358 | |
|
359 | 0 | mworker_proc_list_to_env(); /* put the children description in the env */ |
360 | | |
361 | | /* during the reload we must ensure that every FDs that can't be |
362 | | * reuse (ie those that are not referenced in the proc_list) |
363 | | * are closed or they will leak. */ |
364 | | |
365 | | /* close the listeners FD */ |
366 | 0 | mworker_cli_proxy_stop(); |
367 | |
|
368 | 0 | if (fdtab) |
369 | 0 | deinit_pollers(); |
370 | |
|
371 | | #ifdef HAVE_SSL_RAND_KEEP_RANDOM_DEVICES_OPEN |
372 | | /* close random device FDs */ |
373 | | RAND_keep_random_devices_open(0); |
374 | | #endif |
375 | | |
376 | | /* restore the initial FD limits */ |
377 | 0 | limit.rlim_cur = rlim_fd_cur_at_boot; |
378 | 0 | limit.rlim_max = rlim_fd_max_at_boot; |
379 | 0 | if (raise_rlim_nofile(&limit, &limit) != 0) { |
380 | 0 | ha_warning("Failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n", |
381 | 0 | rlim_fd_cur_at_boot, rlim_fd_max_at_boot, |
382 | 0 | (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max); |
383 | 0 | } |
384 | | |
385 | | /* compute length */ |
386 | 0 | while (old_argv[old_argc]) |
387 | 0 | old_argc++; |
388 | | |
389 | | /* 1 for haproxy -sf, 2 for -x /socket */ |
390 | 0 | next_argv = calloc(old_argc + 1 + 2 + mworker_child_nb() + 1, |
391 | 0 | sizeof(*next_argv)); |
392 | 0 | if (next_argv == NULL) |
393 | 0 | goto alloc_error; |
394 | | |
395 | | /* copy the program name */ |
396 | 0 | next_argv[next_argc++] = old_argv[0]; |
397 | | |
398 | | /* we need to reintroduce /dev/null every time */ |
399 | 0 | if (old_unixsocket && strcmp(old_unixsocket, "/dev/null") == 0) |
400 | 0 | x_off = 1; |
401 | | |
402 | | /* insert the new options just after argv[0] in case we have a -- */ |
403 | | |
404 | | /* add -sf <PID>* to argv */ |
405 | 0 | if (mworker_child_nb() > 0) { |
406 | 0 | struct mworker_proc *child; |
407 | |
|
408 | 0 | if (hardreload) |
409 | 0 | next_argv[next_argc++] = "-st"; |
410 | 0 | else |
411 | 0 | next_argv[next_argc++] = "-sf"; |
412 | |
|
413 | 0 | list_for_each_entry(child, &proc_list, list) { |
414 | 0 | if (!(child->options & PROC_O_LEAVING) && (child->options & PROC_O_TYPE_WORKER)) |
415 | 0 | current_child = child; |
416 | |
|
417 | 0 | if (!(child->options & (PROC_O_TYPE_WORKER)) || child->pid <= -1) |
418 | 0 | continue; |
419 | 0 | if ((next_argv[next_argc++] = memprintf(&msg, "%d", child->pid)) == NULL) |
420 | 0 | goto alloc_error; |
421 | 0 | msg = NULL; |
422 | 0 | } |
423 | 0 | } |
424 | 0 | if (!x_off && current_child) { |
425 | | /* add the -x option with the socketpair of the current worker */ |
426 | 0 | next_argv[next_argc++] = "-x"; |
427 | 0 | if ((next_argv[next_argc++] = memprintf(&msg, "sockpair@%d", current_child->ipc_fd[0])) == NULL) |
428 | 0 | goto alloc_error; |
429 | 0 | msg = NULL; |
430 | 0 | } |
431 | | |
432 | 0 | if (x_off) { |
433 | | /* if the cmdline contained a -x /dev/null, continue to use it */ |
434 | 0 | next_argv[next_argc++] = "-x"; |
435 | 0 | next_argv[next_argc++] = "/dev/null"; |
436 | 0 | } |
437 | | |
438 | | /* copy the previous options */ |
439 | 0 | for (i = 1; i < old_argc; i++) |
440 | 0 | next_argv[next_argc++] = old_argv[i]; |
441 | | |
442 | | /* need to withdraw MODE_STARTING from master, because we have to free |
443 | | * the startup logs ring here, see more details in print_message() |
444 | | */ |
445 | 0 | global.mode &= ~MODE_STARTING; |
446 | 0 | startup_logs_free(startup_logs); |
447 | |
|
448 | 0 | signal(SIGPROF, SIG_IGN); |
449 | 0 | execvp(next_argv[0], next_argv); |
450 | 0 | ha_warning("Failed to reexecute the master process [%d]: %s\n", pid, strerror(errno)); |
451 | 0 | ha_free(&next_argv); |
452 | 0 | return; |
453 | | |
454 | 0 | alloc_error: |
455 | 0 | ha_free(&next_argv); |
456 | 0 | ha_warning("Failed to reexecute the master process [%d]: Cannot allocate memory\n", pid); |
457 | 0 | return; |
458 | 0 | } |
459 | | |
460 | | /* reload haproxy and emit a warning */ |
461 | | static void mworker_reload(int hardreload) |
462 | 0 | { |
463 | 0 | struct mworker_proc *child; |
464 | 0 | struct per_thread_deinit_fct *ptdf; |
465 | |
|
466 | 0 | ha_notice("Reloading HAProxy%s\n", hardreload?" (hard-reload)":""); |
467 | | |
468 | | /* close the poller FD and the thread waker pipe FD */ |
469 | 0 | list_for_each_entry(ptdf, &per_thread_deinit_list, list) |
470 | 0 | ptdf->fct(); |
471 | | |
472 | | /* increment the number of reloads, child->reloads is checked in |
473 | | * mworker_env_to_proc_list() (after reload) in order to set |
474 | | * PROC_O_LEAVING flag for the process |
475 | | */ |
476 | 0 | list_for_each_entry(child, &proc_list, list) { |
477 | 0 | child->reloads++; |
478 | 0 | } |
479 | |
|
480 | 0 | if (global.tune.options & GTUNE_USE_SYSTEMD) { |
481 | 0 | struct timespec ts; |
482 | |
|
483 | 0 | (void)clock_gettime(CLOCK_MONOTONIC, &ts); |
484 | |
|
485 | 0 | sd_notifyf(0, |
486 | 0 | "RELOADING=1\n" |
487 | 0 | "STATUS=Reloading Configuration.\n" |
488 | 0 | "MONOTONIC_USEC=%" PRIu64 "\n", |
489 | 0 | (ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000ULL)); |
490 | 0 | } |
491 | 0 | mworker_reexec(hardreload); |
492 | 0 | } |
493 | | |
494 | | /* |
495 | | * When called, this function reexec haproxy with -sf followed by current |
496 | | * children PIDs and possibly old children PIDs if they didn't leave yet. |
497 | | */ |
498 | | void mworker_catch_sighup(struct sig_handler *sh) |
499 | 0 | { |
500 | 0 | mworker_reload(0); |
501 | 0 | } |
502 | | |
503 | | void mworker_catch_sigterm(struct sig_handler *sh) |
504 | 0 | { |
505 | 0 | int sig = sh->arg; |
506 | |
|
507 | 0 | if (global.tune.options & GTUNE_USE_SYSTEMD) { |
508 | 0 | sd_notify(0, "STOPPING=1"); |
509 | 0 | } |
510 | 0 | ha_warning("Exiting Master process...\n"); |
511 | 0 | mworker_kill(sig); |
512 | 0 | } |
513 | | |
514 | | /* handle operations that can't be done in the signal handler */ |
515 | | static struct task *mworker_task_child_failure(struct task *task, void *context, unsigned int state) |
516 | 0 | { |
517 | 0 | mworker_unblock_signals(); |
518 | 0 | task_destroy(task); |
519 | 0 | return NULL; |
520 | 0 | } |
521 | | |
522 | | /* |
523 | | * Performs some routines for the worker process, which has failed the reload, |
524 | | * updates the global load_status. |
525 | | */ |
526 | | static void mworker_on_new_child_failure(int exitpid, int status) |
527 | 0 | { |
528 | 0 | struct mworker_proc *child; |
529 | 0 | struct task *t; |
530 | | |
531 | | /* increment the number of failed reloads */ |
532 | 0 | list_for_each_entry(child, &proc_list, list) { |
533 | 0 | child->failedreloads++; |
534 | 0 | } |
535 | | |
536 | | /* do not keep unused FDs retrieved from the previous process */ |
537 | 0 | sock_drop_unused_old_sockets(); |
538 | |
|
539 | 0 | usermsgs_clr(NULL); |
540 | 0 | load_status = 0; |
541 | 0 | ha_warning("Failed to load worker (%d) exited with code %d (%s)\n", exitpid, status, (status >= 128) ? strsignal(status - 128): "Exit"); |
542 | | /* the sd_notify API is not able to send a reload failure signal. So |
543 | | * the READY=1 signal still need to be sent */ |
544 | 0 | if (global.tune.options & GTUNE_USE_SYSTEMD) |
545 | 0 | sd_notify(0, "READY=1\nSTATUS=Reload failed!\n"); |
546 | | |
547 | | /* call a task to unblock the signals from outside the sig handler */ |
548 | 0 | if ((t = task_new_here()) == NULL) { |
549 | 0 | ha_warning("Can't restore HAProxy signals!\n"); |
550 | 0 | return; |
551 | 0 | } |
552 | | |
553 | 0 | t->process = mworker_task_child_failure; |
554 | 0 | task_wakeup(t, TASK_WOKEN_MSG); |
555 | 0 | } |
556 | | |
557 | | /* |
558 | | * Wait for every children to exit |
559 | | */ |
560 | | |
561 | | void mworker_catch_sigchld(struct sig_handler *sh) |
562 | 0 | { |
563 | 0 | int exitpid = -1; |
564 | 0 | int status = 0; |
565 | 0 | int childfound; |
566 | 0 | struct listener *l, *l_next; |
567 | 0 | struct proxy *curproxy; |
568 | |
|
569 | 0 | restart_wait: |
570 | |
|
571 | 0 | childfound = 0; |
572 | |
|
573 | 0 | exitpid = waitpid(-1, &status, WNOHANG); |
574 | 0 | if (exitpid > 0) { |
575 | 0 | struct mworker_proc *child, *it; |
576 | |
|
577 | 0 | if (WIFEXITED(status)) |
578 | 0 | status = WEXITSTATUS(status); |
579 | 0 | else if (WIFSIGNALED(status)) |
580 | 0 | status = 128 + WTERMSIG(status); |
581 | 0 | else if (WIFSTOPPED(status)) |
582 | 0 | status = 128 + WSTOPSIG(status); |
583 | 0 | else |
584 | 0 | status = 255; |
585 | | |
586 | | /* delete the child from the process list */ |
587 | 0 | list_for_each_entry_safe(child, it, &proc_list, list) { |
588 | 0 | if (child->pid != exitpid) |
589 | 0 | continue; |
590 | | |
591 | 0 | LIST_DELETE(&child->list); |
592 | 0 | childfound = 1; |
593 | 0 | break; |
594 | 0 | } |
595 | |
|
596 | 0 | if (!childfound) { |
597 | | /* We didn't find the PID in the list, that shouldn't happen but we can emit a warning */ |
598 | 0 | ha_warning("Process %d exited with code %d (%s)\n", exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit"); |
599 | 0 | } else if (child->options & PROC_O_INIT) { |
600 | 0 | mworker_on_new_child_failure(exitpid, status); |
601 | | |
602 | | /* Detach all listeners */ |
603 | 0 | list_for_each_entry(curproxy, &main_proxies, el) { |
604 | 0 | list_for_each_entry_safe(l, l_next, &curproxy->conf.listeners, by_fe) { |
605 | 0 | if ((l->rx.fd == child->ipc_fd[0]) || (l->rx.fd == child->ipc_fd[1])) { |
606 | 0 | unbind_listener(l); |
607 | 0 | delete_listener(l); |
608 | 0 | } |
609 | 0 | } |
610 | 0 | } |
611 | | |
612 | | /* Drop server */ |
613 | 0 | if (child->srv) { |
614 | 0 | srv_detach(child->srv); |
615 | 0 | srv_drop(child->srv); |
616 | 0 | } |
617 | | |
618 | | /* Delete fd from poller fdtab, which will close it */ |
619 | 0 | fd_delete(child->ipc_fd[0]); |
620 | 0 | child->ipc_fd[0] = -1; |
621 | 0 | mworker_free_child(child); |
622 | 0 | child = NULL; |
623 | | |
624 | | /* When worker fails during the first startup, there is |
625 | | * no previous workers with state PROC_O_LEAVING, master |
626 | | * process should exit here as well to keep the |
627 | | * previous behaviour |
628 | | */ |
629 | 0 | if ((proc_self->options & PROC_O_TYPE_MASTER) && (proc_self->reloads == 0)) |
630 | 0 | exit(status); |
631 | 0 | } else { |
632 | | /* check if exited child is a current child */ |
633 | 0 | if (!(child->options & PROC_O_LEAVING)) { |
634 | 0 | if (child->options & PROC_O_TYPE_WORKER) { |
635 | 0 | fd_delete(child->ipc_fd[0]); |
636 | 0 | if (status < 128) |
637 | 0 | ha_warning("Current worker (%d) exited with code %d (%s)\n", exitpid, status, "Exit"); |
638 | 0 | else |
639 | 0 | ha_alert("Current worker (%d) exited with code %d (%s)\n", exitpid, status, strsignal(status - 128)); |
640 | 0 | } |
641 | |
|
642 | 0 | if (status != 0 && status != 130 && status != 143) { |
643 | 0 | if (child->options & PROC_O_TYPE_WORKER) { |
644 | 0 | ha_warning("A worker process unexpectedly died and this can only be explained by a bug in haproxy or its dependencies.\nPlease check that you are running an up to date and maintained version of haproxy and open a bug report.\n"); |
645 | 0 | display_version(); |
646 | 0 | } |
647 | | /* new worker, which has been launched at reload has status PROC_O_INIT */ |
648 | 0 | if (!(global.tune.options & GTUNE_NOEXIT_ONFAILURE) && !(child->options & PROC_O_INIT)) { |
649 | 0 | ha_alert("exit-on-failure: killing every processes with SIGTERM\n"); |
650 | 0 | mworker_kill(SIGTERM); |
651 | 0 | } |
652 | 0 | } |
653 | | /* 0 & SIGTERM (143) are normal, but we should report SIGINT (130) and other signals */ |
654 | 0 | if (exitcode < 0 && status != 0 && status != 143) |
655 | 0 | exitcode = status; |
656 | 0 | } else { |
657 | 0 | if (child->options & PROC_O_TYPE_WORKER) { |
658 | 0 | if (child->reloads > max_reloads) |
659 | 0 | ha_warning("Former worker (%d) exited with code %d (%s), as it exceeds max reloads (%d)\n", exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit", max_reloads); |
660 | 0 | else |
661 | 0 | ha_warning("Former worker (%d) exited with code %d (%s)\n", exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit"); |
662 | | /* Delete fd from poller fdtab, which will close it */ |
663 | 0 | fd_delete(child->ipc_fd[0]); |
664 | 0 | delete_oldpid(exitpid); |
665 | 0 | } |
666 | 0 | } |
667 | 0 | mworker_free_child(child); |
668 | 0 | child = NULL; |
669 | 0 | } |
670 | | |
671 | | /* do it again to check if it was the last worker */ |
672 | 0 | goto restart_wait; |
673 | 0 | } |
674 | | /* Better rely on the system than on a list of process to check if it was the last one */ |
675 | 0 | else if (exitpid == -1 && errno == ECHILD) { |
676 | 0 | struct post_deinit_fct *pdff; |
677 | |
|
678 | 0 | ha_warning("All workers exited. Exiting... (%d)\n", (exitcode > 0) ? exitcode : EXIT_SUCCESS); |
679 | |
|
680 | 0 | list_for_each_entry(pdff, &post_deinit_master_list, list) |
681 | 0 | pdff->fct(); |
682 | |
|
683 | 0 | atexit_flag = 0; |
684 | 0 | if (exitcode > 0) |
685 | 0 | exit(exitcode); /* parent must leave using the status code that provoked the exit */ |
686 | 0 | exit(EXIT_SUCCESS); |
687 | 0 | } |
688 | |
|
689 | 0 | } |
690 | | |
691 | | /* ----- IPC FD (sockpair) related ----- */ |
692 | | |
693 | | /* This wrapper is called from the workers. It is registered instead of the |
694 | | * normal listener_accept() so the worker can exit() when it detects that the |
695 | | * master closed the IPC FD. If it's not a close, we just call the regular |
696 | | * listener_accept() function. |
697 | | */ |
698 | | void mworker_accept_wrapper(int fd) |
699 | 0 | { |
700 | 0 | char c; |
701 | 0 | int ret; |
702 | |
|
703 | 0 | while (1) { |
704 | 0 | ret = recv(fd, &c, 1, MSG_PEEK); |
705 | 0 | if (ret == -1) { |
706 | 0 | if (errno == EINTR) |
707 | 0 | continue; |
708 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK) { |
709 | 0 | fd_cant_recv(fd); |
710 | 0 | return; |
711 | 0 | } |
712 | 0 | break; |
713 | 0 | } else if (ret > 0) { |
714 | 0 | struct listener *l = fdtab[fd].owner; |
715 | |
|
716 | 0 | if (l) |
717 | 0 | listener_accept(l); |
718 | 0 | return; |
719 | 0 | } else if (ret == 0) { |
720 | | /* At this step the master is down before |
721 | | * this worker perform a 'normal' exit. |
722 | | * So we want to exit with an error but |
723 | | * other threads could currently process |
724 | | * some stuff so we can't perform a clean |
725 | | * deinit(). |
726 | | */ |
727 | 0 | exit(EXIT_FAILURE); |
728 | 0 | } |
729 | 0 | } |
730 | 0 | return; |
731 | 0 | } |
732 | | |
733 | | /* |
734 | | * This function registers the accept wrapper for the sockpair of the master |
735 | | * worker. It's only handled by worker thread #0. Other threads and master do |
736 | | * nothing here. It always returns 1 (success). |
737 | | */ |
738 | | static int mworker_sockpair_register_per_thread() |
739 | 0 | { |
740 | 0 | if (!(global.mode & MODE_MWORKER) || master) |
741 | 0 | return 1; |
742 | | |
743 | 0 | if (tid != 0) |
744 | 0 | return 1; |
745 | | |
746 | 0 | if (proc_self->ipc_fd[1] < 0) /* proc_self was incomplete and we can't find the socketpair */ |
747 | 0 | return 1; |
748 | | |
749 | 0 | fd_set_nonblock(proc_self->ipc_fd[1]); |
750 | | /* register the wrapper to handle read 0 when the master exits */ |
751 | 0 | fdtab[proc_self->ipc_fd[1]].iocb = mworker_accept_wrapper; |
752 | 0 | fd_want_recv(proc_self->ipc_fd[1]); |
753 | 0 | return 1; |
754 | 0 | } |
755 | | |
756 | | REGISTER_PER_THREAD_INIT(mworker_sockpair_register_per_thread); |
757 | | |
758 | | /* ----- proxies ----- */ |
759 | | /* |
760 | | * Upon a reload, the master worker needs to close all listeners FDs but the mworker_pipe |
761 | | * fd, and the FD provided by fd@ |
762 | | */ |
763 | | void mworker_cleanlisteners() |
764 | 0 | { |
765 | 0 | struct listener *l, *l_next; |
766 | 0 | struct proxy *curproxy; |
767 | 0 | struct peers *curpeers; |
768 | | |
769 | | /* peers proxies cleanup */ |
770 | 0 | for (curpeers = cfg_peers; curpeers; curpeers = curpeers->next) { |
771 | 0 | if (!curpeers->peers_fe) |
772 | 0 | continue; |
773 | | |
774 | 0 | stop_proxy(curpeers->peers_fe); |
775 | | /* disable this peer section so that it kills itself */ |
776 | 0 | if (curpeers->sighandler) |
777 | 0 | signal_unregister_handler(curpeers->sighandler); |
778 | 0 | task_destroy(curpeers->sync_task); |
779 | 0 | curpeers->sync_task = NULL; |
780 | 0 | curpeers->peers_fe = NULL; |
781 | 0 | } |
782 | | |
783 | | /* main proxies cleanup */ |
784 | 0 | list_for_each_entry(curproxy, &main_proxies, el) { |
785 | 0 | int listen_in_master = 0; |
786 | |
|
787 | 0 | list_for_each_entry_safe(l, l_next, &curproxy->conf.listeners, by_fe) { |
788 | | /* remove the listener, but not those we need in the master... */ |
789 | 0 | if (!(l->rx.flags & RX_F_MWORKER)) { |
790 | 0 | unbind_listener(l); |
791 | 0 | delete_listener(l); |
792 | 0 | } else { |
793 | 0 | listen_in_master = 1; |
794 | 0 | } |
795 | 0 | } |
796 | | /* if the proxy shouldn't be in the master, we stop it */ |
797 | 0 | if (!listen_in_master) |
798 | 0 | curproxy->flags |= PR_FL_DISABLED; |
799 | 0 | } |
800 | 0 | } |
801 | | |
802 | | /* Upon a configuration loading error some mworker_proc and FDs/server were |
803 | | * assigned but the worker was never forked, we must close the FDs and |
804 | | * remove the server |
805 | | */ |
806 | | void mworker_cleanup_proc() |
807 | 0 | { |
808 | 0 | struct mworker_proc *child, *it; |
809 | |
|
810 | 0 | list_for_each_entry_safe(child, it, &proc_list, list) { |
811 | |
|
812 | 0 | if (child->pid == -1) { |
813 | | /* Close the socketpairs. */ |
814 | 0 | if (child->ipc_fd[0] > -1) |
815 | 0 | close(child->ipc_fd[0]); |
816 | 0 | if (child->ipc_fd[1] > -1) |
817 | 0 | close(child->ipc_fd[1]); |
818 | 0 | if (child->srv) { |
819 | | /* only exists if we created a master CLI listener */ |
820 | 0 | srv_detach(child->srv); |
821 | 0 | srv_drop(child->srv); |
822 | 0 | } |
823 | 0 | LIST_DELETE(&child->list); |
824 | 0 | mworker_free_child(child); |
825 | 0 | } |
826 | 0 | } |
827 | 0 | } |
828 | | |
829 | | struct cli_showproc_ctx { |
830 | | int debug; |
831 | | int resume_reload; /* reload count of the last flushed old worker row, 0 = none yet */ |
832 | | }; |
833 | | |
834 | | /* Append a single worker row to trash (shared between current/old sections) */ |
835 | | static void cli_append_worker_row(struct cli_showproc_ctx *ctx, struct mworker_proc *child, time_t tv_sec) |
836 | 0 | { |
837 | 0 | char *uptime = NULL; |
838 | 0 | int up = tv_sec - child->timestamp; |
839 | |
|
840 | 0 | if (up < 0) /* must never be negative because of clock drift */ |
841 | 0 | up = 0; |
842 | |
|
843 | 0 | memprintf(&uptime, "%dd%02dh%02dm%02ds", up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60)); |
844 | 0 | chunk_appendf(&trash, "%-15u %-15s %-15d %-15s %-15s", child->pid, "worker", child->reloads, uptime, child->version); |
845 | 0 | if (ctx->debug) |
846 | 0 | chunk_appendf(&trash, "\t\t %-15d %-15d", child->ipc_fd[0], child->ipc_fd[1]); |
847 | 0 | chunk_appendf(&trash, "\n"); |
848 | 0 | ha_free(&uptime); |
849 | 0 | } |
850 | | |
851 | | /* Displays workers and processes */ |
852 | | static int cli_io_handler_show_proc(struct appctx *appctx) |
853 | 0 | { |
854 | 0 | struct mworker_proc *child; |
855 | 0 | int old = 0; |
856 | 0 | int up = date.tv_sec - proc_self->timestamp; |
857 | 0 | struct cli_showproc_ctx *ctx = appctx->svcctx; |
858 | 0 | char *uptime = NULL; |
859 | 0 | char *reloadtxt = NULL; |
860 | |
|
861 | 0 | if (up < 0) /* must never be negative because of clock drift */ |
862 | 0 | up = 0; |
863 | |
|
864 | 0 | chunk_reset(&trash); |
865 | |
|
866 | 0 | if (ctx->resume_reload == 0) { |
867 | 0 | memprintf(&reloadtxt, "%d [failed: %d]", proc_self->reloads, proc_self->failedreloads); |
868 | 0 | chunk_printf(&trash, "#%-14s %-15s %-15s %-15s %-15s", "<PID>", "<type>", "<reloads>", "<uptime>", "<version>"); |
869 | 0 | if (ctx->debug) |
870 | 0 | chunk_appendf(&trash, "\t\t %-15s %-15s", "<ipc_fd[0]>", "<ipc_fd[1]>"); |
871 | 0 | chunk_appendf(&trash, "\n"); |
872 | | |
873 | | /* display the master only the first time */ |
874 | 0 | memprintf(&uptime, "%dd%02dh%02dm%02ds", up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60)); |
875 | 0 | chunk_appendf(&trash, "%-15u %-15s %-15s %-15s %-15s", (unsigned int)getpid(), "master", reloadtxt, uptime, haproxy_version); |
876 | 0 | if (ctx->debug) |
877 | 0 | chunk_appendf(&trash, "\t\t %-15d %-15d", proc_self->ipc_fd[0], proc_self->ipc_fd[1]); |
878 | 0 | chunk_appendf(&trash, "\n"); |
879 | 0 | } |
880 | 0 | ha_free(&reloadtxt); |
881 | 0 | ha_free(&uptime); |
882 | | |
883 | | /* displays current processes */ |
884 | 0 | if (ctx->resume_reload == 0) |
885 | 0 | chunk_appendf(&trash, "# workers\n"); |
886 | 0 | list_for_each_entry(child, &proc_list, list) { |
887 | | |
888 | | /* don't display current worker if we only need the next ones */ |
889 | 0 | if (ctx->resume_reload != 0) |
890 | 0 | continue; |
891 | | |
892 | 0 | if (!(child->options & PROC_O_TYPE_WORKER)) |
893 | 0 | continue; |
894 | | |
895 | 0 | if (child->options & PROC_O_LEAVING) { |
896 | 0 | old++; |
897 | 0 | continue; |
898 | 0 | } |
899 | 0 | cli_append_worker_row(ctx, child, date.tv_sec); |
900 | 0 | } |
901 | |
|
902 | 0 | if (applet_putchk(appctx, &trash) == -1) |
903 | 0 | return 0; |
904 | | |
905 | | /* displays old processes */ |
906 | 0 | if (old || ctx->resume_reload) { /* there's more */ |
907 | 0 | int skip = ctx->resume_reload; /* if resuming, skip until we pass this reload count */ |
908 | 0 | int prev_reload = 0; /* previous LEAVING entry's reload count during skip phase */ |
909 | |
|
910 | 0 | if (!ctx->resume_reload) |
911 | 0 | chunk_appendf(&trash, "# old workers\n"); |
912 | 0 | list_for_each_entry(child, &proc_list, list) { |
913 | 0 | if (!(child->options & PROC_O_TYPE_WORKER)) |
914 | 0 | continue; |
915 | | |
916 | 0 | if (!(child->options & PROC_O_LEAVING)) |
917 | 0 | continue; |
918 | | |
919 | | /* When resuming after a flush failure, skip entries |
920 | | * up to and including the last successfully flushed |
921 | | * row (identified by its reload count). This is |
922 | | * direction-agnostic: works whether the list is in |
923 | | * ascending or descending reload order. |
924 | | * |
925 | | * If the target entry was deleted from proc_list |
926 | | * (e.g. process exited between handler calls), we |
927 | | * detect that we've passed its former position when |
928 | | * two consecutive LEAVING entries straddle the skip |
929 | | * value — i.e. one has reloads > skip and the next |
930 | | * has reloads < skip (or vice versa). In that case |
931 | | * we stop skipping and emit the current entry. |
932 | | */ |
933 | 0 | if (skip) { |
934 | 0 | if (child->reloads == skip) { |
935 | 0 | skip = 0; /* found it, resume from the next entry */ |
936 | 0 | prev_reload = 0; |
937 | 0 | continue; |
938 | 0 | } |
939 | 0 | if (prev_reload && |
940 | 0 | ((prev_reload > skip) != (child->reloads > skip))) { |
941 | | /* Crossed where skip would have been — |
942 | | * the entry was deleted. Stop skipping |
943 | | * and fall through to emit this entry. |
944 | | */ |
945 | 0 | skip = 0; |
946 | 0 | } else { |
947 | 0 | prev_reload = child->reloads; |
948 | 0 | continue; |
949 | 0 | } |
950 | 0 | } |
951 | | |
952 | 0 | cli_append_worker_row(ctx, child, date.tv_sec); |
953 | |
|
954 | 0 | if (applet_putchk(appctx, &trash) == -1) { |
955 | | /* ctx->resume_reload already holds the last |
956 | | * flushed row or 0; don't update it here so |
957 | | * the failed row will be replayed. |
958 | | */ |
959 | 0 | return 0; |
960 | 0 | } |
961 | | /* This row was successfully flushed, remember it */ |
962 | 0 | ctx->resume_reload = child->reloads; |
963 | 0 | chunk_reset(&trash); |
964 | 0 | } |
965 | 0 | } |
966 | | |
967 | | /* dump complete: reset resume cursor so next 'show proc' starts from the top */ |
968 | 0 | ctx->resume_reload = 0; |
969 | 0 | return 1; |
970 | 0 | } |
971 | | |
972 | | /* reload the master process */ |
973 | | static int cli_parse_show_proc(char **args, char *payload, struct appctx *appctx, void *private) |
974 | 0 | { |
975 | 0 | struct cli_showproc_ctx *ctx; |
976 | |
|
977 | 0 | ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); |
978 | |
|
979 | 0 | if (!cli_has_level(appctx, ACCESS_LVL_OPER)) |
980 | 0 | return 1; |
981 | | |
982 | 0 | if (*args[2]) { |
983 | |
|
984 | 0 | if (strcmp(args[2], "debug") == 0) |
985 | 0 | ctx->debug = 1; |
986 | 0 | else |
987 | 0 | return cli_err(appctx, "'show proc' only supports 'debug' as argument\n"); |
988 | 0 | } |
989 | | |
990 | 0 | return 0; |
991 | 0 | } |
992 | | |
993 | | /* reload the master process */ |
994 | | static int cli_parse_reload(char **args, char *payload, struct appctx *appctx, void *private) |
995 | 0 | { |
996 | 0 | struct stconn *scb = NULL; |
997 | 0 | struct stream *strm = NULL; |
998 | 0 | struct connection *conn = NULL; |
999 | 0 | int fd = -1; |
1000 | 0 | int hardreload = 0; |
1001 | 0 | struct mworker_proc *proc; |
1002 | |
|
1003 | 0 | if (!cli_has_level(appctx, ACCESS_LVL_OPER)) |
1004 | 0 | return 1; |
1005 | | |
1006 | 0 | list_for_each_entry(proc, &proc_list, list) { |
1007 | | /* if there is a process with PROC_O_INIT, i.e. new worker is |
1008 | | * doing its init routine, block the reload |
1009 | | */ |
1010 | 0 | if (proc->options & PROC_O_INIT) { |
1011 | 0 | chunk_printf(&trash, "Success=0\n"); |
1012 | 0 | chunk_appendf(&trash, "--\n"); |
1013 | 0 | chunk_appendf(&trash, "Another reload is still in progress.\n"); |
1014 | |
|
1015 | 0 | if (applet_putchk(appctx, &trash) == -1) |
1016 | 0 | return 0; |
1017 | | |
1018 | 0 | return 1; |
1019 | 0 | } |
1020 | 0 | } |
1021 | | |
1022 | | /* hard reload requested */ |
1023 | 0 | if (*args[0] == 'h') |
1024 | 0 | hardreload = 1; |
1025 | | |
1026 | | /* This ask for a synchronous reload, which means we will keep this FD |
1027 | | instead of closing it. */ |
1028 | |
|
1029 | 0 | scb = appctx_sc(appctx); |
1030 | 0 | if (scb) |
1031 | 0 | strm = sc_strm(scb); |
1032 | 0 | if (strm && strm->scf) |
1033 | 0 | conn = sc_conn(strm->scf); |
1034 | 0 | if (conn) |
1035 | 0 | fd = conn_fd(conn); |
1036 | | |
1037 | | /* Send the FD of the current session to the "cli_reload" FD, which won't be polled */ |
1038 | 0 | if (fd != -1 && send_fd_uxst(proc_self->ipc_fd[0], fd) == 0) { |
1039 | 0 | fd_delete(fd); /* avoid the leak of the FD after sending it via the socketpair */ |
1040 | 0 | } |
1041 | 0 | mworker_reload(hardreload); |
1042 | |
|
1043 | 0 | return 1; |
1044 | 0 | } |
1045 | | |
1046 | | /* Displays if the current reload failed or succeed. |
1047 | | * If the startup-logs is available, dump it. */ |
1048 | | static int cli_io_handler_show_loadstatus(struct appctx *appctx) |
1049 | 0 | { |
1050 | 0 | struct mworker_proc *proc; |
1051 | |
|
1052 | 0 | if (!cli_has_level(appctx, ACCESS_LVL_OPER)) |
1053 | 0 | return 1; |
1054 | | |
1055 | | /* if the worker is still in the process of starting, we have to |
1056 | | * wait a little bit before trying again to get a final status. |
1057 | | */ |
1058 | 0 | list_for_each_entry(proc, &proc_list, list) { |
1059 | 0 | if (proc->options & PROC_O_INIT) { |
1060 | 0 | appctx->t->expire = tick_add(now_ms, 50); |
1061 | 0 | return 0; |
1062 | 0 | } |
1063 | 0 | } |
1064 | | |
1065 | 0 | if (load_status == 0) |
1066 | 0 | chunk_printf(&trash, "Success=0\n"); |
1067 | 0 | else |
1068 | 0 | chunk_printf(&trash, "Success=1\n"); |
1069 | |
|
1070 | 0 | if (startup_logs && ring_data(startup_logs) > 1) |
1071 | 0 | chunk_appendf(&trash, "--\n"); |
1072 | |
|
1073 | 0 | if (applet_putchk(appctx, &trash) == -1) |
1074 | 0 | return 0; |
1075 | | |
1076 | 0 | if (startup_logs) { |
1077 | 0 | appctx->cli_ctx.io_handler = NULL; |
1078 | 0 | ring_attach_cli(startup_logs, appctx, 0); |
1079 | 0 | return 0; |
1080 | 0 | } |
1081 | 0 | return 1; |
1082 | 0 | } |
1083 | | |
1084 | | static int mworker_parse_global_max_reloads(char **args, int section_type, struct proxy *curpx, |
1085 | | const struct proxy *defpx, const char *file, int linenum, char **err) |
1086 | 0 | { |
1087 | 0 | if (!(global.mode & MODE_DISCOVERY)) |
1088 | 0 | return 0; |
1089 | | |
1090 | 0 | if (strcmp(args[0], "mworker-max-reloads") == 0) { |
1091 | 0 | if (too_many_args(1, args, err, NULL)) |
1092 | 0 | return -1; |
1093 | | |
1094 | 0 | if (*(args[1]) == 0) { |
1095 | 0 | memprintf(err, "'%s' expects an integer argument.", args[0]); |
1096 | 0 | return -1; |
1097 | 0 | } |
1098 | | |
1099 | 0 | max_reloads = atol(args[1]); |
1100 | 0 | if (max_reloads < 0) { |
1101 | 0 | memprintf(err, "'%s' expects a positive value or zero.", args[0]); |
1102 | 0 | return -1; |
1103 | 0 | } |
1104 | 0 | } else { |
1105 | 0 | BUG_ON(1, "Triggered in mworker_parse_global_max_reloads() by unsupported keyword."); |
1106 | 0 | return -1; |
1107 | 0 | } |
1108 | | |
1109 | 0 | return 0; |
1110 | 0 | } |
1111 | | |
1112 | | void mworker_free_child(struct mworker_proc *child) |
1113 | 0 | { |
1114 | 0 | int i; |
1115 | |
|
1116 | 0 | if (child == NULL) |
1117 | 0 | return; |
1118 | | |
1119 | 0 | for (i = 0; child->command && child->command[i]; i++) |
1120 | 0 | ha_free(&child->command[i]); |
1121 | |
|
1122 | 0 | ha_free(&child->command); |
1123 | 0 | ha_free(&child->id); |
1124 | 0 | ha_free(&child->version); |
1125 | 0 | free(child); |
1126 | 0 | } |
1127 | | |
1128 | | /* Creates and binds dedicated master CLI 'reload' sockpair and listeners */ |
1129 | | void mworker_create_master_cli(void) |
1130 | 0 | { |
1131 | 0 | struct wordlist *it, *c; |
1132 | |
|
1133 | 0 | if (!LIST_ISEMPTY(&mworker_cli_conf)) { |
1134 | 0 | char *path = NULL; |
1135 | |
|
1136 | 0 | list_for_each_entry_safe(c, it, &mworker_cli_conf, list) { |
1137 | 0 | if (mworker_cli_master_proxy_new_listener(c->s) == NULL) { |
1138 | 0 | ha_alert("Can't create the master's CLI.\n"); |
1139 | 0 | exit(EXIT_FAILURE); |
1140 | 0 | } |
1141 | 0 | LIST_DELETE(&c->list); |
1142 | 0 | free(c->s); |
1143 | 0 | free(c); |
1144 | 0 | } |
1145 | | /* Creates the mcli_reload listener, which is the listener used |
1146 | | * to retrieve the master CLI session which asked for the reload. |
1147 | | * |
1148 | | * ipc_fd[1] will be used as a listener, and ipc_fd[0] |
1149 | | * will be used to send the FD of the session. |
1150 | | * |
1151 | | * Both FDs will be kept in the master. The sockets are |
1152 | | * created only if they weren't inherited. |
1153 | | */ |
1154 | 0 | if (proc_self->ipc_fd[1] == -1) { |
1155 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, proc_self->ipc_fd) < 0) { |
1156 | 0 | ha_alert("Can't create the mcli_reload socketpair.\n"); |
1157 | 0 | exit(EXIT_FAILURE); |
1158 | 0 | } |
1159 | 0 | } |
1160 | | |
1161 | | /* Create the mcli_reload listener from the proc_self struct */ |
1162 | 0 | memprintf(&path, "sockpair@%d", proc_self->ipc_fd[1]); |
1163 | |
|
1164 | 0 | mcli_reload_bind_conf = mworker_cli_master_proxy_new_listener(path); |
1165 | 0 | if (mcli_reload_bind_conf == NULL) { |
1166 | 0 | ha_alert("Can't create the mcli_reload listener.\n"); |
1167 | 0 | exit(EXIT_FAILURE); |
1168 | 0 | } |
1169 | 0 | ha_free(&path); |
1170 | 0 | } |
1171 | 0 | } |
1172 | | |
1173 | | /* This function fills proc_list for master-worker mode and creates a sockpair, |
1174 | | * copied after master-worker fork() to each process context to enable master |
1175 | | * CLI at worker side (worker can send its status to master).It only returns if |
1176 | | * everything is OK. If something fails, it exits. |
1177 | | */ |
1178 | | void mworker_prepare_master(void) |
1179 | 0 | { |
1180 | 0 | struct mworker_proc *tmproc; |
1181 | |
|
1182 | 0 | setenv("HAPROXY_MWORKER", "1", 1); |
1183 | |
|
1184 | 0 | if (getenv("HAPROXY_MWORKER_REEXEC") == NULL) { |
1185 | |
|
1186 | 0 | tmproc = mworker_proc_new(); |
1187 | 0 | if (!tmproc) { |
1188 | 0 | ha_alert("Cannot allocate process structures.\n"); |
1189 | 0 | exit(EXIT_FAILURE); |
1190 | 0 | } |
1191 | 0 | tmproc->options |= PROC_O_TYPE_MASTER; /* master */ |
1192 | 0 | tmproc->pid = pid; |
1193 | 0 | tmproc->timestamp = start_date.tv_sec; |
1194 | 0 | proc_self = tmproc; |
1195 | |
|
1196 | 0 | LIST_APPEND(&proc_list, &tmproc->list); |
1197 | 0 | } |
1198 | | |
1199 | 0 | tmproc = mworker_proc_new(); |
1200 | 0 | if (!tmproc) { |
1201 | 0 | ha_alert("Cannot allocate process structures.\n"); |
1202 | 0 | exit(EXIT_FAILURE); |
1203 | 0 | } |
1204 | | /* worker */ |
1205 | 0 | tmproc->options |= (PROC_O_TYPE_WORKER | PROC_O_INIT); |
1206 | | |
1207 | | /* create a sockpair to copy it via fork(), thus it will be in |
1208 | | * master and in worker processes |
1209 | | */ |
1210 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, tmproc->ipc_fd) < 0) { |
1211 | 0 | ha_alert("Cannot create worker master CLI socketpair.\n"); |
1212 | 0 | exit(EXIT_FAILURE); |
1213 | 0 | } |
1214 | 0 | LIST_APPEND(&proc_list, &tmproc->list); |
1215 | 0 | } |
1216 | | |
1217 | | static void mworker_loop() |
1218 | 0 | { |
1219 | | |
1220 | | /* Busy polling makes no sense in the master :-) */ |
1221 | 0 | global.tune.options &= ~GTUNE_BUSY_POLLING; |
1222 | |
|
1223 | 0 | mworker_unblock_sigchld(); |
1224 | 0 | mworker_cleantasks(); |
1225 | |
|
1226 | 0 | mworker_catch_sigchld(NULL); /* ensure we clean the children in case |
1227 | | some SIGCHLD were lost */ |
1228 | |
|
1229 | 0 | jobs++; /* this is the "master" job, we want to take care of the |
1230 | | signals even if there is no listener so the poll loop don't |
1231 | | leave */ |
1232 | |
|
1233 | 0 | fork_poller(); |
1234 | 0 | run_thread_poll_loop(NULL); |
1235 | 0 | } |
1236 | | |
1237 | | void mworker_run_master(void) |
1238 | 0 | { |
1239 | 0 | struct mworker_proc *child, *it; |
1240 | |
|
1241 | 0 | close(devnullfd); |
1242 | 0 | devnullfd = -1; |
1243 | |
|
1244 | 0 | proc_self->failedreloads = 0; /* reset the number of failure */ |
1245 | 0 | mworker_loop(); |
1246 | | #if defined(USE_OPENSSL) && !defined(OPENSSL_NO_DH) |
1247 | | ssl_free_dh(); |
1248 | | #endif |
1249 | 0 | master = 0; |
1250 | | /* close useless master sockets */ |
1251 | 0 | mworker_cli_proxy_stop(); |
1252 | | |
1253 | | /* free proc struct of other processes */ |
1254 | 0 | list_for_each_entry_safe(child, it, &proc_list, list) { |
1255 | | /* close the FD of the master side for all |
1256 | | * workers, we don't need to close the worker |
1257 | | * side of other workers since it's done with |
1258 | | * the bind_proc */ |
1259 | 0 | if (child->ipc_fd[0] >= 0) { |
1260 | 0 | close(child->ipc_fd[0]); |
1261 | 0 | child->ipc_fd[0] = -1; |
1262 | 0 | } |
1263 | 0 | LIST_DELETE(&child->list); |
1264 | 0 | mworker_free_child(child); |
1265 | 0 | child = NULL; |
1266 | 0 | } |
1267 | | /* master must leave */ |
1268 | 0 | exit(0); |
1269 | 0 | } |
1270 | | |
1271 | | /* This function at first does master-worker fork. It creates then GLOBAL and |
1272 | | * MASTER proxies, allocates listeners for these proxies and binds a GLOBAL |
1273 | | * proxy listener in worker process on ipc_fd[1] and MASTER proxy listener |
1274 | | * in master process on ipc_fd[0]. ipc_fd[0] and ipc_fd[1] are the "ends" of the |
1275 | | * sockpair, created in prepare_master(). This sockpair is copied via fork to |
1276 | | * each process and serves as communication channel between master and worker |
1277 | | * (master CLI applet is attached in master process to MASTER proxy). This |
1278 | | * function returns only if everything is OK. If something fails, it exits. |
1279 | | */ |
1280 | | void mworker_apply_master_worker_mode(void) |
1281 | 0 | { |
1282 | 0 | int worker_pid; |
1283 | 0 | struct mworker_proc *child; |
1284 | 0 | char *sock_name = NULL; |
1285 | 0 | char *errmsg = NULL; |
1286 | |
|
1287 | 0 | worker_pid = fork(); |
1288 | 0 | switch (worker_pid) { |
1289 | 0 | case -1: |
1290 | 0 | ha_alert("[%s.main()] Cannot fork.\n", progname); |
1291 | |
|
1292 | 0 | exit(EXIT_FAILURE); |
1293 | 0 | case 0: |
1294 | 0 | if (daemon_fd[1] >= 0) { |
1295 | 0 | close(daemon_fd[1]); |
1296 | 0 | daemon_fd[1] = -1; |
1297 | 0 | } |
1298 | | |
1299 | | /* This one must not be exported, it's internal! */ |
1300 | 0 | unsetenv("HAPROXY_MWORKER_REEXEC"); |
1301 | 0 | ha_random_jump128(1); |
1302 | |
|
1303 | 0 | list_for_each_entry(child, &proc_list, list) { |
1304 | 0 | if ((child->options & PROC_O_TYPE_WORKER) && (child->options & PROC_O_INIT)) { |
1305 | 0 | close(child->ipc_fd[0]); |
1306 | 0 | child->ipc_fd[0] = -1; |
1307 | | /* proc_self needs to point to the new forked worker in |
1308 | | * worker's context, as it's dereferenced in |
1309 | | * mworker_sockpair_register_per_thread(), called for |
1310 | | * master and for worker. |
1311 | | */ |
1312 | 0 | proc_self = child; |
1313 | | /* attach listener to GLOBAL proxy on child->ipc_fd[1] */ |
1314 | 0 | if (mworker_cli_global_proxy_new_listener(child) < 0) |
1315 | 0 | exit(EXIT_FAILURE); |
1316 | | |
1317 | 0 | break; |
1318 | 0 | } |
1319 | | |
1320 | | /* need to close reload sockpair fds, inherited after master's execvp and fork(), |
1321 | | * we can't close these fds in master before the fork(), as ipc_fd[1] serves after |
1322 | | * the mworker_reexec to obtain the MCLI client connection fd, like this we can |
1323 | | * write to this connection fd the content of the startup_logs ring. |
1324 | | */ |
1325 | 0 | if (child->options & PROC_O_TYPE_MASTER) { |
1326 | 0 | if (child->ipc_fd[0] > 0) |
1327 | 0 | close(child->ipc_fd[0]); |
1328 | 0 | if (child->ipc_fd[1] > 0) |
1329 | 0 | close(child->ipc_fd[1]); |
1330 | 0 | } |
1331 | 0 | } |
1332 | 0 | break; |
1333 | 0 | default: |
1334 | | /* in parent */ |
1335 | 0 | ha_notice("Initializing new worker (%d)\n", worker_pid); |
1336 | 0 | master = 1; |
1337 | | |
1338 | | /* in exec mode, there's always exactly one thread. Failure to |
1339 | | * set these ones now will result in nbthread being detected |
1340 | | * automatically. |
1341 | | */ |
1342 | 0 | global.nbtgroups = 1; |
1343 | 0 | global.nbthread = 1; |
1344 | | |
1345 | | /* creates MASTER proxy */ |
1346 | 0 | if (mworker_cli_create_master_proxy(&errmsg) < 0) { |
1347 | 0 | ha_alert("Can't create MASTER proxy: %s\n", errmsg); |
1348 | 0 | free(errmsg); |
1349 | 0 | exit(EXIT_FAILURE); |
1350 | 0 | } |
1351 | | |
1352 | | /* attaches servers to all existed workers on its shared MCLI sockpair ends, ipc_fd[0] */ |
1353 | 0 | if (mworker_cli_attach_server(&errmsg) < 0) { |
1354 | 0 | ha_alert("Can't attach servers needed for master CLI %s\n", errmsg ? errmsg : ""); |
1355 | 0 | free(errmsg); |
1356 | 0 | exit(EXIT_FAILURE); |
1357 | 0 | } |
1358 | | |
1359 | | /* creates reload sockpair and listeners for master CLI (-S) */ |
1360 | 0 | mworker_create_master_cli(); |
1361 | | |
1362 | | /* find the right mworker_proc */ |
1363 | 0 | list_for_each_entry(child, &proc_list, list) { |
1364 | 0 | if ((child->options & PROC_O_TYPE_WORKER) && (child->options & PROC_O_INIT)) { |
1365 | 0 | child->timestamp = date.tv_sec; |
1366 | 0 | child->pid = worker_pid; |
1367 | 0 | child->version = strdup(haproxy_version); |
1368 | |
|
1369 | 0 | close(child->ipc_fd[1]); |
1370 | 0 | child->ipc_fd[1] = -1; |
1371 | | |
1372 | | /* attach listener to MASTER proxy on child->ipc_fd[0] */ |
1373 | 0 | memprintf(&sock_name, "sockpair@%d", child->ipc_fd[0]); |
1374 | 0 | if (mworker_cli_master_proxy_new_listener(sock_name) == NULL) { |
1375 | 0 | ha_free(&sock_name); |
1376 | 0 | exit(EXIT_FAILURE); |
1377 | 0 | } |
1378 | 0 | ha_free(&sock_name); |
1379 | |
|
1380 | 0 | break; |
1381 | 0 | } |
1382 | 0 | } |
1383 | 0 | } |
1384 | 0 | } |
1385 | | |
1386 | | static struct cfg_kw_list mworker_kws = {{ }, { |
1387 | | { CFG_GLOBAL, "mworker-max-reloads", mworker_parse_global_max_reloads, KWF_DISCOVERY }, |
1388 | | { 0, NULL, NULL }, |
1389 | | }}; |
1390 | | |
1391 | | INITCALL1(STG_REGISTER, cfg_register_keywords, &mworker_kws); |
1392 | | |
1393 | | |
1394 | | /* register cli keywords */ |
1395 | | static struct cli_kw_list cli_kws = {{ },{ |
1396 | | { { "@<relative pid>", NULL }, "@<relative pid> : send a command to the <relative pid> process", NULL, cli_io_handler_show_proc, NULL, NULL, ACCESS_MASTER_ONLY}, |
1397 | | { { "@!<pid>", NULL }, "@!<pid> : send a command to the <pid> process", cli_parse_default, NULL, NULL, NULL, ACCESS_MASTER_ONLY}, |
1398 | | { { "@master", NULL }, "@master : send a command to the master process", cli_parse_default, NULL, NULL, NULL, ACCESS_MASTER_ONLY}, |
1399 | | { { "show", "proc", NULL }, "show proc : show processes status", cli_parse_show_proc, cli_io_handler_show_proc, NULL, NULL, ACCESS_MASTER_ONLY}, |
1400 | | { { "reload", NULL }, "reload : achieve a soft-reload (-sf) of haproxy", cli_parse_reload, NULL, NULL, NULL, ACCESS_MASTER_ONLY}, |
1401 | | { { "hard-reload", NULL }, "hard-reload : achieve a hard-reload (-st) of haproxy", cli_parse_reload, NULL, NULL, NULL, ACCESS_MASTER_ONLY}, |
1402 | | { { "_loadstatus", NULL }, NULL, cli_parse_default, cli_io_handler_show_loadstatus, NULL, NULL, ACCESS_MASTER_ONLY}, |
1403 | | {{},} |
1404 | | }}; |
1405 | | |
1406 | | INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); |