diff --git a/include/proto/protocols.h b/include/proto/protocols.h index 354a4e971..05567ea68 100644 --- a/include/proto/protocols.h +++ b/include/proto/protocols.h @@ -71,6 +71,14 @@ int enable_all_listeners(struct protocol *proto); */ int disable_all_listeners(struct protocol *proto); +/* Marks a ready listener as limited so that we only try to re-enable it when + * resources are free again. It will be queued into the specified queue. + */ +void limit_listener(struct listener *l, struct list *list); + +/* Dequeues all of the listeners waiting for a resource in wait queue . */ +void dequeue_all_listeners(struct list *list); + /* 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 diff --git a/include/types/protocols.h b/include/types/protocols.h index a333ea2fc..80c1a6e02 100644 --- a/include/types/protocols.h +++ b/include/types/protocols.h @@ -46,6 +46,7 @@ enum { LI_LISTEN, /* started, listening but not enabled */ LI_READY, /* started, listening and enabled */ LI_FULL, /* reached its connection limit */ + LI_LIMITED, /* transient state: limits have been reached, listener is queued */ }; /* Listener transitions @@ -67,6 +68,11 @@ enum { * +----------------- * disable() * + * The LIMITED state my be used when a limit has been detected just before + * using a listener. In this case, the listener MUST be queued into the + * appropriate wait queue (either the proxy's or the global one). It may be + * set back to the READY state at any instant and for any reason, so one must + * not rely on this state. */ /* listener socket options */ @@ -101,6 +107,7 @@ struct listener { struct task * (*handler)(struct task *t); /* protocol handler. It is a task */ int *timeout; /* pointer to client-side timeout */ struct proxy *frontend; /* the frontend this listener belongs to, or NULL */ + struct list wait_queue; /* link element to make the listener wait for something (LI_LIMITED) */ unsigned int analysers; /* bitmap of required protocol analysers */ int nice; /* nice value to assign to the instanciated tasks */ union { /* protocol-dependant access restrictions */ diff --git a/src/protocols.c b/src/protocols.c index 7d8913d6c..46c41f607 100644 --- a/src/protocols.c +++ b/src/protocols.c @@ -50,6 +50,8 @@ void disable_listener(struct listener *listener) return; if (listener->state == LI_READY) EV_FD_CLR(listener->fd, DIR_RD); + if (listener->state == LI_LIMITED) + LIST_DEL(&listener->wait_queue); listener->state = LI_LISTEN; } @@ -74,15 +76,18 @@ int pause_listener(struct listener *l) if (shutdown(l->fd, SHUT_RD) != 0) return 0; /* should always be OK */ + if (l->state == LI_LIMITED) + LIST_DEL(&l->wait_queue); + EV_FD_CLR(l->fd, DIR_RD); l->state = LI_PAUSED; return 1; } -/* This function tries to resume a temporarily disabled listener. Paused, full - * and disabled listeners are handled, which means that this function may - * replace enable_listener(). The resulting state will either be LI_READY or - * LI_FULL. 0 is returned in case of failure to resume (eg: dead socket). +/* This function tries to resume a temporarily disabled listener. Paused, full, + * limited and disabled listeners are handled, which means that this function + * may replace enable_listener(). The resulting state will either be LI_READY + * or LI_FULL. 0 is returned in case of failure to resume (eg: dead socket). */ int resume_listener(struct listener *l) { @@ -96,6 +101,9 @@ int resume_listener(struct listener *l) if (l->state == LI_READY) return 1; + if (l->state == LI_LIMITED) + LIST_DEL(&l->wait_queue); + if (l->nbconn >= l->maxconn) { l->state = LI_FULL; return 1; @@ -112,11 +120,26 @@ int resume_listener(struct listener *l) void listener_full(struct listener *l) { if (l->state >= LI_READY) { + if (l->state == LI_LIMITED) + LIST_DEL(&l->wait_queue); + EV_FD_CLR(l->fd, DIR_RD); l->state = LI_FULL; } } +/* Marks a ready listener as limited so that we only try to re-enable it when + * resources are free again. It will be queued into the specified queue. + */ +void limit_listener(struct listener *l, struct list *list) +{ + if (l->state == LI_READY) { + LIST_ADDQ(list, &l->wait_queue); + EV_FD_CLR(l->fd, DIR_RD); + l->state = LI_LIMITED; + } +} + /* This function adds all of the protocol's listener's file descriptors to the * polling lists when they are in the LI_LISTEN state. It is intended to be * used as a protocol's generic enable_all() primitive, for use after the @@ -146,6 +169,20 @@ int disable_all_listeners(struct protocol *proto) return ERR_NONE; } +/* Dequeues all of the listeners waiting for a resource in wait queue . */ +void dequeue_all_listeners(struct list *list) +{ + struct listener *listener, *l_back; + + list_for_each_entry_safe(listener, l_back, list, wait_queue) { + /* This cannot fail because the listeners are by definition in + * the LI_LIMITED state. The function also removes the entry + * from the queue. + */ + resume_listener(listener); + } +} + /* 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 @@ -156,6 +193,9 @@ int unbind_listener(struct listener *listener) if (listener->state == LI_READY) EV_FD_CLR(listener->fd, DIR_RD); + if (listener->state == LI_LIMITED) + LIST_DEL(&listener->wait_queue); + if (listener->state >= LI_PAUSED) { fd_delete(listener->fd); listener->state = LI_ASSIGNED;