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