A master process is detached from the terminal and then spawns worker processes. The master does nothing but waits for a signal on which it will kill its workers.
A later patch will allow for restarting the workers. To facilitate this the master keeps all bound sockets open and by using the socket_cache these can be re-used. This will enable haproxy to be restarted without refusing or resetting connections to existing sockets. --- examples/examples.cfg | 1 + examples/haproxy.vim | 2 +- include/types/global.h | 1 + src/cfgparse.c | 3 + src/haproxy.c | 112 ++++++++++++++++++++++++++++++++++++++++------- src/log.c | 1 + src/protocols.c | 5 ++- 7 files changed, 106 insertions(+), 19 deletions(-) diff --git a/examples/examples.cfg b/examples/examples.cfg index 3499e7b..739f8a8 100644 --- a/examples/examples.cfg +++ b/examples/examples.cfg @@ -8,6 +8,7 @@ global # chroot /tmp # nbproc 2 # daemon +# master_worker # debug # quiet diff --git a/examples/haproxy.vim b/examples/haproxy.vim index 728a1c5..44efdaf 100644 --- a/examples/haproxy.vim +++ b/examples/haproxy.vim @@ -41,7 +41,7 @@ syn match hapIp2 /,\(\d\{1,3}\.\d\{1,3}\.\d\{1,3}\.\d\{1,3}\)\?:\d\{1,5} " Parameters syn keyword hapParam chroot cliexp clitimeout contimeout -syn keyword hapParam daemon debug disabled +syn keyword hapParam daemon debug disabled master_worker syn keyword hapParam enabled syn keyword hapParam fullconn syn keyword hapParam gid grace group diff --git a/include/types/global.h b/include/types/global.h index 535b833..fecbeae 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -38,6 +38,7 @@ #define MODE_VERBOSE 0x10 #define MODE_STARTING 0x20 #define MODE_FOREGROUND 0x40 +#define MODE_MASTER_WORKER 0x80 /* list of last checks to perform, depending on config options */ #define LSTCHK_CAP_BIND 0x00000001 /* check that we can bind to any port */ diff --git a/src/cfgparse.c b/src/cfgparse.c index e7ce4c3..dec3ef6 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -499,6 +499,9 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm) else if (!strcmp(args[0], "daemon")) { global.mode |= MODE_DAEMON; } + else if (!strcmp(args[0], "master_worker")) { + global.mode |= MODE_MASTER_WORKER; + } else if (!strcmp(args[0], "debug")) { global.mode |= MODE_DEBUG; } diff --git a/src/haproxy.c b/src/haproxy.c index 8a50f8c..3c20f92 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -232,6 +232,7 @@ void usage(char *name) " -c check mode : only check config files and exit\n" " -n sets the maximum total # of connections (%d)\n" " -m limits the usable amount of memory (in MB)\n" + " -M master/worker mode\n" " -N sets the default, per-proxy maximum # of connections (%d)\n" " -L set local peer name (default to hostname)\n" " -p writes pids of all children to this file\n" @@ -476,6 +477,8 @@ void init(int argc, char **argv) arg_mode |= MODE_CHECK; else if (*flag == 'D') arg_mode |= MODE_DAEMON; + else if (*flag == 'M') + arg_mode |= MODE_MASTER_WORKER; else if (*flag == 'q') arg_mode |= MODE_QUIET; else if (*flag == 's' && (flag[1] == 'f' || flag[1] == 't')) { @@ -529,7 +532,8 @@ void init(int argc, char **argv) global.mode = MODE_STARTING | /* during startup, we want most of the alerts */ (arg_mode & (MODE_DAEMON | MODE_FOREGROUND | MODE_VERBOSE - | MODE_QUIET | MODE_CHECK | MODE_DEBUG)); + | MODE_QUIET | MODE_CHECK | MODE_DEBUG + | MODE_MASTER_WORKER)); if (LIST_ISEMPTY(&cfg_cfgfiles)) usage(old_argv); @@ -632,16 +636,18 @@ void init(int argc, char **argv) global.mode &= ~(MODE_DAEMON | MODE_QUIET); } global.mode |= (arg_mode & (MODE_DAEMON | MODE_FOREGROUND | MODE_QUIET | - MODE_VERBOSE | MODE_DEBUG )); + MODE_VERBOSE | MODE_DEBUG | + MODE_MASTER_WORKER)); if ((global.mode & MODE_DEBUG) && (global.mode & (MODE_DAEMON | MODE_QUIET))) { Warning("<debug> mode incompatible with <quiet> and <daemon>. Keeping <debug> only.\n"); global.mode &= ~(MODE_DAEMON | MODE_QUIET); } - if ((global.nbproc > 1) && !(global.mode & MODE_DAEMON)) { + if ((global.nbproc > 1) && + !(global.mode & (MODE_DAEMON|MODE_MASTER_WORKER))) { if (!(global.mode & (MODE_FOREGROUND | MODE_DEBUG))) - Warning("<nbproc> is only meaningful in daemon mode. Setting limit to 1 process.\n"); + Warning("<nbproc> is only meaningful if daemon or master/worker modes are enabled. Setting limit to 1 process.\n"); global.nbproc = 1; } @@ -901,7 +907,6 @@ void deinit(void) free(global.node); global.node = NULL; free(global.desc); global.desc = NULL; free(fdtab); fdtab = NULL; - free(oldpids); oldpids = NULL; list_for_each_entry_safe(wl, wlb, &cfg_cfgfiles, list) { LIST_DEL(&wl->list); @@ -969,6 +974,11 @@ void run_poll_loop() if (jobs == 0) break; + if (is_master) { + sleep(1); + continue; + } + /* The poller will ensure it returns around <next> */ cur_poller.poll(&cur_poller, next); } @@ -1055,6 +1065,7 @@ static void setid_late(const char *name) void run(int argc, char **argv) { int err, retry; + int mode = global.mode & (MODE_DAEMON|MODE_MASTER_WORKER); struct rlimit limit; static FILE *pidfile = NULL; char errmsg[100]; @@ -1062,9 +1073,15 @@ void run(int argc, char **argv) socket_cache_make_all_available(); init(argc, argv); + /* daemon and master/worker modes can't be altered on restart */ + if (is_master) + global.mode = (global.mode & + ~(MODE_DAEMON|MODE_MASTER_WORKER)) | mode; close_log(); /* It will automatically be reopened as needed */ signal_register_fct(SIGQUIT, dump, SIGQUIT); signal_register_fct(SIGUSR1, sig_soft_stop, SIGUSR1); + if (global.mode & MODE_MASTER_WORKER) + signal_register_fct(SIGUSR2, sig_restart, SIGUSR2); signal_register_fct(SIGHUP, sig_dump_state, SIGHUP); signal_register_fct(SIGCHLD, sig_reaper, SIGCHLD); @@ -1173,7 +1190,8 @@ void run(int argc, char **argv) struct timeval w; err = start_proxies(retry == 0 || nb_oldpids == 0); /* exit the loop on no error or fatal error */ - if ((err & (ERR_RETRYABLE|ERR_FATAL)) != ERR_RETRYABLE) + if (!(is_master && retry == MAX_START_RETRIES) && + (err & (ERR_RETRYABLE|ERR_FATAL)) != ERR_RETRYABLE) break; if (nb_oldpids == 0 || retry == 0) break; @@ -1182,7 +1200,7 @@ void run(int argc, char **argv) * listening sockets. So on those platforms, it would be wiser to * simply send SIGUSR1, which will not be undoable. */ - if (tell_old_pids(SIGTTOU) == 0) { + if (tell_old_pids(is_master ? SIGUSR1 : SIGTTOU) == 0) { /* no need to wait if we can't contact old pids */ retry = 0; continue; @@ -1255,25 +1273,80 @@ void run(int argc, char **argv) socket_cache_gc(); - if (global.mode & MODE_DAEMON) { + if (global.mode & (MODE_DAEMON|MODE_MASTER_WORKER)) { struct proxy *px; int ret = 0; int proc; + /* Create master if it doesn't already exist, + * is needed and is to be detached + */ + if (!is_master && global.mode & MODE_MASTER_WORKER) { + if (global.mode & MODE_DAEMON) { + ret = fork(); + if (ret < 0) { + Alert("[%s.run()] Cannot fork.\n", + argv[0]); + protocol_unbind_all(); + exit(1); /* there has been an error */ + } + if (ret > 0) + exit(0); /* Exit original process */ + + /* Child */ + setsid(); + close_log(); + pid = getpid(); + } + if (!(global.mode & MODE_QUIET)) + send_log(NULL, LOG_INFO, "Master started\n"); + } + + if (pidfile != NULL) { + rewind(pidfile); + ftruncate(fileno(pidfile), 0); + if (global.mode & MODE_MASTER_WORKER) { + fprintf(pidfile, "%d\n", pid); + fflush(pidfile); + } + } + + /* Store PIDs of worker processes in oldpid so + * they can be signaled later */ + nb_oldpids = global.nbproc; + free(oldpids); + oldpids = malloc(nb_oldpids * sizeof(*oldpids)); + if (!oldpids) { + send_log(NULL, LOG_ERR, "Cannot allocate memory " + "for oldpids.\n"); + protocol_unbind_all(); + exit(1); /* there has been an error */ + } + /* the father launches the required number of processes */ for (proc = 0; proc < global.nbproc; proc++) { ret = fork(); if (ret < 0) { - Alert("[%s.run()] Cannot fork.\n", argv[0]); + send_log(NULL, LOG_ERR, "Cannot fork.\n"); protocol_unbind_all(); exit(1); /* there has been an error */ } - else if (ret == 0) /* child breaks here */ + else if (ret == 0) { /* child breaks here */ + if (!(global.mode & MODE_MASTER_WORKER)) + setsid(); + is_master = 0; + close_log(); + pid = getpid(); + if (!(global.mode & MODE_QUIET)) + send_log(NULL, LOG_INFO, + "Worker #%d started\n", proc); break; + } if (pidfile != NULL) { fprintf(pidfile, "%d\n", ret); fflush(pidfile); } + oldpids[proc] = ret; relative_pid++; /* each child will get a different one */ } /* close the pidfile both in children and father */ @@ -1283,19 +1356,24 @@ void run(int argc, char **argv) /* We won't ever use this anymore */ free(oldpids); oldpids = NULL; + if (proc == global.nbproc) { + if (!(global.mode & MODE_MASTER_WORKER)) + /* The parent process is no longer needed */ + exit(0); + else + is_master = 1; + } + /* we might have to unbind some proxies from some processes */ px = proxy; while (px != NULL) { if (px->bind_proc && px->state != PR_STSTOPPED) { - if (!(px->bind_proc & (1 << proc))) + if (px->bind_proc & (1 << proc)) stop_proxy(px); } px = px->next; } - if (proc == global.nbproc) - exit(0); /* parent must leave */ - /* if we're NOT in QUIET mode, we should now close the 3 first FDs to ensure * that we can detach from the TTY. We MUST NOT do it in other cases since * it would have already be done, and 0-2 would have been affected to listening @@ -1307,12 +1385,12 @@ void run(int argc, char **argv) global.mode &= ~MODE_VERBOSE; global.mode |= MODE_QUIET; /* ensure that we won't say anything from now */ } - pid = getpid(); /* update child's pid */ - setsid(); + fork_poller(); } - drop_capabilities(); + if (!is_master) + drop_capabilities(); protocol_enable_all(); /* diff --git a/src/log.c b/src/log.c index 5da1260..d4614ab 100644 --- a/src/log.c +++ b/src/log.c @@ -229,6 +229,7 @@ void close_log(void) if (logfdinet >= 0) close(logfdinet); logfdinet = -1; + tvsec = -1; } /* diff --git a/src/protocols.c b/src/protocols.c index 5eddb2c..6c5ba27 100644 --- a/src/protocols.c +++ b/src/protocols.c @@ -82,6 +82,8 @@ int disable_all_listeners(struct protocol *proto) return ERR_NONE; } +extern int is_master; + /* This function closes the listening socket for the specified listener, * provided that it's already in a listening state. The listener enters the * LI_ASSIGNED state. It always returns ERR_NONE. This function is intended @@ -93,7 +95,8 @@ int unbind_listener(struct listener *listener) EV_FD_CLR(listener->fd, DIR_RD); if (listener->state >= LI_LISTEN) { - fd_delete(listener->fd); + if (!is_master) + fd_delete(listener->fd); listener->state = LI_ASSIGNED; } return ERR_NONE; -- 1.7.2.3