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