Hi, On top of those patches, here a 3 more patches. The first one makes the systemd wrapper check for a HAPROXY_STATS_SOCKET environment variable. If set, it will use that as an argument to -x, when reloading the process. The second one sends listening unix sockets, as well as IPv4/v6 sockets. I see no reason not to, and that means we no longer have to wait until the old process close the socket before being able to accept new connections on it. The third one adds a new global optoin, nosockettransfer, if set, we assume we will never try to transfer listening sockets through the stats socket, and close any socket nout bound to our process, to save a few file descriptors.
Regards, Olivier
>From 8d6c38b6824346b096ba31757ab62bc986a433b3 Mon Sep 17 00:00:00 2001 From: Olivier Houchard <ohouch...@haproxy.com> Date: Sun, 9 Apr 2017 16:28:10 +0200 Subject: [PATCH 7/9] MINOR: systemd wrapper: add support for passing the -x option. Make the systemd wrapper chech if HAPROXY_STATS_SOCKET if set. If set, it will use it as an argument to the "-x" option, which makes haproxy asks for any listening socket, on the stats socket, in order to achieve reloads with no new connection lost. --- contrib/systemd/haproxy.service.in | 2 ++ src/haproxy-systemd-wrapper.c | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/contrib/systemd/haproxy.service.in b/contrib/systemd/haproxy.service.in index dca81a2..05bb716 100644 --- a/contrib/systemd/haproxy.service.in +++ b/contrib/systemd/haproxy.service.in @@ -3,6 +3,8 @@ Description=HAProxy Load Balancer After=network.target [Service] +# You can point the environment variable HAPROXY_STATS_SOCKET to a stats +# socket if you want seamless reloads. Environment="CONFIG=/etc/haproxy/haproxy.cfg" "PIDFILE=/run/haproxy.pid" ExecStartPre=@SBINDIR@/haproxy -f $CONFIG -c -q ExecStart=@SBINDIR@/haproxy-systemd-wrapper -f $CONFIG -p $PIDFILE diff --git a/src/haproxy-systemd-wrapper.c b/src/haproxy-systemd-wrapper.c index f6a9c85..1d00111 100644 --- a/src/haproxy-systemd-wrapper.c +++ b/src/haproxy-systemd-wrapper.c @@ -92,11 +92,15 @@ static void spawn_haproxy(char **pid_strv, int nb_pid) pid = fork(); if (!pid) { char **argv; + char *stats_socket = NULL; int i; int argno = 0; /* 3 for "haproxy -Ds -sf" */ - argv = calloc(4 + main_argc + nb_pid + 1, sizeof(char *)); + if (nb_pid > 0) + stats_socket = getenv("HAPROXY_STATS_SOCKET"); + argv = calloc(4 + main_argc + nb_pid + 1 + + stats_socket != NULL ? 2 : 0, sizeof(char *)); if (!argv) { fprintf(stderr, SD_NOTICE "haproxy-systemd-wrapper: failed to calloc(), please try again later.\n"); exit(1); @@ -121,6 +125,10 @@ static void spawn_haproxy(char **pid_strv, int nb_pid) argv[argno++] = "-sf"; for (i = 0; i < nb_pid; ++i) argv[argno++] = pid_strv[i]; + if (stats_socket != NULL) { + argv[argno++] = "-x"; + argv[argno++] = stats_socket; + } } argv[argno] = NULL; -- 2.9.3
>From df5e6e70f2e73fca9e28ba273904ab5c5acf53d3 Mon Sep 17 00:00:00 2001 From: Olivier Houchard <ohouch...@haproxy.com> Date: Sun, 9 Apr 2017 19:17:15 +0200 Subject: [PATCH 8/9] MINOR: cli: When sending listening sockets, send unix sockets too. Send unix sockets, as well as IPv4/IPv6 sockets, so that we don't have to wait for the old process to die before being able to bind those. --- src/cli.c | 6 ++++-- src/proto_uxst.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/cli.c b/src/cli.c index d5ff11f..533f792 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1067,7 +1067,8 @@ static int _getsocks(char **args, struct appctx *appctx, void *private) list_for_each_entry(l, &px->conf.listeners, by_fe) { /* Only transfer IPv4/IPv6 sockets */ if (l->proto->sock_family == AF_INET || - l->proto->sock_family == AF_INET6) + l->proto->sock_family == AF_INET6 || + l->proto->sock_family == AF_UNIX) tot_fd_nb++; } px = px->next; @@ -1120,7 +1121,8 @@ static int _getsocks(char **args, struct appctx *appctx, void *private) /* Only transfer IPv4/IPv6 sockets */ if (l->state >= LI_LISTEN && (l->proto->sock_family == AF_INET || - l->proto->sock_family == AF_INET6)) { + l->proto->sock_family == AF_INET6 || + l->proto->sock_family == AF_UNIX)) { memcpy(&tmpfd[i % MAX_SEND_FD], &l->fd, sizeof(l->fd)); if (!l->netns) tmpbuf[curoff++] = 0; diff --git a/src/proto_uxst.c b/src/proto_uxst.c index 27ff0fa..d68267e 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -150,6 +150,54 @@ static void destroy_uxst_socket(const char *path) ********************************/ +static int uxst_find_compatible_fd(struct listener *l) +{ + struct xfer_sock_list *xfer_sock = xfer_sock_list; + int ret = -1; + + while (xfer_sock) { + struct sockaddr_un *un1 = (void *)&l->addr; + struct sockaddr_un *un2 = (void *)&xfer_sock->addr; + + /* + * The bound socket's path as returned by getsockaddr + * will be the temporary name <sockname>.XXXXX.tmp, + * so we can't just compare the two names + */ + if (xfer_sock->addr.ss_family == AF_UNIX && + strncmp(un1->sun_path, un2->sun_path, + strlen(un1->sun_path)) == 0) { + char *after_sockname = un2->sun_path + + strlen(un1->sun_path); + /* Make a reasonnable effort to check that + * it is indeed a haproxy-generated temporary + * name, it's not perfect, but probably good enough. + */ + if (after_sockname[0] == '.') { + after_sockname++; + while (after_sockname[0] >= '0' && + after_sockname[0] <= '9') + after_sockname++; + if (!strcmp(after_sockname, ".tmp")) + break; + } + } + xfer_sock = xfer_sock->next; + } + if (xfer_sock != NULL) { + ret = xfer_sock->fd; + if (xfer_sock == xfer_sock_list) + xfer_sock_list = xfer_sock->next; + if (xfer_sock->prev) + xfer_sock->prev->next = xfer_sock->next; + if (xfer_sock->next) + xfer_sock->next->prev = xfer_sock->next->prev; + free(xfer_sock); + } + return ret; + +} + /* This function creates a UNIX socket associated to the listener. It changes * the state from ASSIGNED to LISTEN. The socket is NOT enabled for polling. * The return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL. It @@ -179,6 +227,8 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle if (listener->state != LI_ASSIGNED) return ERR_NONE; /* already bound */ + if (listener->fd == -1) + listener->fd = uxst_find_compatible_fd(listener); path = ((struct sockaddr_un *)&listener->addr)->sun_path; /* if the listener already has an fd assigned, then we were offered the -- 2.9.3
>From e81ed243f466f5d1e9850ee31ca6bba5d6af6f63 Mon Sep 17 00:00:00 2001 From: Olivier Houchard <ohouch...@haproxy.com> Date: Sun, 9 Apr 2017 23:39:52 +0200 Subject: [PATCH 9/9] MINOR: global: Add a new option to close unused sockets. By default, processes keep all bound socket opened, even those that are only used by other processes, so that we can provide all sockets when asked for them. However it may be of no use for people who don't use socket transfer, so provide a new global option, "nosockettransfer", that restore the old behavior of closing the sockets not belonging to the process. --- doc/configuration.txt | 7 +++++++ include/types/global.h | 2 ++ src/cfgparse.c | 5 +++++ src/haproxy.c | 9 +++++++-- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 05f0701..c18821e 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -587,6 +587,7 @@ The following keywords are supported in the "global" section : - nosplice - nogetaddrinfo - noreuseport + - nosockettransfer - spread-checks - server-state-base - server-state-file @@ -1250,6 +1251,12 @@ noreuseport Disables the use of SO_REUSEPORT - see socket(7). It is equivalent to the command line argument "-dR". +nosockettrasnfer + By default, each haproxy process keeps all sockets opened, event those that + are only used by another processes, so that any process can provide all the + sockets, to make reloads seamless. This option disables this, and close all + unused sockets, to save some file descriptors. + spread-checks <0..50, in percent> Sometimes it is desirable to avoid sending agent and health checks to servers at exact intervals, for instance when many logical servers are diff --git a/include/types/global.h b/include/types/global.h index df8e2c6..57b969d 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -62,6 +62,8 @@ #define GTUNE_USE_REUSEPORT (1<<6) #define GTUNE_RESOLVE_DONTFAIL (1<<7) +#define GTUNE_SOCKET_TRANSFER (1<<8) + /* Access level for a stats socket */ #define ACCESS_LVL_NONE 0 #define ACCESS_LVL_USER 1 diff --git a/src/cfgparse.c b/src/cfgparse.c index e1b6b3e..0f9c15a 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -659,6 +659,11 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm) goto out; global.tune.options &= ~GTUNE_USE_REUSEPORT; } + else if (!strcmp(args[0], "nosockettransfer")) { + if (alertif_too_many_args(0, file, linenum, args, &err_code)) + goto out; + global.tune.options &= ~GTUNE_SOCKET_TRANSFER; + } else if (!strcmp(args[0], "quiet")) { if (alertif_too_many_args(0, file, linenum, args, &err_code)) goto out; diff --git a/src/haproxy.c b/src/haproxy.c index af86b78..0e3350c 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -864,6 +864,7 @@ static void init(int argc, char **argv) #if defined(SO_REUSEPORT) global.tune.options |= GTUNE_USE_REUSEPORT; #endif + global.tune.options |= GTUNE_SOCKET_TRANSFER; pid = getpid(); progname = *argv; @@ -2164,8 +2165,12 @@ int main(int argc, char **argv) px = proxy; while (px != NULL) { if (px->bind_proc && px->state != PR_STSTOPPED) { - if (!(px->bind_proc & (1UL << proc))) - zombify_proxy(px); + if (!(px->bind_proc & (1UL << proc))) { + if (global.tune.options & GTUNE_SOCKET_TRANSFER) + zombify_proxy(px); + else + stop_proxy(px); + } } px = px->next; } -- 2.9.3