Files
uwsgi/proxy.c
T
2010-04-30 12:28:01 +02:00

407 lines
10 KiB
C

#ifdef UWSGI_PROXY
/*
uWSGI proxy
it needs one of this tecnology to work:
- epoll (linux 2.6)
- kqueue (various BSD and Darwin)
- /dev/poll (Solaris)
*/
#include "uwsgi.h"
#define UWSGI_PROXY_CONNECTING 1
#define UWSGI_PROXY_WAITING 2
extern struct uwsgi_server uwsgi;
struct uwsgi_proxy_connection {
int dest_fd;
int status;
int retry;
int node;
};
static void end_proxy(void) {
exit(UWSGI_END_CODE);
}
static void reload_proxy(void) {
exit(UWSGI_RELOAD_CODE);
}
static void uwsgi_proxy_close(struct uwsgi_proxy_connection *upcs, int fd) {
if (upcs[fd].dest_fd >= 0) {
close(upcs[fd].dest_fd);
upcs[upcs[fd].dest_fd].dest_fd = -1;
upcs[upcs[fd].dest_fd].status = 0;
upcs[upcs[fd].dest_fd].retry = 0;
if (upcs[upcs[fd].dest_fd].node > -1) {
if (uwsgi.shared->nodes[upcs[upcs[fd].dest_fd].node].connections > 0)
uwsgi.shared->nodes[upcs[upcs[fd].dest_fd].node].connections--;
}
}
if (fd >= 0) {
close(fd);
upcs[fd].dest_fd = -1;
upcs[fd].status = 0;
upcs[fd].retry = 0;
if (upcs[fd].node > -1) {
if (uwsgi.shared->nodes[upcs[fd].node].connections > 0)
uwsgi.shared->nodes[upcs[fd].node].connections--;
}
}
}
static int uwsgi_proxy_find_next_node(int current_node) {
int i;
current_node++;
if (current_node >= MAX_CLUSTER_NODES) {
current_node = 0;
}
// is it a good node ?
if (uwsgi.shared->nodes[current_node].name[0] != 0 && uwsgi.shared->nodes[current_node].status == UWSGI_NODE_OK) {
if (uwsgi.shared->nodes[current_node].connections < uwsgi.shared->nodes[current_node].workers)
return current_node;
}
// try to find a better one
for (i = 0; i < MAX_CLUSTER_NODES; i++) {
if (uwsgi.shared->nodes[i].name[0] != 0 && uwsgi.shared->nodes[i].status == UWSGI_NODE_OK) {
if (uwsgi.shared->nodes[i].connections < uwsgi.shared->nodes[i].workers)
return i;
}
}
// ok, it is a very loaded system, fallback to round robin
if (uwsgi.shared->nodes[current_node].name[0] != 0 && uwsgi.shared->nodes[current_node].status == UWSGI_NODE_OK) {
return current_node;
}
for (i = 0; i < MAX_CLUSTER_NODES; i++) {
if (uwsgi.shared->nodes[i].name[0] != 0 && uwsgi.shared->nodes[i].status == UWSGI_NODE_OK) {
return i;
}
}
return -1;
}
void uwsgi_proxy(int proxyfd) {
int efd ;
#ifdef __linux__
struct epoll_event *eevents;
struct epoll_event ev;
#elif defined(__sun__)
struct pollfd *eevents;
struct pollfd ev;
#else
struct kevent *eevents;
struct kevent ev;
#endif
int max_events = 64;
int nevents, i;
const int nonblocking = 1;
const int blocking = 0;
char buffer[4096];
ssize_t rlen;
ssize_t wlen;
int max_connections = sysconf(_SC_OPEN_MAX);
int soopt;
socklen_t solen = sizeof(int);
int rc;
struct uwsgi_proxy_connection *upcs;
struct sockaddr_in upc_addr;
socklen_t upc_len = sizeof(struct sockaddr_in);
int next_node = -1;
uwsgi_log( "spawned uWSGI proxy (pid: %d)\n", getpid());
uwsgi_log( "allocating space for %d concurrent proxy connections\n", max_connections);
// allocate memory for connections
upcs = malloc(sizeof(struct uwsgi_proxy_connection) * max_connections);
if (!upcs) {
uwsgi_error("malloc()");
exit(1);
}
memset(upcs, 0, sizeof(struct uwsgi_proxy_connection) * max_connections);
efd = async_queue_init(proxyfd);
if (efd < 0) {
exit(1);
}
#ifdef __linux__
eevents = malloc(sizeof(struct epoll_event) * max_events);
memset(&ev, 0, sizeof(struct epoll_event));
#elif defined(__sun)
eevents = malloc(sizeof(struct pollfd) * max_events);
memset(&ev, 0, sizeof(struct pollfd));
#else
eevents = malloc(sizeof(struct kevent) * max_events);
memset(&ev, 0, sizeof(struct kevent));
#endif
if (!eevents) {
uwsgi_error("malloc()");
exit(1);
}
signal(SIGINT, (void *) &end_proxy);
signal(SIGTERM, (void *) &reload_proxy);
signal(SIGHUP, (void *) &reload_proxy);
// and welcome to the loop...
for (;;) {
nevents = async_wait(efd, eevents, max_events, -1, 0);
if (nevents < 0) {
uwsgi_error("epoll_wait()");
continue;
}
for (i = 0; i < nevents; i++) {
if (eevents[i].ASYNC_FD == proxyfd) {
if (eevents[i].ASYNC_IS_IN) {
// new connection, accept it
ev.ASYNC_FD = accept(proxyfd, (struct sockaddr *) &upc_addr, &upc_len);
if (ev.ASYNC_FD < 0) {
uwsgi_error("accept()");
continue;
}
upcs[ev.ASYNC_FD].node = -1;
// now connect to the first worker available
upcs[ev.ASYNC_FD].dest_fd = socket(AF_INET, SOCK_STREAM, 0);
if (upcs[ev.ASYNC_FD].dest_fd < 0) {
uwsgi_error("socket()");
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
upcs[upcs[ev.ASYNC_FD].dest_fd].node = -1;
// set nonblocking
if (ioctl(upcs[ev.ASYNC_FD].dest_fd, FIONBIO, &nonblocking)) {
uwsgi_error("ioctl()");
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
upcs[ev.ASYNC_FD].status = 0;
upcs[ev.ASYNC_FD].retry = 0;
next_node = uwsgi_proxy_find_next_node(next_node);
if (next_node == -1) {
uwsgi_log( "unable to find an available worker in the cluster !\n");
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
upcs[upcs[ev.ASYNC_FD].dest_fd].node = next_node;
rc = connect(upcs[ev.ASYNC_FD].dest_fd, (struct sockaddr *) &uwsgi.shared->nodes[next_node].ucn_addr, sizeof(struct sockaddr_in));
uwsgi.shared->nodes[next_node].connections++;
if (!rc) {
// connected to worker, put it in the epoll_list
if (async_add(efd, ev.ASYNC_FD, ASYNC_IN)) {
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
upcs[upcs[ev.ASYNC_FD].dest_fd].dest_fd = ev.ASYNC_FD;
upcs[upcs[ev.ASYNC_FD].dest_fd].status = 0;
upcs[upcs[ev.ASYNC_FD].dest_fd].retry = 0;
ev.ASYNC_FD = upcs[ev.ASYNC_FD].dest_fd;
if (async_add(efd, ev.ASYNC_FD, ASYNC_IN)) {
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
// re-set blocking
if (ioctl(upcs[upcs[ev.ASYNC_FD].dest_fd].dest_fd, FIONBIO, &blocking)) {
uwsgi_error("ioctl()");
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
}
else if (errno == EINPROGRESS) {
// the socket is waiting, set status to CONNECTING
upcs[ev.ASYNC_FD].status = UWSGI_PROXY_WAITING;
upcs[upcs[ev.ASYNC_FD].dest_fd].dest_fd = ev.ASYNC_FD;
upcs[upcs[ev.ASYNC_FD].dest_fd].status = UWSGI_PROXY_CONNECTING;
upcs[upcs[ev.ASYNC_FD].dest_fd].retry = 0;
ev.ASYNC_FD = upcs[ev.ASYNC_FD].dest_fd;
if (async_add(efd, ev.ASYNC_FD, ASYNC_OUT)) {
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
}
else {
// connection failed, retry with the next node ?
uwsgi_error("connect()");
// close only when all node are tried
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
}
else {
uwsgi_log( "!!! something horrible happened to the uWSGI proxy, reloading it !!!\n");
exit(1);
}
}
else {
// this is for clients/workers
if (eevents[i].ASYNC_IS_IN) {
// is this a connected client/worker ?
//uwsgi_log("ready %d\n", upcs[eevents[i].data.fd].status);
if (!upcs[eevents[i].ASYNC_FD].status) {
if (upcs[eevents[i].ASYNC_FD].dest_fd >= 0) {
rlen = read(eevents[i].ASYNC_FD, buffer, 4096);
if (rlen < 0) {
uwsgi_error("read()");
uwsgi_proxy_close(upcs, eevents[i].ASYNC_FD);
continue;
}
else if (rlen == 0) {
uwsgi_proxy_close(upcs, eevents[i].ASYNC_FD);
continue;
}
else {
wlen = write(upcs[eevents[i].ASYNC_FD].dest_fd, buffer, rlen);
if (wlen != rlen) {
uwsgi_error("write()");
uwsgi_proxy_close(upcs, eevents[i].ASYNC_FD);
continue;
}
}
}
else {
uwsgi_proxy_close(upcs, eevents[i].ASYNC_FD);
continue;
}
}
else if (upcs[eevents[i].ASYNC_FD].status == UWSGI_PROXY_WAITING) {
// disconnected node
continue;
}
else {
uwsgi_log( "UNKNOWN STATUS %d\n", upcs[eevents[i].ASYNC_FD].status);
continue;
}
}
else if (eevents[i].ASYNC_IS_OUT) {
if (upcs[eevents[i].ASYNC_FD].status == UWSGI_PROXY_CONNECTING) {
#ifdef UWSGI_PROXY_USE_KQUEUE
if (getsockopt(eevents[i].ASYNC_FD, SOL_SOCKET, SO_ERROR, (void *) (&soopt), &solen) < 0) {
uwsgi_error("getsockopt()");
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
/* is something bad ? */
if (soopt) {
uwsgi_log( "connect() %s\n", strerror(soopt));
// increase errors on node
uwsgi_log( "*** marking cluster node %d/%s as failed ***\n", upcs[eevents[i].ASYNC_FD].node, uwsgi.shared->nodes[upcs[eevents[i].ASYNC_FD].node].name);
uwsgi.shared->nodes[upcs[eevents[i].ASYNC_FD].node].errors++;
uwsgi.shared->nodes[upcs[eevents[i].ASYNC_FD].node].status = UWSGI_NODE_FAILED;
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
// increase errors on node
#endif
ev.ASYNC_FD = upcs[eevents[i].ASYNC_FD].dest_fd;
upcs[ev.ASYNC_FD].status = 0;
if (async_add(efd, ev.ASYNC_FD, ASYNC_IN)) {
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
ev.ASYNC_FD = upcs[ev.ASYNC_FD].dest_fd;
upcs[ev.ASYNC_FD].status = 0;
if (async_mod(efd, ev.ASYNC_FD, ASYNC_IN)) {
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
// re-set blocking
if (ioctl(ev.ASYNC_FD, FIONBIO, &blocking)) {
uwsgi_error("ioctl()");
uwsgi_proxy_close(upcs, ev.ASYNC_FD);
continue;
}
}
else {
uwsgi_log( "strange event for %d\n", (int) eevents[i].ASYNC_FD);
}
}
else {
if (upcs[eevents[i].ASYNC_FD].status == UWSGI_PROXY_CONNECTING) {
if (getsockopt(eevents[i].ASYNC_FD, SOL_SOCKET, SO_ERROR, (void *) (&soopt), &solen) < 0) {
uwsgi_error("getsockopt()");
}
/* is something bad ? */
if (soopt) {
uwsgi_log( "connect() %s\n", strerror(soopt));
}
// increase errors on node
uwsgi_log( "*** marking cluster node %d/%s as failed ***\n", upcs[eevents[i].ASYNC_FD].node, uwsgi.shared->nodes[upcs[eevents[i].ASYNC_FD].node].name);
uwsgi.shared->nodes[upcs[eevents[i].ASYNC_FD].node].errors++;
uwsgi.shared->nodes[upcs[eevents[i].ASYNC_FD].node].status = UWSGI_NODE_FAILED;
}
else {
uwsgi_log( "STRANGE EVENT !!! %d %d %d\n", (int) eevents[i].ASYNC_FD, (int) eevents[i].ASYNC_EV, upcs[eevents[i].ASYNC_FD].status);
}
uwsgi_proxy_close(upcs, eevents[i].ASYNC_FD);
continue;
}
}
}
}
}
#else
#warning "*** PROXY support is disabled ***"
#endif