[MINOR] listeners: add support for queueing resource limited listeners

When a listeners encounters a resource shortage, it currently stops until
one re-enables it. This is far from being perfect as it does not yet handle
the case where the single connection from the listener is rejected (eg: the
stats page).

Now we'll have a special status for resource limited listeners and we'll
queue them into one or multiple lists. That way, each time we have to stop
a listener because of a resource shortage, we can enqueue it and change its
state, so that it is dequeued once more resources are available.

This patch currently does not change any existing behaviour, it only adds
the basic building blocks for doing that.
diff --git a/include/proto/protocols.h b/include/proto/protocols.h
index 354a4e9..05567ea 100644
--- a/include/proto/protocols.h
+++ b/include/proto/protocols.h
@@ -71,6 +71,14 @@
  */
 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 <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 a333ea2..80c1a6e 100644
--- a/include/types/protocols.h
+++ b/include/types/protocols.h
@@ -46,6 +46,7 @@
 	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 @@
  *   +-----------------
  *            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 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 7d8913d..46c41f6 100644
--- a/src/protocols.c
+++ b/src/protocols.c
@@ -50,6 +50,8 @@
 		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 @@
 	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 @@
 	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 @@
 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 @@
 	return ERR_NONE;
 }
 
+/* Dequeues all of the listeners waiting for a resource in wait queue <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 @@
 	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;