blob: 0c4ae21b270110f5e1ef3cfbeca02a0a9cccc84e [file] [log] [blame]
William Lallemand48dfbbd2019-04-01 11:29:53 +02001/*
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
William Lallemand3fa724d2019-04-01 11:29:55 +020013#include <errno.h>
14#include <fcntl.h>
15#include <signal.h>
William Lallemand48dfbbd2019-04-01 11:29:53 +020016#include <stdlib.h>
17#include <string.h>
William Lallemande25473c2019-04-01 11:29:56 +020018#include <sys/wait.h>
William Lallemand48dfbbd2019-04-01 11:29:53 +020019
20#include <common/mini-clist.h>
21
William Lallemand3fa724d2019-04-01 11:29:55 +020022#include <proto/fd.h>
23#include <proto/listener.h>
William Lallemande25473c2019-04-01 11:29:56 +020024#include <proto/log.h>
William Lallemand48dfbbd2019-04-01 11:29:53 +020025#include <proto/mworker.h>
William Lallemand3cd95d22019-04-01 11:29:54 +020026#include <proto/signal.h>
William Lallemand48dfbbd2019-04-01 11:29:53 +020027
28#include <types/global.h>
William Lallemand9001ce82019-04-01 11:29:57 +020029#include <types/peers.h>
30#include <proto/proxy.h>
William Lallemande25473c2019-04-01 11:29:56 +020031#include <types/signal.h>
William Lallemand48dfbbd2019-04-01 11:29:53 +020032
William Lallemande25473c2019-04-01 11:29:56 +020033#if defined(USE_SYSTEMD)
34#include <systemd/sd-daemon.h>
35#endif
36
37static int exitcode = -1;
38
William Lallemande25473c2019-04-01 11:29:56 +020039/* ----- children processes handling ----- */
William Lallemand48dfbbd2019-04-01 11:29:53 +020040
William Lallemand48dfbbd2019-04-01 11:29:53 +020041/*
William Lallemande25473c2019-04-01 11:29:56 +020042 * Send signal to every known children.
43 */
44
45static void mworker_kill(int sig)
46{
William Lallemand3f128872019-04-01 11:29:59 +020047 struct mworker_proc *child;
William Lallemande25473c2019-04-01 11:29:56 +020048
William Lallemand3f128872019-04-01 11:29:59 +020049 list_for_each_entry(child, &proc_list, list) {
50 /* careful there, we must be sure that the pid > 0, we don't want to emit a kill -1 */
51 if ((child->type == 'w' || child->type == 'e') && (child->reloads == 0) && (child->pid > 0))
52 kill(child->pid, sig);
William Lallemande25473c2019-04-01 11:29:56 +020053 }
54}
55
56
57/* return 1 if a pid is a current child otherwise 0 */
William Lallemand3f128872019-04-01 11:29:59 +020058int mworker_current_child(int pid)
William Lallemande25473c2019-04-01 11:29:56 +020059{
William Lallemand3f128872019-04-01 11:29:59 +020060 struct mworker_proc *child;
William Lallemande25473c2019-04-01 11:29:56 +020061
William Lallemand3f128872019-04-01 11:29:59 +020062 list_for_each_entry(child, &proc_list, list) {
63 if ((child->type == 'w' || child->type == 'e') && (child->reloads == 0) && (child->pid == pid))
William Lallemande25473c2019-04-01 11:29:56 +020064 return 1;
65 }
66 return 0;
67}
68
William Lallemand3f128872019-04-01 11:29:59 +020069/*
70 * Return the number of new and old children (including workers and external
71 * processes)
72 */
73int mworker_child_nb()
74{
75 struct mworker_proc *child;
76 int ret = 0;
77
78 list_for_each_entry(child, &proc_list, list) {
79 if ((child->type == 'w' || child->type == 'e'))
80 ret++;
81 }
82
83 return ret;
84}
85
86
William Lallemande25473c2019-04-01 11:29:56 +020087/*
William Lallemand48dfbbd2019-04-01 11:29:53 +020088 * serialize the proc list and put it in the environment
89 */
90void mworker_proc_list_to_env()
91{
92 char *msg = NULL;
93 struct mworker_proc *child;
94
95 list_for_each_entry(child, &proc_list, list) {
96 if (child->pid > -1)
97 memprintf(&msg, "%s|type=%c;fd=%d;pid=%d;rpid=%d;reloads=%d;timestamp=%d", msg ? msg : "", child->type, child->ipc_fd[0], child->pid, child->relative_pid, child->reloads, child->timestamp);
98 }
99 if (msg)
100 setenv("HAPROXY_PROCESSES", msg, 1);
101}
102
103/*
104 * unserialize the proc list from the environment
105 */
106void mworker_env_to_proc_list()
107{
108 char *msg, *token = NULL, *s1;
109
110 msg = getenv("HAPROXY_PROCESSES");
111 if (!msg)
112 return;
113
114 while ((token = strtok_r(msg, "|", &s1))) {
115 struct mworker_proc *child;
116 char *subtoken = NULL;
117 char *s2;
118
119 msg = NULL;
120
121 child = calloc(1, sizeof(*child));
122
123 while ((subtoken = strtok_r(token, ";", &s2))) {
124
125 token = NULL;
126
127 if (strncmp(subtoken, "type=", 5) == 0) {
128 child->type = *(subtoken+5);
129 if (child->type == 'm') /* we are in the master, assign it */
130 proc_self = child;
131 } else if (strncmp(subtoken, "fd=", 3) == 0) {
132 child->ipc_fd[0] = atoi(subtoken+3);
133 } else if (strncmp(subtoken, "pid=", 4) == 0) {
134 child->pid = atoi(subtoken+4);
135 } else if (strncmp(subtoken, "rpid=", 5) == 0) {
136 child->relative_pid = atoi(subtoken+5);
137 } else if (strncmp(subtoken, "reloads=", 8) == 0) {
138 /* we reloaded this process once more */
139 child->reloads = atoi(subtoken+8) + 1;
140 } else if (strncmp(subtoken, "timestamp=", 10) == 0) {
141 child->timestamp = atoi(subtoken+10);
142 }
143 }
144 if (child->pid)
145 LIST_ADDQ(&proc_list, &child->list);
146 else
147 free(child);
148 }
149
150 unsetenv("HAPROXY_PROCESSES");
151}
William Lallemand3cd95d22019-04-01 11:29:54 +0200152
153/* Signal blocking and unblocking */
154
155void mworker_block_signals()
156{
157 sigset_t set;
158
159 sigemptyset(&set);
160 sigaddset(&set, SIGUSR1);
161 sigaddset(&set, SIGUSR2);
162 sigaddset(&set, SIGHUP);
163 sigaddset(&set, SIGCHLD);
164 ha_sigmask(SIG_SETMASK, &set, NULL);
165}
166
167void mworker_unblock_signals()
168{
169 haproxy_unblock_signals();
170}
William Lallemand3fa724d2019-04-01 11:29:55 +0200171
William Lallemande25473c2019-04-01 11:29:56 +0200172/* ----- mworker signal handlers ----- */
173
174/*
175 * When called, this function reexec haproxy with -sf followed by current
176 * children PIDs and possibly old children PIDs if they didn't leave yet.
177 */
178void mworker_catch_sighup(struct sig_handler *sh)
179{
180 mworker_reload();
181}
182
183void mworker_catch_sigterm(struct sig_handler *sh)
184{
185 int sig = sh->arg;
186
187#if defined(USE_SYSTEMD)
188 if (global.tune.options & GTUNE_USE_SYSTEMD) {
189 sd_notify(0, "STOPPING=1");
190 }
191#endif
192 ha_warning("Exiting Master process...\n");
193 mworker_kill(sig);
194}
195
196/*
197 * Wait for every children to exit
198 */
199
200void mworker_catch_sigchld(struct sig_handler *sh)
201{
202 int exitpid = -1;
203 int status = 0;
204 struct mworker_proc *child, *it;
205 int childfound;
206
207restart_wait:
208
209 childfound = 0;
210
211 exitpid = waitpid(-1, &status, WNOHANG);
212 if (exitpid > 0) {
213 if (WIFEXITED(status))
214 status = WEXITSTATUS(status);
215 else if (WIFSIGNALED(status))
216 status = 128 + WTERMSIG(status);
217 else if (WIFSTOPPED(status))
218 status = 128 + WSTOPSIG(status);
219 else
220 status = 255;
221
William Lallemand3f128872019-04-01 11:29:59 +0200222 /* delete the child from the process list */
William Lallemande25473c2019-04-01 11:29:56 +0200223 list_for_each_entry_safe(child, it, &proc_list, list) {
224 if (child->pid != exitpid)
225 continue;
226
227 LIST_DEL(&child->list);
228 close(child->ipc_fd[0]);
229 childfound = 1;
230 break;
231 }
232
William Lallemand3f128872019-04-01 11:29:59 +0200233 if (!childfound) {
234 /* We didn't find the PID in the list, that shouldn't happen but we can emit a warning */
William Lallemande25473c2019-04-01 11:29:56 +0200235 ha_warning("Worker %d exited with code %d (%s)\n", exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit");
236 } else {
William Lallemand3f128872019-04-01 11:29:59 +0200237 /* check if exited child was is a current child */
238 if (child->reloads == 0) {
239 if (child->type == 'w')
240 ha_alert("Current worker #%d (%d) exited with code %d (%s)\n", child->relative_pid, exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit");
241
William Lallemande25473c2019-04-01 11:29:56 +0200242 if (status != 0 && status != 130 && status != 143
243 && !(global.tune.options & GTUNE_NOEXIT_ONFAILURE)) {
244 ha_alert("exit-on-failure: killing every workers with SIGTERM\n");
245 if (exitcode < 0)
246 exitcode = status;
247 mworker_kill(SIGTERM);
248 }
249 } else {
William Lallemand3f128872019-04-01 11:29:59 +0200250 if (child->type == 'w') {
251 ha_warning("Former worker #%d (%d) exited with code %d (%s)\n", child->relative_pid, exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit");
252 delete_oldpid(exitpid);
253 }
William Lallemande25473c2019-04-01 11:29:56 +0200254 }
255 free(child);
256 }
257
258 /* do it again to check if it was the last worker */
259 goto restart_wait;
260 }
261 /* Better rely on the system than on a list of process to check if it was the last one */
262 else if (exitpid == -1 && errno == ECHILD) {
263 ha_warning("All workers exited. Exiting... (%d)\n", (exitcode > 0) ? exitcode : status);
264 atexit_flag = 0;
265 if (exitcode > 0)
266 exit(exitcode);
267 exit(status); /* parent must leave using the latest status code known */
268 }
269
270}
271
William Lallemand3fa724d2019-04-01 11:29:55 +0200272/* ----- IPC FD (sockpair) related ----- */
273
274/* This wrapper is called from the workers. It is registered instead of the
275 * normal listener_accept() so the worker can exit() when it detects that the
276 * master closed the IPC FD. If it's not a close, we just call the regular
277 * listener_accept() function */
278void mworker_accept_wrapper(int fd)
279{
280 char c;
281 int ret;
282
283 while (1) {
284 ret = recv(fd, &c, 1, MSG_PEEK);
285 if (ret == -1) {
286 if (errno == EINTR)
287 continue;
288 if (errno == EAGAIN) {
289 fd_cant_recv(fd);
290 return;
291 }
292 break;
293 } else if (ret > 0) {
294 listener_accept(fd);
295 return;
296 } else if (ret == 0) {
297 /* At this step the master is down before
298 * this worker perform a 'normal' exit.
299 * So we want to exit with an error but
300 * other threads could currently process
301 * some stuff so we can't perform a clean
302 * deinit().
303 */
304 exit(EXIT_FAILURE);
305 }
306 }
307 return;
308}
309
310/*
311 * This function register the accept wrapper for the sockpair of the master worker
312 */
313void mworker_pipe_register()
314{
315 /* The iocb should be already initialized with listener_accept */
316 if (fdtab[proc_self->ipc_fd[1]].iocb == mworker_accept_wrapper)
317 return;
318
319 fcntl(proc_self->ipc_fd[1], F_SETFL, O_NONBLOCK);
320 /* In multi-tread, we need only one thread to process
321 * events on the pipe with master
322 */
323 fd_insert(proc_self->ipc_fd[1], fdtab[proc_self->ipc_fd[1]].owner, mworker_accept_wrapper, 1);
324 fd_want_recv(proc_self->ipc_fd[1]);
325}
William Lallemand9001ce82019-04-01 11:29:57 +0200326
327/* ----- proxies ----- */
328/*
329 * Upon a reload, the master worker needs to close all listeners FDs but the mworker_pipe
330 * fd, and the FD provided by fd@
331 */
332void mworker_cleanlisteners()
333{
334 struct listener *l, *l_next;
335 struct proxy *curproxy;
336 struct peers *curpeers;
337
338 /* we might have to unbind some peers sections from some processes */
339 for (curpeers = cfg_peers; curpeers; curpeers = curpeers->next) {
340 if (!curpeers->peers_fe)
341 continue;
342
343 stop_proxy(curpeers->peers_fe);
344 /* disable this peer section so that it kills itself */
345 signal_unregister_handler(curpeers->sighandler);
346 task_delete(curpeers->sync_task);
347 task_free(curpeers->sync_task);
348 curpeers->sync_task = NULL;
349 task_free(curpeers->peers_fe->task);
350 curpeers->peers_fe->task = NULL;
351 curpeers->peers_fe = NULL;
352 }
353
354 for (curproxy = proxies_list; curproxy; curproxy = curproxy->next) {
355 int listen_in_master = 0;
356
357 list_for_each_entry_safe(l, l_next, &curproxy->conf.listeners, by_fe) {
358 /* remove the listener, but not those we need in the master... */
359 if (!(l->options & LI_O_MWORKER)) {
360 /* unbind the listener but does not close if
361 the FD is inherited with fd@ from the parent
362 process */
363 if (l->options & LI_O_INHERITED)
364 unbind_listener_no_close(l);
365 else
366 unbind_listener(l);
367 delete_listener(l);
368 } else {
369 listen_in_master = 1;
370 }
371 }
372 /* if the proxy shouldn't be in the master, we stop it */
373 if (!listen_in_master)
374 curproxy->state = PR_STSTOPPED;
375 }
376}