[ANNOUNCE] haproxy 1.4.13
Hi all, since Hank's report about the crash on files containing an empty pattern, I have released 1.4.13 with the fix for this regression introduced in 1.4.11 when trying to fix handling of empty lines. In the commit, I stated that I could only reproduce it with files composed only of empty lines but this is wrong. In fact if patterns are not loaded in a tree (eg: regex, prefixes, ...) then the issue can happen if the file ends with an empty line, so this is quite a serious regression because many pattern lists may be manually edited and contain an empty line at the end. The nice point is that it crashes upon startup so if your process is running, there is nothing to worry about. Of course, my tests only stressed IP addresses which are loaded in a tree... So please avoid 1.4.11-1.4.12 and use only 1.4.13 : site index : http://haproxy.1wt.eu/ sources: http://haproxy.1wt.eu/download/1.4/src/ changelog : http://haproxy.1wt.eu/download/1.4/src/CHANGELOG binaries : http://haproxy.1wt.eu/download/1.4/bin/ Thanks and sorry for the mess, Willy
Apache Compatible HAProxy Log Formatting?
Has anyone worked out a way to get HAProxy to output logging in an Apache Combined Log Format? Most log analysis tools out there already speak Apache log format and I was hoping to avoid writing a parser for what is essentially mostly the same information. Anyone got a work around for this situation? Best, Joel
Re: Apache Compatible HAProxy Log Formatting?
On Wed, 09 Mar 2011 00:40:12 -0800 Joel Krauska jkrau...@gmail.com wrote: Has anyone worked out a way to get HAProxy to output logging in an Apache Combined Log Format? Most log analysis tools out there already speak Apache log format and I was hoping to avoid writing a parser for what is essentially mostly the same information. Anyone got a work around for this situation? Best, Joel Hi Joel, you can try option httplog clf instead of option httplog Regards, Hervé. -- Your Network supports your *BUSINESS !* Appliances de *contrôle d'activité* et d'*optimisation* du réseau EXCELIANCE - Rule your Network ! - www.exceliance.fr ZAC des Metz - 3 Rue du petit robinson 78350 Jouy en Josas Tél: +33 1 30 67 60 74 - Fax: +33 1 75 43 40 70
mode tcp with mode http/shoutcast radio issue
Hello, I'm trying to use: frontend front mode http and backend web mode http backend radio mode tcp But will error with Unable to use proxy with wrong mode, required: http, has: tcp. Ok, this is understandable, so I changed to: frontend front mode tcp and backend web mode http backend radio mode tcp This works. Problem is that I cannot use acl's like the following: hdr(User-Agent) -i WinampMPEG/5.60 hdr(Host) -i 80.86.106.35 I guess this is because it receives the tcp stream and doen't analyze headers anymore. So I tried to match at destination ip using dst 80.86.106.35. This also fails because I am using a proxy, and the connection is in fact being addressed to the proxy ip, not the external host that I am trying to contact. If I use dst $proxy_ip, it matches. But it matchess _ALL_ connections, and I want to separate http ones from radio streaming. Also, trying to use mode http for the backend radio will give me 502 Bad Gateway errors when trying to connect to Shoutcast radio servers. mode tcp is the only way I could make it work. Also found a patch for haproxy+icey but I'm not very keen on recompiling. Any ideas? The information contained herein is intended for its addressee(s) only and it is privileged or otherwise confidential. Any unauthorized distribution, amendment or disclosure hereof is strictly forbidden by the law. Please find complete and translated versions at http://www.rompetrol.com/disclaimer.html
Re: Script greasmonkey to navigate in Haproxy docs ...
Ignoring the fact that some else (http://www.mail-archive.com/haproxy@formilux.org/msg02764.html) had a similar idea, I pushed a public Git repo of the docs in Markdown format. I've only used a few simple regexes, which I lazily dumped in the repo as well, as well as a healthy dose of manual labor. There are still some glitches here and there, but it's a good start overall. Unfortunately Github strips the anchors I've inserted to navigate the docs, but any other markdown converter should do it nicely (I'm using Wolf Rentzsch Markdown Live on osx). Help yourselves: https://github.com/tmslnz/HAProxy_Markdown ~~~ tommasolanza.com +44 772 1692021 @tmslnz
Re: Apache Compatible HAProxy Log Formatting?
On Wed, Mar 09, 2011 at 01:10:12AM -0800, Joel Krauska wrote: On 3/9/11 1:03 AM, Graeme Donaldson wrote: 2011/3/9 Hervé COMMOWICKhcommow...@exceliance.fr: http://haproxy.1wt.eu/download/1.4/doc/configuration.txt option httplog [ clf ] Enable logging of HTTP request, session state and timers May be used in sections : defaults | frontend | listen | backend yes |yes | yes | yes Arguments : clf if the clf argument is added, then the output format will be the CLF format instead of HAProxy's default HTTP format. You can use this when you need to feed HAProxy's logs through a specific log analyser which only support the CLF format and which is not extensible. Be careful, HAproxy's CLF is Apache's common log format, not the combined log format that Joel originally asked for. The latter also logs the User-Agent and Referer headers. Regards, Graeme. Ah yes. That's 95% though. I'll take it. Combined would be a great addition. Hey, simply add header captures and you'll get it at a different position though. Even better would be apache style custom format syntax. No, it serves a different purpose. Some info that apache has are not in haproxy and many of the info haproxy logs cannot be expressed with the custom format. Anyway, we've planned to do a big rework of the logs subsystem to make them much more extensible. Regards, Willy
[PATCH 04/26] Reap children
In the master/worker the workers will be child processes of the master and should be reaped after they exit. --- src/haproxy.c | 11 +++ 1 files changed, 11 insertions(+), 0 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index 3fe8889..99d8c0d 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -48,6 +48,7 @@ #include sys/prctl.h #include sys/capability.h #endif +#include sys/wait.h #ifdef DEBUG_FULL #include assert.h @@ -287,6 +288,15 @@ void sig_listen(struct sig_handler *sh) } /* + * upon SIGCHLD reap child + */ +void sig_reaper(struct sig_handler *sh) +{ + int status; + while(waitpid(-1, status, WNOHANG) 0); +} + +/* * this function dumps every server's state when the process receives SIGHUP. */ void sig_dump_state(struct sig_handler *sh) @@ -1045,6 +1055,7 @@ void run(int argc, char **argv) signal_register_fct(SIGQUIT, dump, SIGQUIT); signal_register_fct(SIGUSR1, sig_soft_stop, SIGUSR1); signal_register_fct(SIGHUP, sig_dump_state, SIGHUP); + signal_register_fct(SIGCHLD, sig_reaper, SIGCHLD); /* Always catch SIGPIPE even on platforms which define MSG_NOSIGNAL. * Some recent FreeBSD setups report broken pipes, and MSG_NOSIGNAL -- 1.7.2.3
[PATCH 02/26] Add a socket cache
When a socket is closed, typically because a proxy is deleted, don't close the file descriptor. Rather keep a cache which can be looked-up and used when a subsequent proxy is started. The aim is to avoid unbinding and binding to ports and thus interrupting services unnecessarily. This will be used in conjunction with a facility to allow haproxy to reinitialise itself. --- include/proto/fd.h |5 ++ include/types/fd.h | 28 ++ include/types/global.h |1 + src/fd.c | 133 src/haproxy.c |4 ++ src/proto_tcp.c| 10 +++- src/proto_uxst.c | 12 - 7 files changed, 191 insertions(+), 2 deletions(-) diff --git a/include/proto/fd.h b/include/proto/fd.h index 1cba33b..3ca4a97 100644 --- a/include/proto/fd.h +++ b/include/proto/fd.h @@ -87,6 +87,11 @@ static inline void fd_insert(int fd) maxfd = fd + 1; } +void socket_cache_make_all_available(void); +int socket_cache_get(const struct listener *listener); +void socket_cache_gc(void); +int socket_cache_add(int fd, struct listener *listener); + #endif /* _PROTO_FD_H */ diff --git a/include/types/fd.h b/include/types/fd.h index f698863..eb253a5 100644 --- a/include/types/fd.h +++ b/include/types/fd.h @@ -136,6 +136,34 @@ extern int maxfd; /* # of the highest fd + 1 */ extern int totalconn; /* total # of terminated sessions */ extern int actconn; /* # of active sessions */ +/* The socket cache allows bound sockets to be looked up + * by the code that would bind them + */ +struct socket_cache { + struct socket_cache *next; /* next entry in the cache, or NULL */ + int fd; /* the listen socket */ + int state; /* state: NEW, ASSIGNED */ + char *interface;/* interface name or NULL */ + + /* Body */ + int sock_type; /* As passed to socket() */ + int sock_prot; /* As passed to socket() */ + socklen_t sock_addrlen; /* socket address length, used by bind() */ + struct sockaddr_storage addr; /* the address we listen to */ + int options;/* socket options : LI_O_* */ + int maxconn;/* maximum connections allowed on this listener */ + unsigned int backlog; /* if set, listen backlog */ + union { /* protocol-dependant access restrictions */ + struct {/* UNIX socket permissions */ + uid_t uid; /* -1 to leave unchanged */ + gid_t gid; /* -1 to leave unchanged */ + mode_t mode;/* 0 to leave unchanged */ + int level; /* access level (ACCESS_LVL_*) */ + } ux; + } perm; + int maxseg; /* for TCP, advertised MSS */ +}; + #endif /* _TYPES_FD_H */ /* diff --git a/include/types/global.h b/include/types/global.h index b6c60dd..535b833 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -121,6 +121,7 @@ extern const int zero; extern const int one; extern const struct linger nolinger; extern int stopping; /* non zero means stopping in progress */ +extern int is_master; extern char hostname[MAX_HOSTNAME_LEN]; extern char localpeer[MAX_HOSTNAME_LEN]; diff --git a/src/fd.c b/src/fd.c index 80bddd6..c2f16bb 100644 --- a/src/fd.c +++ b/src/fd.c @@ -18,6 +18,8 @@ #include common/compat.h #include common/config.h +#include types/protocols.h + #include proto/fd.h #include proto/port_range.h @@ -31,6 +33,7 @@ struct poller pollers[MAX_POLLERS]; struct poller cur_poller; int nbpollers = 0; +static struct socket_cache *socket_cache = NULL; /* Deletes an FD from the fdsets, and recomputes the maxfd limit. * The file descriptor is also closed. @@ -174,6 +177,136 @@ int fork_poller() return 1; } +enum { + SC_AVAILABLE, + SC_INVALID, + SC_INUSE +}; + +void socket_cache_make_all_available(void) +{ + struct socket_cache *e; + + for (e = socket_cache; e; e = e-next) + e-state = SC_AVAILABLE; +} + +void socket_cache_gc(void) +{ + struct socket_cache *e, *next, *prev = NULL; + + for (e = socket_cache; e; e = next) { + next = e-next; + if (e-state == SC_INUSE) { + prev = e; + continue; + } + if (prev) + prev-next = e-next; + else + socket_cache = e-next; + if (e-state != SC_INVALID) + fd_delete(e-fd); + free(e); + } +} + +static void socket_cache_body_assign(struct socket_cache *e, +const struct listener *listener)
[PATCH 06/26] Add vAlert and vWarning
--- include/proto/log.h | 12 src/log.c | 35 --- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/include/proto/log.h b/include/proto/log.h index f2ecf0b..45bec88 100644 --- a/include/proto/log.h +++ b/include/proto/log.h @@ -25,6 +25,7 @@ #include stdio.h #include syslog.h +#include stdarg.h #include common/config.h #include common/memory.h @@ -38,12 +39,23 @@ extern struct pool_head *pool2_requri; * Displays the message on stderr with the date and pid. Overrides the quiet * mode during startup. */ +void vAlert(const char *fmt, va_list argp); + +/* + * Displays the message on stderr with the date and pid. Overrides the quiet + * mode during startup. + */ void Alert(const char *fmt, ...) __attribute__ ((format(printf, 1, 2))); /* * Displays the message on stderr with the date and pid. */ +void vWarning(const char *fmt, va_list argp); + +/* + * Displays the message on stderr with the date and pid. + */ void Warning(const char *fmt, ...) __attribute__ ((format(printf, 1, 2))); diff --git a/src/log.c b/src/log.c index 119536c..4cd3b55 100644 --- a/src/log.c +++ b/src/log.c @@ -91,6 +91,17 @@ static void __Alert(int send_log, const char *fmt, va_list argp) * with a priority of LOG_ERR. * Overrides the quiet mode during startup. */ +void vAlert(const char *fmt, va_list argp) +{ + __Alert(1, fmt, argp); +} + +/* + * Displays the message on stderr with the date and pid. + * Also logs the same message using the prevailing logger, if any, + * with a priority of LOG_ERR. + * Overrides the quiet mode during startup. + */ void Alert(const char *fmt, ...) { va_list argp; @@ -116,27 +127,37 @@ static void Alert_no_send_log(const char *fmt, ...) /* * Displays the message on stderr with the date and pid. */ -void Warning(const char *fmt, ...) +void vWarning(const char *fmt, va_list argp) { - va_list argp; + va_list argp2; struct tm tm; if (!(global.mode MODE_QUIET) || (global.mode MODE_VERBOSE)) { - va_start(argp, fmt); - vsend_log(NULL, LOG_WARNING, fmt, argp); - va_end(argp); + va_copy(argp2, argp); + vsend_log(NULL, LOG_WARNING, fmt, argp2); + va_end(argp2); - va_start(argp, fmt); get_localtime(date.tv_sec, tm); fprintf(stderr, [WARNING] %03d/%02d%02d%02d (%d) : , tm.tm_yday, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)getpid()); vfprintf(stderr, fmt, argp); fflush(stderr); - va_end(argp); } } /* + * Displays the message on stderr with the date and pid. + */ +void Warning(const char *fmt, ...) +{ + va_list argp; + + va_start(argp, fmt); + vWarning(fmt, argp); + va_end(argp); +} + +/* * Displays the message on out only if quiet mode is not set. */ void qfprintf(FILE *out, const char *fmt, ...) -- 1.7.2.3
[PATCH 07/26] Add is_master flag
This is in preparation for adding Master/Worker mode --- src/haproxy.c |3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index 99d8c0d..1f02902 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -136,6 +136,9 @@ struct global global = { /*/ int stopping; /* non zero means stopping in progress */ +int is_master = 0; /* non zero means that master/worker mode +* has been activated and the current process +* is the master */ int jobs = 0; /* number of active jobs (conns, listeners, active tasks, ...) */ /* Here we store informations about the pids of the processes we may pause -- 1.7.2.3
[PATCH 09/26] Clean up logger on deinit()
This will allow new logging configuration to take effect once changes to reinitialise haproxy are introduced. --- include/proto/log.h |5 + src/haproxy.c |1 + src/log.c | 17 ++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/include/proto/log.h b/include/proto/log.h index 45bec88..ecd51b1 100644 --- a/include/proto/log.h +++ b/include/proto/log.h @@ -66,6 +66,11 @@ void qfprintf(FILE *out, const char *fmt, ...) __attribute__ ((format(printf, 2, 3))); /* + * Close and clean up logs + */ +void close_log(void); + +/* * This function sends a syslog message to both log servers of a proxy, * or to global log servers if the proxy is NULL. * It also tries not to waste too much time computing the message header. diff --git a/src/haproxy.c b/src/haproxy.c index 1f02902..a0bc8f5 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -1055,6 +1055,7 @@ void run(int argc, char **argv) socket_cache_make_all_available(); init(argc, argv); + close_log(); /* It will automatically be reopened as needed */ signal_register_fct(SIGQUIT, dump, SIGQUIT); signal_register_fct(SIGUSR1, sig_soft_stop, SIGUSR1); signal_register_fct(SIGHUP, sig_dump_state, SIGHUP); diff --git a/src/log.c b/src/log.c index 4cd3b55..5da1260 100644 --- a/src/log.c +++ b/src/log.c @@ -217,6 +217,20 @@ static inline int logsrv_addrlen(const struct logsrv *logsrv) return -1; } +static int logfdunix = -1; /* syslog to AF_UNIX socket */ +static int logfdinet = -1; /* syslog to AF_INET socket */ +static long tvsec = -1;/* to force the string to be initialized */ + +void close_log(void) +{ + if (logfdunix = 0) + close(logfdunix); + logfdunix = -1; + if (logfdinet = 0) + close(logfdinet); + logfdinet = -1; +} + /* * This function sends a syslog message to both log servers of a proxy, * or to global log servers if the proxy is NULL. @@ -226,9 +240,6 @@ static inline int logsrv_addrlen(const struct logsrv *logsrv) static void vsend_log(struct proxy *p, int level, const char *message, va_list argp) { - static int logfdunix = -1; /* syslog to AF_UNIX socket */ - static int logfdinet = -1; /* syslog to AF_INET socket */ - static long tvsec = -1; /* to force the string to be initialized */ static char logmsg[MAX_SYSLOG_LEN]; static char *dataptr = NULL; int fac_level; -- 1.7.2.3
[PATCH 12/26] Free global.chroot earlier
--- src/haproxy.c |2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index dc2739b..8c9053f 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -1131,6 +1131,7 @@ void run(int argc, char **argv) } chdir(/); } + free(global.chroot); global.chroot = NULL; setid_early(argv[0]); @@ -1280,7 +1281,6 @@ void run(int argc, char **argv) /* We won't ever use this anymore */ free(oldpids);oldpids = NULL; - free(global.chroot); global.chroot = NULL; free(global.pidfile); global.pidfile = NULL; /* we might have to unbind some proxies from some processes */ -- 1.7.2.3
[PATCH 14/26] Allow pollers to be initialised multiple times
--- src/ev_epoll.c |2 +- src/ev_kqueue.c |1 - src/ev_poll.c |1 - src/ev_select.c |1 - src/ev_sepoll.c |4 +++- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ev_epoll.c b/src/ev_epoll.c index b976868..f55a382 100644 --- a/src/ev_epoll.c +++ b/src/ev_epoll.c @@ -336,13 +336,13 @@ REGPRM1 static void _do_term(struct poller *p) epoll_fd = -1; } + nbchanges = 0; chg_ptr = NULL; chg_list = NULL; fd_evts = NULL; epoll_events = NULL; p-private = NULL; - p-pref = 0; } /* diff --git a/src/ev_kqueue.c b/src/ev_kqueue.c index e167984..61a5422 100644 --- a/src/ev_kqueue.c +++ b/src/ev_kqueue.c @@ -210,7 +210,6 @@ REGPRM1 static void _do_term(struct poller *p) } p-private = NULL; - p-pref = 0; } /* diff --git a/src/ev_poll.c b/src/ev_poll.c index f5d011e..48e6247 100644 --- a/src/ev_poll.c +++ b/src/ev_poll.c @@ -212,7 +212,6 @@ REGPRM1 static void _do_term(struct poller *p) free(fd_evts[DIR_RD]); free(poll_events); p-private = NULL; - p-pref = 0; } /* diff --git a/src/ev_select.c b/src/ev_select.c index 5a87282..6a0562d 100644 --- a/src/ev_select.c +++ b/src/ev_select.c @@ -208,7 +208,6 @@ REGPRM1 static void _do_term(struct poller *p) free(tmp_evts[DIR_WR]); free(tmp_evts[DIR_RD]); p-private = NULL; - p-pref = 0; } /* diff --git a/src/ev_sepoll.c b/src/ev_sepoll.c index 248f1f4..9bddaad 100644 --- a/src/ev_sepoll.c +++ b/src/ev_sepoll.c @@ -587,11 +587,13 @@ REGPRM1 static void _do_term(struct poller *p) epoll_fd = -1; } + nbspec = 0; + absmaxevents = 0; + fd_created = 0; spec_list = NULL; epoll_events = NULL; p-private = NULL; - p-pref = 0; } /* -- 1.7.2.3
[PATCH 08/26] Conditionally ignore errors when resolving users and groups
If is_master is set then the configuration is being read as part a reinitialisation. And as such it seems reasonable to ignore errors when resolving users and groups - if they are unchanged from the original configuration then they will already be set, and otherwise they may not be settable as the process may already be non-privileged. Resolution failure may commonly occur if chroot is in effect and for example /etc/passwd and /etc/group are not present in the chroot. --- src/cfgparse.c | 39 +++ 1 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/cfgparse.c b/src/cfgparse.c index aae2efe..e7ce4c3 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -466,6 +466,17 @@ static int warnif_cond_requires_req(const struct acl_cond *cond, const char *fil return ERR_WARN; } +static int Warning_if_master(const char *fmt, ...) +{ + va_list argp; + void (*f)(const char *, va_list) = is_master ? vWarning : vAlert; + + va_start(argp, fmt); + f(fmt, argp); + va_end(argp); + + return is_master ? 0 : (ERR_ALERT | ERR_FATAL); +} /* * parse a line in a global section. Returns the error code, 0 if OK, or @@ -655,8 +666,7 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm) global.uid = (int)ha_user-pw_uid; } else { - Alert(parsing [%s:%d] : cannot find user id for '%s' (%d:%s)\n, file, linenum, args[1], errno, strerror(errno)); - err_code |= ERR_ALERT | ERR_FATAL; + err_code |= Warning_if_master(parsing [%s:%d] : cannot find user id for '%s' (%d:%s)\n, file, linenum, args[1], errno, strerror(errno)); } } else if (!strcmp(args[0], group)) { @@ -672,8 +682,7 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm) global.gid = (int)ha_group-gr_gid; } else { - Alert(parsing [%s:%d] : cannot find group id for '%s' (%d:%s)\n, file, linenum, args[1], errno, strerror(errno)); - err_code |= ERR_ALERT | ERR_FATAL; + err_code |= Warning_if_master(parsing [%s:%d] : cannot find group id for '%s' (%d:%s)\n, file, linenum, args[1], errno, strerror(errno)); } } /* end of user/group name handling*/ @@ -1871,13 +1880,12 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) } user = getpwnam(args[cur_arg + 1]); if (!user) { - Alert(parsing [%s:%d] : '%s' : '%s' unknown user.\n, + err_code |= Warning_if_master(parsing [%s:%d] : '%s' : '%s' unknown user.\n, file, linenum, args[0], args[cur_arg + 1 ]); - err_code |= ERR_ALERT | ERR_FATAL; - goto out; - } - - curproxy-listen-perm.ux.uid = user-pw_uid; + if (!is_master) + goto out; + } else + curproxy-listen-perm.ux.uid = user-pw_uid; cur_arg += 2; continue; } @@ -1893,13 +1901,12 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) } group = getgrnam(args[cur_arg + 1]); if (!group) { - Alert(parsing [%s:%d] : '%s' : '%s' unknown group.\n, + err_code |= Warning_if_master(parsing [%s:%d] : '%s' : '%s' unknown group.\n, file, linenum, args[0], args[cur_arg + 1 ]); - err_code |= ERR_ALERT | ERR_FATAL; - goto out; - } - - curproxy-listen-perm.ux.gid = group-gr_gid; + if (!is_master) + goto out; + } else + curproxy-listen-perm.ux.gid = group-gr_gid; cur_arg += 2; continue; } -- 1.7.2.3
[PATCH 11/26] Move chroot much earlier
This allows chroot to occur before setsid_early(). In the case where a master process is re-initialised * It will not chroot itself, though if it is already chrooted that will remain in effect. * If it is already chrooted then the path to the configuration file read during reinitialisation will be relative to the chroot. --- src/haproxy.c | 116 1 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index 4ee7161..dc2739b 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -1074,6 +1074,64 @@ void run(int argc, char **argv) */ signal_register_fct(SIGPIPE, NULL, 0); + /* open log pid files before the chroot */ + if (global.mode MODE_DAEMON global.pidfile != NULL) { + int pidfd; + unlink(global.pidfile); + pidfd = open(global.pidfile, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (pidfd 0) { + Alert([%s.run()] Cannot create pidfile %s\n, argv[0], global.pidfile); + if (nb_oldpids) + tell_old_pids(SIGTTIN); + protocol_unbind_all(); + exit(1); + } + pidfile = fdopen(pidfd, w); + } + +#ifdef CONFIG_HAP_CTTPROXY + if (global.last_checks LSTCHK_CTTPROXY) { + int ret; + + ret = check_cttproxy_version(); + if (ret 0) { + Alert([%s.run()] Cannot enable cttproxy.\n%s, + argv[0], + (ret == -1) ? Incorrect module version.\n + : Make sure you have enough permissions and that the module is loaded.\n); + protocol_unbind_all(); + exit(1); + } + } +#endif + + if ((global.last_checks LSTCHK_NETADM) global.uid) { + Alert([%s.run()] Some configuration options require full privileges, so global.uid cannot be changed.\n + , argv[0]); + protocol_unbind_all(); + exit(1); + } + + /* If the user is not root, we'll still let him try the configuration +* but we inform him that unexpected behaviour may occur. +*/ + if ((global.last_checks LSTCHK_NETADM) getuid()) + Warning([%s.run()] Some options which require full privileges +might not work well.\n + , argv[0]); + + /* chroot if needed */ + if (!is_master global.chroot != NULL) { + if (chroot(global.chroot) == -1) { + Alert([%s.run()] Cannot chroot(%s).\n, argv[0], global.chroot); + if (nb_oldpids) + tell_old_pids(SIGTTIN); + protocol_unbind_all(); + exit(1); + } + chdir(/); + } + setid_early(argv[0]); /* ulimits */ @@ -1176,64 +1234,6 @@ void run(int argc, char **argv) fclose(stdin); fclose(stdout); fclose(stderr); } - /* open log pid files before the chroot */ - if (global.mode MODE_DAEMON global.pidfile != NULL) { - int pidfd; - unlink(global.pidfile); - pidfd = open(global.pidfile, O_CREAT | O_WRONLY | O_TRUNC, 0644); - if (pidfd 0) { - Alert([%s.run()] Cannot create pidfile %s\n, argv[0], global.pidfile); - if (nb_oldpids) - tell_old_pids(SIGTTIN); - protocol_unbind_all(); - exit(1); - } - pidfile = fdopen(pidfd, w); - } - -#ifdef CONFIG_HAP_CTTPROXY - if (global.last_checks LSTCHK_CTTPROXY) { - int ret; - - ret = check_cttproxy_version(); - if (ret 0) { - Alert([%s.run()] Cannot enable cttproxy.\n%s, - argv[0], - (ret == -1) ? Incorrect module version.\n - : Make sure you have enough permissions and that the module is loaded.\n); - protocol_unbind_all(); - exit(1); - } - } -#endif - - if ((global.last_checks LSTCHK_NETADM) global.uid) { - Alert([%s.run()] Some configuration options require full privileges, so global.uid cannot be changed.\n - , argv[0]); - protocol_unbind_all(); - exit(1); - } - - /* If the user is not root, we'll still let him try the configuration -* but we inform him that unexpected behaviour may occur. -*/ - if ((global.last_checks LSTCHK_NETADM) getuid()) -
[PATCH 16/26] Restart master process on SIGUSR2
If worker/master mode is active and the master process receives a SIGUSR2 then it will restart haproxy by re-reading the configuration file, killing existing workers and starting new workers. SIGUSR2 is ignored if worker/master mode is not active. SIGUSR2 is ignored by worker processes. --- include/types/global.h |1 + src/haproxy.c | 30 -- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/include/types/global.h b/include/types/global.h index fecbeae..0739ca3 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -122,6 +122,7 @@ extern const int zero; extern const int one; extern const struct linger nolinger; extern int stopping; /* non zero means stopping in progress */ +extern int restarting; /* non zero means restart in progress */ extern int is_master; extern char hostname[MAX_HOSTNAME_LEN]; extern char localpeer[MAX_HOSTNAME_LEN]; diff --git a/src/haproxy.c b/src/haproxy.c index 3c20f92..076a7f4 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -137,6 +137,7 @@ struct global global_default = { /*/ int stopping; /* non zero means stopping in progress */ +int restarting;/* non zero means restart in progress */ int is_master = 0; /* non zero means that master/worker mode * has been activated and the current process * is the master */ @@ -276,6 +277,20 @@ void sig_soft_stop(struct sig_handler *sh) } /* + * upon SIGUSR2, restart master. Ignored by workers + */ +void sig_restart(struct sig_handler *sh) +{ + if (!is_master) + return; + + restarting = 1; + soft_stop(); + signal_unregister_handler(sh); + pool_gc2(); +} + +/* * upon SIGTTOU, we pause everything */ void sig_pause(struct sig_handler *sh) @@ -391,7 +406,7 @@ void init(int argc, char **argv) * Initialize the previously static variables. */ - totalconn = actconn = maxfd = listeners = stopping = 0; + totalconn = actconn = maxfd = listeners = stopping = restarting = 0; #ifdef HAPROXY_MEMMAX @@ -1406,7 +1421,18 @@ void run(int argc, char **argv) int main(int argc, char **argv) { - run(argc, argv); + while (1) { + run(argc, argv); + if (!restarting) + break; + } + + if (is_master) + /* The master is gracefully shutting down, +* ask the clients to gracefully shutdown too. +*/ + tell_old_pids(SIGUSR1); + exit(0); } -- 1.7.2.3
[PATCH 22/26] Write pid file if master/worker mode is active
If master/worker mode is active then there will be multiple processes present and as such it is useful to know the relevant pids. This also splits run() up into two parts which will aid later refactoring. --- src/haproxy.c | 43 +++ 1 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index 998c4ff..caa2a29 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -404,6 +404,8 @@ void init(int argc, char **argv) error_snapshot_id = 0; jobs = 0; + socket_cache_make_all_available(); + /* NB: POSIX does not make it mandatory for gethostname() to NULL-terminate * the string in case of truncation, and at least FreeBSD appears not to do * it. @@ -1100,12 +1102,12 @@ static void setid_late(const char *name) #endif } -void run(int argc, char **argv) +static FILE *prepare(int argc, char **argv) { int err, retry; int mode = global.mode (MODE_DAEMON|MODE_MASTER_WORKER); struct rlimit limit; - static FILE *pidfile = NULL; + FILE *pidfile = NULL; char errmsg[100]; socket_cache_make_all_available(); @@ -1120,7 +1122,7 @@ void run(int argc, char **argv) /* Signalling a reset can be handled here */ if (oldpids_sig == SIGUSR2) { tell_old_pids(SIGUSR2); - return; + exit(0); } signal_register_fct(SIGQUIT, dump, SIGQUIT); @@ -1138,7 +1140,8 @@ void run(int argc, char **argv) signal_register_fct(SIGPIPE, NULL, 0); /* open log pid files before the chroot */ - if (!pidfile global.mode MODE_DAEMON global.pidfile != NULL) { + if (!is_master global.mode (MODE_DAEMON|MODE_MASTER_WORKER) + global.pidfile != NULL) { int pidfd; unlink(global.pidfile); pidfd = open(global.pidfile, O_CREAT | O_WRONLY | O_TRUNC, 0644); @@ -1319,6 +1322,12 @@ void run(int argc, char **argv) socket_cache_gc(); + return pidfile; +} + +static void run(int argc, char **argv, FILE *pidfile) +{ + if (global.mode (MODE_DAEMON|MODE_MASTER_WORKER)) { struct proxy *px; int ret = 0; @@ -1348,13 +1357,11 @@ void run(int argc, char **argv) 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); - } + 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 @@ -1388,10 +1395,8 @@ void run(int argc, char **argv) Worker #%d started\n, proc); break; } - if (pidfile != NULL) { - fprintf(pidfile, %d\n, ret); - fflush(pidfile); - } + fprintf(pidfile, %d\n, ret); + fflush(pidfile); oldpids[proc] = ret; relative_pid++; /* each child will get a different one */ } @@ -1452,8 +1457,14 @@ void run(int argc, char **argv) int main(int argc, char **argv) { + FILE *pidfile = NULL; + while (1) { - run(argc, argv); + FILE *newpidfile = prepare(argc, argv); + if (!is_master) + pidfile = newpidfile; + + run(argc, argv, pidfile); if (!restarting) break; } -- 1.7.2.3
[PATCH 21/26] Exit workers if the master disappears
This should allow the master to be restarted, for instance using ruinit, if it exits for some reason. --- src/haproxy.c |8 1 files changed, 8 insertions(+), 0 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index 175d3e8..998c4ff 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -983,6 +983,8 @@ static int tell_old_pids(int sig) void run_poll_loop() { int next; + pid_t ppid = ((global.mode MODE_MASTER_WORKER) !is_master) ? + getppid() : -1; tv_update_date(0,1); while (1) { @@ -1004,6 +1006,12 @@ void run_poll_loop() if (jobs == 0) break; + if (ppid 0 kill(ppid, 0)) { + send_log(NULL, LOG_INFO, +Parent disappeared, exiting.\n); + break; + } + if (is_master) { sleep(1); continue; -- 1.7.2.3
[PATCH 17/26] Exit master proxies as soon as possible
--- src/proxy.c |7 --- 1 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/proxy.c b/src/proxy.c index c4f452c..ff5f635 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -575,9 +575,10 @@ void soft_stop(void) tv_update_date(0,1); /* else, the old time before select will be used */ while (p) { if (p-state != PR_STSTOPPED) { - Warning(Stopping %s %s in %d ms.\n, proxy_cap_str(p-cap), p-id, p-grace); - send_log(p, LOG_WARNING, Stopping %s %s in %d ms.\n, proxy_cap_str(p-cap), p-id, p-grace); - p-stop_time = tick_add(now_ms, p-grace); + int grace = is_master ? 0 : p-grace; + Warning(Stopping %s %s in %d ms.\n, proxy_cap_str(p-cap), p-id, grace); + send_log(p, LOG_WARNING, Stopping %s %s in %d ms.\n, proxy_cap_str(p-cap), p-id, grace); + p-stop_time = tick_add(now_ms, grace); } if (p-table.size p-table.sync_task) task_wakeup(p-table.sync_task, TASK_WOKEN_MSG); -- 1.7.2.3
[PATCH 20/26] Document master/worker mode
Note: I have not updated the French documentation --- doc/configuration.txt | 48 ++-- doc/haproxy-en.txt| 47 ++- doc/haproxy.1 | 20 ++-- 3 files changed, 94 insertions(+), 21 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 7a5ace2..f9c9bd8 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -432,6 +432,7 @@ The following keywords are supported in the global section : * Process management and security - chroot - daemon + - master_worker - gid - group - log @@ -486,10 +487,25 @@ daemon operation. It is equivalent to the command line -D argument. It can be disabled by the command line -db argument. +master_worker + Split haproxy into two types of processes. A master process which + binds to sockets and worker sockets which run proxies. When running + in this mode haproxy will restart rather than exiting when + haproxy -st is used. On restart configuration files will be reread. + + On linux the master process will run with CAP_NET_BIND_SERVCE and + CAP_SYS_RESOURCE so that even if is running as non-root it will be able + to bind to privilaged ports and update ulimits. + gid number Changes the process' group ID to number. It is recommended that the group ID is dedicated to HAProxy or to a small set of similar daemons. HAProxy must be started with a user belonging to this group, or with superuser privileges. + + If running in master_worker mode changes to the gid are ignored if + haproxy is already runing as non-root, either because it was started as + non-root or it switched to another user when originally started. + See also group and uid. group group name @@ -542,17 +558,21 @@ log-tag string running on the same host. nbproc number - Creates number processes when going daemon. This requires the daemon - mode. By default, only one process is created, which is the recommended mode - of operation. For systems limited to small sets of file descriptors per - process, it may be needed to fork multiple daemons. USING MULTIPLE PROCESSES - IS HARDER TO DEBUG AND IS REALLY DISCOURAGED. See also daemon. + Creates number processes when going daemon. This requires daemon or + master_worker mode. By default, only one process is created, which is + the recommended mode of operation. For systems limited to small sets of + file descriptors per process, it may be needed to fork multiple daemons. + USING MULTIPLE PROCESSES IS HARDER TO DEBUG AND IS REALLY DISCOURAGED. + See also daemon. pidfile pidfile Writes pids of all daemons into file pidfile. This option is equivalent to the -p command line argument. The file must be accessible to the user starting the process. See also daemon. + If both daemon and master_worker mode is set then the pid + of the master process will be the first entry in pid file. + stats socket path [{uid | user} uid] [{gid | group} gid] [mode mode] [level level] @@ -595,7 +615,13 @@ uid number Changes the process' user ID to number. It is recommended that the user ID is dedicated to HAProxy or to a small set of similar daemons. HAProxy must be started with superuser privileges in order to be able to switch to another - one. See also gid and user. + one. + + If running in master_worker mode changes to the uid are ignored if + haproxy is already runing as non-root, either because it was started as + non-root or it switched to another user when originally started. + + See also gid and user. ulimit-n number Sets the maximum number of per-process file-descriptors to number. By @@ -616,10 +642,17 @@ unix-bind [ prefix prefix ] [ mode mode ] [ user user ] [ uid uid ] both are specified, the bind statement has priority, meaning that the unix-bind settings may be seen as process-wide default settings. + If running in master_worker mode and in a chroot any failure to resolve + the user or group on restart will be ignored and the previous settings + will prevail. + user user name Similar to uid but uses the UID of user name user name from /etc/passwd. See also uid and group. + If running in master_worker mode and in a chroot any failure to resolve + the user will be ignored and the previous settings will prevail. + node name Only letters, digits, hyphen and underscore are allowed, like in DNS names. @@ -791,6 +824,9 @@ group groupname [users user,user,(...)] attach users to this group by using a comma separated list of names proceeded by users keyword. + If running in master_worker mode and in a chroot any failure to resolve + the group will be ignored and the previous settings will prevail. + user username [password|insecure-password password] [groups group,group,(...)] Adds user username to the current userlist. Both secure (encrypted) and diff --git a/doc/haproxy-en.txt
[PATCH 15/26] Add master/worker model
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 @@ #defineMODE_VERBOSE0x10 #defineMODE_STARTING 0x20 #defineMODE_FOREGROUND 0x40 +#defineMODE_MASTER_WORKER 0x80 /* list of last checks to perform, depending on config options */ #define LSTCHK_CAP_BIND0x0001 /* 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) +
[PATCH 19/26] Add restart (-sr)
This works similarly to -sf and -st except that rather than terminating processes and then starting a new haproxy instance is requests a restart and then exits. --- src/haproxy.c | 17 ++--- 1 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index 96c39ab..175d3e8 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -507,12 +507,16 @@ void init(int argc, char **argv) arg_mode |= MODE_MASTER_WORKER; else if (*flag == 'q') arg_mode |= MODE_QUIET; - else if (*flag == 's' (flag[1] == 'f' || flag[1] == 't')) { - /* list of pids to finish ('f') or terminate ('t') */ + else if (*flag == 's' (flag[1] == 'f' || flag[1] == 'r' || flag[1] == 't')) { + /* list of pids to finish ('f'), +* reset ('r') or terminate ('t') +*/ if (flag[1] == 'f') oldpids_sig = SIGUSR1; /* finish then exit */ - else + else if (flag[1] == 'r') + oldpids_sig = SIGUSR2; /* restart */ + else if (flag[1] == 't') oldpids_sig = SIGTERM; /* terminate immediately */ argv++; argc--; @@ -1104,6 +1108,13 @@ void run(int argc, char **argv) global.mode = (global.mode ~(MODE_DAEMON|MODE_MASTER_WORKER)) | mode; close_log(); /* It will automatically be reopened as needed */ + + /* Signalling a reset can be handled here */ + if (oldpids_sig == SIGUSR2) { + tell_old_pids(SIGUSR2); + return; + } + signal_register_fct(SIGQUIT, dump, SIGQUIT); signal_register_fct(SIGUSR1, sig_soft_stop, SIGUSR1); if (global.mode MODE_MASTER_WORKER) -- 1.7.2.3
[PATCH 24/26] Write pidfile after spawning all workers
This is to allow the pidfile to be more easily rewritten if the pid a worker changes --- src/haproxy.c | 19 ++- 1 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index 35ee06e..c33c072 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -1358,13 +1358,6 @@ static void create_processes(int argc, char **argv, FILE *pidfile) send_log(NULL, LOG_INFO, Master started\n); } - 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; @@ -1396,8 +1389,6 @@ static void create_processes(int argc, char **argv, FILE *pidfile) Worker #%d started\n, proc); break; } - fprintf(pidfile, %d\n, ret); - fflush(pidfile); oldpids[proc] = ret; relative_pid++; /* each child will get a different one */ } @@ -1432,6 +1423,16 @@ static void create_processes(int argc, char **argv, FILE *pidfile) global.mode |= MODE_QUIET; /* ensure that we won't say anything from now */ } + if (pidfile (is_master || !(global.mode MODE_MASTER_WORKER))) { + rewind(pidfile); + ftruncate(fileno(pidfile), 0); + if (is_master) + fprintf(pidfile, %d\n, pid); + for (proc = 0; proc nb_oldpids; proc++) + fprintf(pidfile, %d\n, oldpids[proc]); + fflush(pidfile); + } + fork_poller(); } -- 1.7.2.3
[PATCH 23/26] Break out process creation
Break out the portion of run() that forks processes into create_processes() and move the remainder of run() back into main(). This increases readability by 1) reducing the indentation of almost all of the code in create_processes() 2) Removing the arbitrary split between run() and main(). It was originally envisaged that run() would be the inner-loop of main(), but the split never worked that way. --- src/haproxy.c | 214 - 1 files changed, 105 insertions(+), 109 deletions(-) diff --git a/src/haproxy.c b/src/haproxy.c index caa2a29..35ee06e 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -1325,134 +1325,114 @@ static FILE *prepare(int argc, char **argv) return pidfile; } -static void run(int argc, char **argv, FILE *pidfile) +static void create_processes(int argc, char **argv, FILE *pidfile) { + struct proxy *px; + int ret = 0; + int proc; - 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); - } - - 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 */ - } + if (!(global.mode (MODE_DAEMON|MODE_MASTER_WORKER))) + return; - /* the father launches the required number of processes */ - for (proc = 0; proc global.nbproc; 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) { - send_log(NULL, LOG_ERR, Cannot fork.\n); + Alert([%s.run()] Cannot fork.\n, + argv[0]); protocol_unbind_all(); exit(1); /* there has been an error */ } - 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; - } - fprintf(pidfile, %d\n, ret); - fflush(pidfile); - oldpids[proc] = ret; - relative_pid++; /* each child will get a different one */ + if (ret 0) + exit(0); /* Exit original process */ + + /* Child */ + setsid(); + close_log(); + pid = getpid(); } - /* close
Re: [PATCH 00/26] Restart of haproxy without dropping connections
On Thu, Mar 10, 2011 at 01:32:42PM +0900, Simon Horman wrote: Hi, this patch series attempts to allow haproxy to be restarted - perhaps reconfigured would be a better term - without refusing or dropping connections. The model that I am using is that of a master process, which really does nothing but hold bound file descriptors, and workers - these should be very similar to what haproxy processes have looked like up to now. This feature can be enabled using the -M command line option of the master_worker configuration file option. It may be used in conjunction with daemon. In order to effect a restart a SIGUSR2 should be sent to the master process. The master process is guaranteed to be the first pid listed in the pid file. It will also be the parent process, and can thus be found using ps. It should also be noted in the logs. SIGUSR2 is ignored if master/worker mode is not enabled. SIGUSR2 is ignored by workers. The patches are available in git git://github.com/horms/haproxy.git master
How to delete a node in EB Tree
Hello: I am try to delete the proxy because some task. and reload the new proxy. and then use check_config_validity. but I get the problem that I use the fallowing code in delete_proxy() (custom function) /* p = proxy */ 786 if (!p-uuid){ 787 eb32_delete(p-conf.id); 788 } I clear all the proxy, but when i use next_pxid = get_next_id(used_proxy_id, next_pxid); I get segment fault. I thought the whole tree should be empty, but not. why? Am I using the right eb tree api? Thanks!
Re: [PATCH 00/26] Restart of haproxy without dropping connections
Hi Simon, On Thu, Mar 10, 2011 at 01:32:42PM +0900, Simon Horman wrote: Hi, this patch series attempts to allow haproxy to be restarted - perhaps reconfigured would be a better term - without refusing or dropping connections. Wow, I'm impressed, you managed to do that really quickly ! You sent that at the right moment, because I have almost settled down on the internal changes needed to work on server-side keep-alive, so I should issue -dev4 once I'm fine with those changes. Then we'll be able to start by integrating your changes. In the mean time, I have a few questions which come to mind : - does the socket cache consider all of the bind parameters ? (eg: mss, interface, transparent, ...) - what happens if the new config file uses some conflicting bind entries ? Eg: old config used to listen on 192.168.1.1:80 and the new one uses 0.0.0.0:80 ? Or even :80 for the old one (IPv4) and :::80 for the new one (IPv6) ? - does the master send a signal to all children asking them to unbind (as we did with -sf) ? - do the debug modes (-d/-db) disable the master_worker mode ? In fact I'm interested in any corner cases we should be aware of so that we can clearly document them and indicate how to handle them (eg: fall back to -st if it's not possible to rebind, etc...) Thanks ! Willy
Re: How to deal with proxying shoutcast / ICY protocol
Hi David, On Thu, Mar 10, 2011 at 03:36:11PM +1300, David Young wrote: I thought that if I configured haproxy in mode tcp instead of mode http, that it'd behave more like a simple loadbalancer, and just pass the request onto my squid backend verbatim, but that doesn't seem to have worked either. An example URL I'm having trouble with is http://66.225.205.47/;stream.mp3 Even in mode tcp, if I request that URL through by browser from haproxy, I get a 502 error, whereas if I requested it directly from the backend squid instance which services my request anyway, I get the expected headers and stream. If you get a 502 while you're in mode tcp, then you're not in mode tcp, but still in http. Haproxy cannot emit HTTP error codes in TCP mode. Looking at your config, I understand what happens : your defaults section is http, which means that all subsequent sections will have mode http by default. Your frontend is explicitly TCP but not your backend, which is still HTTP. So once the connection is forwarded to the backend, it's still parsed and processed in HTTP mode. One thing to keep in mind : if you set a mode in a defaults section, do not use a different one in subsequent sections, or sooner or later you'll get trapped. Better redefined a new defaults section to make things clearer. I like to use two sections defaults http and defaults tcp for this. Regards, Willy
Re: How to delete a node in EB Tree
Hello, On Thu, Mar 10, 2011 at 01:59:30PM +0800, xiaoxin wrote: Hello: I am try to delete the proxy because some task. and reload the new proxy. and then use check_config_validity. but I get the problem that I use the fallowing code in delete_proxy() (custom function) /* p = proxy */ 786 if (!p-uuid){ 787 eb32_delete(p-conf.id); 788 } I clear all the proxy, but when i use next_pxid = get_next_id(used_proxy_id, next_pxid); I get segment fault. I thought the whole tree should be empty, but not. why? I don't know what your whole function does, but I suspect the following is happening : - your proxy has a valid p-uuid - line 787 does not delete it from the tree - after that you delete and free all the proxy contents - later on, get_next_id() walks the ID tree and at one point it encounters the node that you freed without deleting it and since the memory is not allocated anymore, the process crashes. Am I using the right eb tree api? Yes, in order to delete a node from a tree, ebXX_delete(node) is the way to do it. You did it right, reason why I think the scenario above happened. Try to comment out lines 786 and 788 in order to unconditionally remove the id from the tree. I'm sure it will not crash anymore. Regards, Willy
Re: [PATCH 00/26] Restart of haproxy without dropping connections
Hi Willy, On Thu, Mar 10, 2011 at 07:29:11AM +0100, Willy Tarreau wrote: Hi Simon, On Thu, Mar 10, 2011 at 01:32:42PM +0900, Simon Horman wrote: Hi, this patch series attempts to allow haproxy to be restarted - perhaps reconfigured would be a better term - without refusing or dropping connections. Wow, I'm impressed, you managed to do that really quickly ! Thanks. Its taken quite a bit of effort to get this far. You sent that at the right moment, because I have almost settled down on the internal changes needed to work on server-side keep-alive, so I should issue -dev4 once I'm fine with those changes. Then we'll be able to start by integrating your changes. Great. In the mean time, I have a few questions which come to mind : - does the socket cache consider all of the bind parameters ? (eg: mss, interface, transparent, ...) Yes, it attempts to take them into account, though in a very naive way. If they all match then the socket cache entry can be re-used. Otherwise the entry is invalidated. Thanks for earlier advice on this else I would have probably written code that didn't take these parameters into account at all. - what happens if the new config file uses some conflicting bind entries ? Eg: old config used to listen on 192.168.1.1:80 and the new one uses 0.0.0.0:80 ? Or even :80 for the old one (IPv4) and :::80 for the new one (IPv6) ? I think there will be a problem. Lets talk about what would be a sensible thing to do. - does the master send a signal to all children asking them to unbind (as we did with -sf) ? Yes. It sends them a SIGUSR1. It does not send them a SIGTTOU. Because although that works fine I am not entirely sure how to sanely unwind the master at that point. - do the debug modes (-d/-db) disable the master_worker mode ? No. I can make that so if you like. In fact I'm interested in any corner cases we should be aware of so that we can clearly document them and indicate how to handle them (eg: fall back to -st if it's not possible to rebind, etc...) I don't know of any off hand. But one of my reasons for posting the code was to get more eyes on it so we can find such cases. Re-initialising the configuration was somewhat non-trivial, and I am sure that I have missed a few things. Thanks ! Willy
Re: [PATCH 00/26] Restart of haproxy without dropping connections
On Thu, Mar 10, 2011 at 03:52:58PM +0900, Simon Horman wrote: Wow, I'm impressed, you managed to do that really quickly ! Thanks. Its taken quite a bit of effort to get this far. I have no doubt about that :-) In the mean time, I have a few questions which come to mind : - does the socket cache consider all of the bind parameters ? (eg: mss, interface, transparent, ...) Yes, it attempts to take them into account, though in a very naive way. If they all match then the socket cache entry can be re-used. Otherwise the entry is invalidated. OK. - what happens if the new config file uses some conflicting bind entries ? Eg: old config used to listen on 192.168.1.1:80 and the new one uses 0.0.0.0:80 ? Or even :80 for the old one (IPv4) and :::80 for the new one (IPv6) ? I think there will be a problem. Lets talk about what would be a sensible thing to do. In my opinion we should define how to fail a restart. Right now with -sf/-st, the new process is able to detect the failure to bind and exits with an error which can be detected by management scripts and reported to the admin which must immediately take action. I think that a failure to restart smoothly means that the admin will have to take the decision to restart harder (-sf/-st). Sometimes the scripts will do that by themselves as an automatic fallback. So that make me believe that all we want is the master process to refuse to change anything in case an anomaly is detected, and to indicate its refusal. We cannot make it send a signal to the calling process because it does not know its pid, and even if it knew it it would probably not have enough permissions to send it anyway. But we should find something the master process can act on that the calling one can detect (eg: among the horrible ideas, if it's not chrooted, it can change its pid and update the pidfile, but that's ugly). - does the master send a signal to all children asking them to unbind (as we did with -sf) ? Yes. It sends them a SIGUSR1. It does not send them a SIGTTOU. Because although that works fine I am not entirely sure how to sanely unwind the master at that point. I think it's OK that way because the master still owns the sockets, so even in case of a late failure, it can restart new processes anyway. - do the debug modes (-d/-db) disable the master_worker mode ? No. I can make that so if you like. Yes that's the idea. Many users abuse -d/-db (including me) and it's important that they don't have to touch their config for this. In fact I'm interested in any corner cases we should be aware of so that we can clearly document them and indicate how to handle them (eg: fall back to -st if it's not possible to rebind, etc...) I don't know of any off hand. But one of my reasons for posting the code was to get more eyes on it so we can find such cases. Fine. Any failure to restart with the expected config typically is what I'd call a corner case. That's why I'd like that we find a way to safely fail so that proper action can be taken externally. Re-initialising the configuration was somewhat non-trivial, and I am sure that I have missed a few things. I'm certain that some parts were a real nightmare ! I'll take time to review all your patches because yes, it's possible that you fell in some traps or had to take some decisions that might have other impacts. That's also why I'd like to get your code quickly merged, it's the best way to find what might have been overlooked. Best regards, Willy