haproxy/src/proxy.c
Willy Tarreau 4d2d098ea3 [MAJOR] call garbage collector when doing soft stop
When we're interrupted by another instance, it is very likely
that the other one will need some memory. Now we know how to
free what is not used, so let's do it.

Also only free non-null pointers. Previously, pool_destroy()
did implicitly check for this case which was incidentely
needed.
2007-05-14 00:39:29 +02:00

385 lines
9.9 KiB
C

/*
* Proxy variables and functions.
*
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <common/defaults.h>
#include <common/compat.h>
#include <common/config.h>
#include <common/memory.h>
#include <common/time.h>
#include <types/global.h>
#include <types/polling.h>
#include <proto/client.h>
#include <proto/fd.h>
#include <proto/log.h>
#include <proto/proxy.h>
int listeners; /* # of listeners */
struct proxy *proxy = NULL; /* list of all existing proxies */
/*
* This function returns a string containing the type of the proxy in a format
* suitable for error messages, from its capabilities.
*/
const char *proxy_type_str(struct proxy *proxy)
{
int cap = proxy->cap;
if ((cap & PR_CAP_LISTEN) == PR_CAP_LISTEN)
return "listener";
else if (cap & PR_CAP_FE)
return "frontend";
else if (cap & PR_CAP_BE)
return "backend";
else if (cap & PR_CAP_RS)
return "ruleset";
else
return "proxy";
}
/*
* This function creates all proxy sockets. It should be done very early,
* typically before privileges are dropped. The sockets will be registered
* but not added to any fd_set, in order not to loose them across the fork().
* The proxies also start in IDLE state, meaning that it will be
* maintain_proxies that will finally complete their loading.
*
* Its return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL.
* Retryable errors will only be printed if <verbose> is not zero.
*/
int start_proxies(int verbose)
{
struct proxy *curproxy;
struct listener *listener;
int err = ERR_NONE;
int fd, pxerr;
for (curproxy = proxy; curproxy != NULL; curproxy = curproxy->next) {
if (curproxy->state != PR_STNEW)
continue; /* already initialized */
pxerr = 0;
for (listener = curproxy->listen; listener != NULL; listener = listener->next) {
if (listener->fd != -1)
continue; /* already initialized */
if ((fd = socket(listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
if (verbose)
Alert("cannot create listening socket for proxy %s. Aborting.\n",
curproxy->id);
err |= ERR_RETRYABLE;
pxerr |= 1;
continue;
}
if (fd >= global.maxsock) {
Alert("socket(): not enough free sockets for proxy %s. Raise -n argument. Aborting.\n",
curproxy->id);
close(fd);
err |= ERR_FATAL;
pxerr |= 1;
break;
}
if ((fcntl(fd, F_SETFL, O_NONBLOCK) == -1) ||
(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
(char *) &one, sizeof(one)) == -1)) {
Alert("cannot make socket non-blocking for proxy %s. Aborting.\n",
curproxy->id);
close(fd);
err |= ERR_FATAL;
pxerr |= 1;
break;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) == -1) {
Alert("cannot do so_reuseaddr for proxy %s. Continuing.\n",
curproxy->id);
}
#ifdef SO_REUSEPORT
/* OpenBSD supports this. As it's present in old libc versions of Linux,
* it might return an error that we will silently ignore.
*/
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &one, sizeof(one));
#endif
if (bind(fd,
(struct sockaddr *)&listener->addr,
listener->addr.ss_family == AF_INET6 ?
sizeof(struct sockaddr_in6) :
sizeof(struct sockaddr_in)) == -1) {
if (verbose)
Alert("cannot bind socket for proxy %s. Aborting.\n",
curproxy->id);
close(fd);
err |= ERR_RETRYABLE;
pxerr |= 1;
continue;
}
if (listen(fd, curproxy->maxconn) == -1) {
if (verbose)
Alert("cannot listen to socket for proxy %s. Aborting.\n",
curproxy->id);
close(fd);
err |= ERR_RETRYABLE;
pxerr |= 1;
continue;
}
/* the socket is ready */
listener->fd = fd;
/* the function for the accept() event */
fd_insert(fd);
fdtab[fd].cb[DIR_RD].f = &event_accept;
fdtab[fd].cb[DIR_WR].f = NULL; /* never called */
fdtab[fd].cb[DIR_RD].b = fdtab[fd].cb[DIR_WR].b = NULL;
fdtab[fd].owner = (struct task *)curproxy; /* reference the proxy instead of a task */
fdtab[fd].state = FD_STLISTEN;
fdtab[fd].ev = 0;
listeners++;
}
if (!pxerr) {
curproxy->state = PR_STIDLE;
send_log(curproxy, LOG_NOTICE, "Proxy %s started.\n", curproxy->id);
}
}
return err;
}
/*
* this function enables proxies when there are enough free sessions,
* or stops them when the table is full. It is designed to be called from the
* select_loop(). It returns the date of next expiration event during stop
* time, ETERNITY otherwise.
*/
void maintain_proxies(struct timeval *next)
{
struct proxy *p;
struct listener *l;
p = proxy;
tv_eternity(next);
/* if there are enough free sessions, we'll activate proxies */
if (actconn < global.maxconn) {
while (p) {
if (p->feconn < p->maxconn) {
if (p->state == PR_STIDLE) {
for (l = p->listen; l != NULL; l = l->next) {
EV_FD_SET(l->fd, DIR_RD);
}
p->state = PR_STRUN;
}
}
else {
if (p->state == PR_STRUN) {
for (l = p->listen; l != NULL; l = l->next) {
EV_FD_CLR(l->fd, DIR_RD);
}
p->state = PR_STIDLE;
}
}
p = p->next;
}
}
else { /* block all proxies */
while (p) {
if (p->state == PR_STRUN) {
for (l = p->listen; l != NULL; l = l->next) {
EV_FD_CLR(l->fd, DIR_RD);
}
p->state = PR_STIDLE;
}
p = p->next;
}
}
if (stopping) {
p = proxy;
while (p) {
if (p->state != PR_STSTOPPED) {
int t;
t = tv_ms_remain2(&now, &p->stop_time);
if (t == 0) {
Warning("Proxy %s stopped.\n", p->id);
send_log(p, LOG_WARNING, "Proxy %s stopped.\n", p->id);
for (l = p->listen; l != NULL; l = l->next) {
fd_delete(l->fd);
listeners--;
}
p->state = PR_STSTOPPED;
/* try to free more memory */
pool_gc2();
}
else {
tv_bound(next, &p->stop_time);
}
}
p = p->next;
}
}
return;
}
/*
* this function disables health-check servers so that the process will quickly be ignored
* by load balancers. Note that if a proxy was already in the PAUSED state, then its grace
* time will not be used since it would already not listen anymore to the socket.
*/
void soft_stop(void)
{
struct proxy *p;
stopping = 1;
p = proxy;
tv_now(&now); /* else, the old time before select will be used */
while (p) {
if (p->state != PR_STSTOPPED) {
Warning("Stopping proxy %s in %d ms.\n", p->id, p->grace);
send_log(p, LOG_WARNING, "Stopping proxy %s in %d ms.\n", p->id, p->grace);
tv_ms_add(&p->stop_time, &now, p->grace);
}
p = p->next;
}
}
/*
* Linux unbinds the listen socket after a SHUT_RD, and ignores SHUT_WR.
* Solaris refuses either shutdown().
* OpenBSD ignores SHUT_RD but closes upon SHUT_WR and refuses to rebind.
* So a common validation path involves SHUT_WR && listen && SHUT_RD.
* If disabling at least one listener returns an error, then the proxy
* state is set to PR_STERROR because we don't know how to resume from this.
*/
void pause_proxy(struct proxy *p)
{
struct listener *l;
for (l = p->listen; l != NULL; l = l->next) {
if (shutdown(l->fd, SHUT_WR) == 0 &&
listen(l->fd, p->maxconn) == 0 &&
shutdown(l->fd, SHUT_RD) == 0) {
EV_FD_CLR(l->fd, DIR_RD);
if (p->state != PR_STERROR)
p->state = PR_STPAUSED;
}
else
p->state = PR_STERROR;
}
}
/*
* This function temporarily disables listening so that another new instance
* can start listening. It is designed to be called upon reception of a
* SIGTTOU, after which either a SIGUSR1 can be sent to completely stop
* the proxy, or a SIGTTIN can be sent to listen again.
*/
void pause_proxies(void)
{
int err;
struct proxy *p;
err = 0;
p = proxy;
tv_now(&now); /* else, the old time before select will be used */
while (p) {
if (p->state != PR_STERROR &&
p->state != PR_STSTOPPED &&
p->state != PR_STPAUSED) {
Warning("Pausing proxy %s.\n", p->id);
send_log(p, LOG_WARNING, "Pausing proxy %s.\n", p->id);
pause_proxy(p);
if (p->state != PR_STPAUSED) {
err |= 1;
Warning("Proxy %s failed to enter pause mode.\n", p->id);
send_log(p, LOG_WARNING, "Proxy %s failed to enter pause mode.\n", p->id);
}
}
p = p->next;
}
if (err) {
Warning("Some proxies refused to pause, performing soft stop now.\n");
send_log(p, LOG_WARNING, "Some proxies refused to pause, performing soft stop now.\n");
soft_stop();
}
}
/*
* This function reactivates listening. This can be used after a call to
* sig_pause(), for example when a new instance has failed starting up.
* It is designed to be called upon reception of a SIGTTIN.
*/
void listen_proxies(void)
{
struct proxy *p;
struct listener *l;
p = proxy;
tv_now(&now); /* else, the old time before select will be used */
while (p) {
if (p->state == PR_STPAUSED) {
Warning("Enabling proxy %s.\n", p->id);
send_log(p, LOG_WARNING, "Enabling proxy %s.\n", p->id);
for (l = p->listen; l != NULL; l = l->next) {
if (listen(l->fd, p->maxconn) == 0) {
if (actconn < global.maxconn && p->feconn < p->maxconn) {
EV_FD_SET(l->fd, DIR_RD);
p->state = PR_STRUN;
}
else
p->state = PR_STIDLE;
} else {
int port;
if (l->addr.ss_family == AF_INET6)
port = ntohs(((struct sockaddr_in6 *)(&l->addr))->sin6_port);
else
port = ntohs(((struct sockaddr_in *)(&l->addr))->sin_port);
Warning("Port %d busy while trying to enable proxy %s.\n",
port, p->id);
send_log(p, LOG_WARNING, "Port %d busy while trying to enable proxy %s.\n",
port, p->id);
/* Another port might have been enabled. Let's stop everything. */
pause_proxy(p);
break;
}
}
}
p = p->next;
}
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/