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 | |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 39 | /* ----- children processes handling ----- */ |
William Lallemand | 48dfbbd | 2019-04-01 11:29:53 +0200 | [diff] [blame] | 40 | |
William Lallemand | 48dfbbd | 2019-04-01 11:29:53 +0200 | [diff] [blame] | 41 | /* |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 42 | * Send signal to every known children. |
| 43 | */ |
| 44 | |
| 45 | static void mworker_kill(int sig) |
| 46 | { |
William Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 47 | struct mworker_proc *child; |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 48 | |
William Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 49 | 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 Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 53 | } |
| 54 | } |
| 55 | |
| 56 | |
| 57 | /* return 1 if a pid is a current child otherwise 0 */ |
William Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 58 | int mworker_current_child(int pid) |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 59 | { |
William Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 60 | struct mworker_proc *child; |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 61 | |
William Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 62 | list_for_each_entry(child, &proc_list, list) { |
| 63 | if ((child->type == 'w' || child->type == 'e') && (child->reloads == 0) && (child->pid == pid)) |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 64 | return 1; |
| 65 | } |
| 66 | return 0; |
| 67 | } |
| 68 | |
William Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 69 | /* |
| 70 | * Return the number of new and old children (including workers and external |
| 71 | * processes) |
| 72 | */ |
| 73 | int 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 Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 87 | /* |
William Lallemand | 48dfbbd | 2019-04-01 11:29:53 +0200 | [diff] [blame] | 88 | * serialize the proc list and put it in the environment |
| 89 | */ |
| 90 | void 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 | */ |
| 106 | void 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 Lallemand | 3cd95d2 | 2019-04-01 11:29:54 +0200 | [diff] [blame] | 152 | |
| 153 | /* Signal blocking and unblocking */ |
| 154 | |
| 155 | void 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 | |
| 167 | void mworker_unblock_signals() |
| 168 | { |
| 169 | haproxy_unblock_signals(); |
| 170 | } |
William Lallemand | 3fa724d | 2019-04-01 11:29:55 +0200 | [diff] [blame] | 171 | |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 172 | /* ----- 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 | */ |
| 178 | void mworker_catch_sighup(struct sig_handler *sh) |
| 179 | { |
| 180 | mworker_reload(); |
| 181 | } |
| 182 | |
| 183 | void 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 | |
| 200 | void 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 | |
| 207 | restart_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 Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 222 | /* delete the child from the process list */ |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 223 | 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 Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 233 | if (!childfound) { |
| 234 | /* We didn't find the PID in the list, that shouldn't happen but we can emit a warning */ |
William Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 235 | ha_warning("Worker %d exited with code %d (%s)\n", exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit"); |
| 236 | } else { |
William Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 237 | /* 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 Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 242 | 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 Lallemand | 3f12887 | 2019-04-01 11:29:59 +0200 | [diff] [blame] | 250 | 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 Lallemand | e25473c | 2019-04-01 11:29:56 +0200 | [diff] [blame] | 254 | } |
| 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 Lallemand | 3fa724d | 2019-04-01 11:29:55 +0200 | [diff] [blame] | 272 | /* ----- 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 */ |
| 278 | void 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 | */ |
| 313 | void 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 Lallemand | 9001ce8 | 2019-04-01 11:29:57 +0200 | [diff] [blame] | 326 | |
| 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 | */ |
| 332 | void 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 | } |