]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MAJOR] first limited implementation of connection queueing.
authorwilly tarreau <willy@wtap.(none)>
Mon, 1 May 2006 22:19:57 +0000 (00:19 +0200)
committerwilly tarreau <willy@wtap.(none)>
Mon, 1 May 2006 22:19:57 +0000 (00:19 +0200)
        There is no timeout yet, and the server UP/DOWN events are not used
to export/import list of connections yet. It seems that the process
can sometimes eat lots of user CPU (~50%) if a maxconn is set on an
overloaded server.

haproxy.c

index 5b96681712880212766e54ec26166195bfbef7cf..10cb6e5d8bbbd8a2d439ef1fd050d3260e2321f7 100644 (file)
--- a/haproxy.c
+++ b/haproxy.c
@@ -405,6 +405,8 @@ int strlcpy2(char *dst, const char *src, int size) {
 
 /* various other session flags, bits values 0x400000 and above */
 #define SN_MONITOR     0x00400000      /* this session comes from a monitoring system */
+#define SN_ASSIGNED    0x00800000      /* no need to assign a server to this session */
+#define SN_ADDR_SET    0x01000000      /* this session's server address has been set */
 
 
 /* different possible states for the client side */
@@ -448,6 +450,13 @@ int strlcpy2(char *dst, const char *src, int size) {
 #define        SRV_BIND_SRC    8       /* this server uses a specific source address */
 #define        SRV_CHECKED     16      /* this server needs to be checked */
 
+/* function which act on servers need to return various errors */
+#define SRV_STATUS_OK       0   /* everything is OK. */
+#define SRV_STATUS_INTERNAL 1   /* other unrecoverable errors. */
+#define SRV_STATUS_NOSRV    2   /* no server is available */
+#define SRV_STATUS_FULL     3   /* the/all server(s) are saturated */
+#define SRV_STATUS_QUEUED   4   /* the/all server(s) are saturated but the connection was queued */
+
 /* what to do when a header matches a regex */
 #define ACT_ALLOW      0       /* allow the request */
 #define ACT_REPLACE    1       /* replace the matching header */
@@ -622,6 +631,8 @@ struct proxy {
     int srvtimeout;                    /* server I/O timeout (in milliseconds) */
     int contimeout;                    /* connect timeout (in milliseconds) */
     char *id;                          /* proxy id */
+    struct list pendconns;             /* pending connections with no server assigned yet */
+    int nbpend;                                /* number of pending connections with no server assigned yet */
     int nbconn;                                /* # of active sessions */
     unsigned int cum_conn;             /* cumulated number of processed sessions */
     int maxconn;                       /* max # of active sessions */
@@ -1781,57 +1792,94 @@ struct task *task_queue(struct task *task) {
 /*********************************************************************/
 
 /*
- * returns the first pending connection of server <s> or NULL if none.
+ * Detaches pending connection <p>, decreases the pending count, and frees
+ * the pending connection. The connection might have been queued to a specific
+ * server as well as to the proxy. The session also gets marked unqueued.
  */
-static inline struct pendconn *pendconn_peek(struct server *s) {
+static void pendconn_free(struct pendconn *p) {
+    LIST_DEL(&p->list);
+    p->sess->pend_pos = NULL;
+    if (p->srv)
+       p->srv->nbpend--;
+    else
+       p->sess->proxy->nbpend--;
+    pool_free(pendconn, p);
+}
+
+/* Returns the first pending connection for server <s>, which may be NULL if
+ * nothing is pending.
+ */
+static inline struct pendconn *pendconn_from_srv(struct server *s) {
     if (!s->nbpend)
        return NULL;
 
     return LIST_ELEM(s->pendconns.n, struct pendconn *, list);
 }
 
-/*
- * Detaches pending connection <p>, decreases the pending count, and frees
- * the pending connection.
+/* Returns the first pending connection for proxy <px>, which may be NULL if
+ * nothing is pending.
  */
-static inline void pendconn_free(struct pendconn *p) {
-    LIST_DEL(&p->list);
-    p->sess->pend_pos = NULL;
-    p->srv->nbpend--;
-    pool_free(pendconn, p);
+static inline struct pendconn *pendconn_from_px(struct proxy *px) {
+    if (!px->nbpend)
+       return NULL;
+
+    return LIST_ELEM(px->pendconns.n, struct pendconn *, list);
 }
 
-/* detaches the first pending connection for server <s> and returns its
- * associated session. If no pending connection is found, NULL is returned.
+/* Detaches the next pending connection for either current session's server or
+ * current session's proxy, and returns its associated session. If no pending
+ * connection is found, NULL is returned. Note that cur->srv cannot be NULL.
  */
-static inline struct session *pendconn_get(struct server *s) {
+static struct session *pendconn_get_next_sess(struct session *cur) {
     struct pendconn *p;
     struct session *sess;
 
-    p = pendconn_peek(s);
-    if (!p)
-       return NULL;
+    p = pendconn_from_srv(cur->srv);
+    if (!p) {
+       p = pendconn_from_px(cur->proxy);
+       if (!p)
+           return NULL;
+       p->sess->srv = cur->srv;
+    }
     sess = p->sess;
     pendconn_free(p);
     return sess;
 }
 
-/* adds the session <sess> to the pending connection list of server <srv>.
- * All counters and back pointers are updated accordingly. Returns NULL if
- * no memory is available, otherwise the pendconn itself.
+/* Checks if other sessions are waiting for the same server, and wakes the
+ * first one up. Note that cur->srv cannot be NULL.
+ */
+void offer_connection_slot(struct session *cur) {
+    struct session *sess;
+
+    sess = pendconn_get_next_sess(cur);
+    if (sess == NULL)
+       return;
+    task_wakeup(&rq, sess->task);
+}
+
+/* Adds the session <sess> to the pending connection list of server <sess>->srv
+ * or to the one of <sess>->proxy if srv is NULL. All counters and back pointers
+ * are updated accordingly. Returns NULL if no memory is available, otherwise the
+ * pendconn itself.
  */
-static struct pendconn *pendconn_add(struct server *srv, struct session *sess) {
+static struct pendconn *pendconn_add(struct session *sess) {
     struct pendconn *p;
 
     p = pool_alloc(pendconn);
     if (!p)
        return NULL;
 
-    LIST_ADDQ(&srv->pendconns, &p->list);
-    p->sess = sess;
-    p->srv  = srv;
     sess->pend_pos = p;
-    srv->nbpend++;
+    p->sess = sess;
+    p->srv  = sess->srv;
+    if (sess->srv) {
+       LIST_ADDQ(&sess->srv->pendconns, &p->list);
+       sess->srv->nbpend++;
+    } else {
+       LIST_ADDQ(&sess->proxy->pendconns, &p->list);
+       sess->proxy->nbpend++;
+    }
     return p;
 }
 
@@ -1860,7 +1908,7 @@ static int get_original_dst(int fd, struct sockaddr_in *sa, socklen_t *salen) {
 /*
  * frees  the context associated to a session. It must have been removed first.
  */
-static inline void session_free(struct session *s) {
+static void session_free(struct session *s) {
     if (s->pend_pos)
        pendconn_free(s->pend_pos);
     if (s->req)
@@ -2053,60 +2101,99 @@ static inline struct server *get_server_sh(struct proxy *px, char *addr, int len
 
 
 /*
- * This function initiates a connection to the current server (s->srv) if (s->direct)
- * is set, or to the dispatch server if (s->direct) is 0.
- * It can return one of :
- *  - SN_ERR_NONE if everything's OK
- *  - SN_ERR_SRVTO if there are no more servers
- *  - SN_ERR_SRVCL if the connection was refused by the server
- *  - SN_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
- *  - SN_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
- *  - SN_ERR_INTERNAL for any other purely internal errors
- * Additionnally, in the case of SN_ERR_RESOURCE, an emergency log will be emitted.
+ * This function marks the session as 'assigned' in direct or dispatch modes,
+ * or tries to assign one in balance mode, according to the algorithm. It does
+ * nothing if the session had already been assigned a server.
+ *
+ * It may return :
+ *   SRV_STATUS_OK       if everything is OK.
+ *   SRV_STATUS_NOSRV    if no server is available
+ *   SRV_STATUS_FULL     if all servers are saturated
+ *   SRV_STATUS_INTERNAL for other unrecoverable errors.
+ *
+ * Upon successful return, the session flag SN_ASSIGNED to indicate that it does
+ * not need to be called anymore. This usually means that s->srv can be trusted
+ * in balance and direct modes. This flag is not cleared, so it's to the caller
+ * to clear it if required (eg: redispatch).
+ *
  */
-int connect_server(struct session *s) {
-    int fd;
 
+int assign_server(struct session *s) {
 #ifdef DEBUG_FULL
-    fprintf(stderr,"connect_server : s=%p\n",s);
+    fprintf(stderr,"assign_server : s=%p\n",s);
 #endif
 
-    if (s->flags & SN_DIRECT) { /* srv cannot be null */
-       s->srv_addr = s->srv->addr;
+    if (s->pend_pos)
+       return SRV_STATUS_INTERNAL;
+
+    if (!(s->flags & SN_ASSIGNED)) {
+        if ((s->proxy->options & PR_O_BALANCE) && !(s->flags & SN_DIRECT)) {
+           if (!s->proxy->srv_act && !s->proxy->srv_bck)
+               return SRV_STATUS_NOSRV;
+
+           if (s->proxy->options & PR_O_BALANCE_RR) {
+               s->srv = get_server_rr_with_conns(s->proxy);
+               if (!s->srv)
+                   return SRV_STATUS_FULL;
+           }
+           else if (s->proxy->options & PR_O_BALANCE_SH) {
+               int len;
+               
+               if (s->cli_addr.ss_family == AF_INET)
+                   len = 4;
+               else if (s->cli_addr.ss_family == AF_INET6)
+                   len = 16;
+               else /* unknown IP family */
+                   return SRV_STATUS_INTERNAL;
+               
+               s->srv = get_server_sh(s->proxy,
+                                      (void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr,
+                                      len);
+           }
+           else /* unknown balancing algorithm */
+               return SRV_STATUS_INTERNAL;
+       }
+       s->flags |= SN_ASSIGNED;
     }
-    else if (s->proxy->options & PR_O_BALANCE) {
-        /* Ensure that srv will not be NULL */
-        if (!s->proxy->srv_act && !s->proxy->srv_bck)
-            return SN_ERR_SRVTO;
+    return SRV_STATUS_OK;
+}
 
-       if (s->proxy->options & PR_O_BALANCE_RR) {
-           struct server *srv;
+/*
+ * This function assigns a server address to a session, and sets SN_ADDR_SET.
+ * The address is taken from the currently assigned server, or from the
+ * dispatch or transparent address.
+ *
+ * It may return :
+ *   SRV_STATUS_OK       if everything is OK.
+ *   SRV_STATUS_INTERNAL for other unrecoverable errors.
+ *
+ * Upon successful return, the session flag SN_ADDR_SET is set. This flag is
+ * not cleared, so it's to the caller to clear it if required.
+ *
+ */
+int assign_server_address(struct session *s) {
+#ifdef DEBUG_FULL
+    fprintf(stderr,"assign_server_address : s=%p\n",s);
+#endif
 
-           srv = get_server_rr_with_conns(s->proxy);
-           if (!srv)
-               srv = get_server_rr(s->proxy);
-           s->srv_addr = srv->addr;
-           s->srv = srv;
+    if (s->flags & SN_DIRECT || s->proxy->options & PR_O_BALANCE) {
+       /* A server is necessarily known for this session */
+       if (!(s->flags & SN_ASSIGNED))
+           return SRV_STATUS_INTERNAL;
+
+       s->srv_addr = s->srv->addr;
+
+       /* if this server remaps proxied ports, we'll use
+        * the port the client connected to with an offset. */
+       if (s->srv->state & SRV_MAPPORTS) {
+           struct sockaddr_in sockname;
+           socklen_t namelen = sizeof(sockname);
+
+           if (!(s->proxy->options & PR_O_TRANSP) ||
+               get_original_dst(s->cli_fd, (struct sockaddr_in *)&sockname, &namelen) == -1)
+               getsockname(s->cli_fd, (struct sockaddr *)&sockname, &namelen);
+           s->srv_addr.sin_port = htons(ntohs(s->srv_addr.sin_port) + ntohs(sockname.sin_port));
        }
-       else if (s->proxy->options & PR_O_BALANCE_SH) {
-           struct server *srv;
-            int len;
-
-            if (s->cli_addr.ss_family == AF_INET)
-                len = 4;
-            else if (s->cli_addr.ss_family == AF_INET6)
-                len = 16;
-            else /* unknown IP family */
-                return SN_ERR_INTERNAL;
-
-            srv = get_server_sh(s->proxy,
-                                (void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr,
-                                len);
-           s->srv_addr = srv->addr;
-           s->srv = srv;
-       }
-       else /* unknown balancing algorithm */
-           return SN_ERR_INTERNAL;
     }
     else if (*(int *)&s->proxy->dispatch_addr.sin_addr) {
        /* connect to the defined dispatch addr */
@@ -2118,20 +2205,101 @@ int connect_server(struct session *s) {
 
        if (get_original_dst(s->cli_fd, &s->srv_addr, &salen) == -1) {
            qfprintf(stderr, "Cannot get original server address.\n");
-           return SN_ERR_INTERNAL;
+           return SRV_STATUS_INTERNAL;
        }
     }
 
-    /* if this server remaps proxied ports, we'll use
-     * the port the client connected to with an offset. */
-    if (s->srv != NULL && s->srv->state & SRV_MAPPORTS) {
-       struct sockaddr_in sockname;
-       socklen_t namelen = sizeof(sockname);
+    s->flags |= SN_ADDR_SET;
+    return SRV_STATUS_OK;
+}
 
-       if (!(s->proxy->options & PR_O_TRANSP) ||
-           get_original_dst(s->cli_fd, (struct sockaddr_in *)&sockname, &namelen) == -1)
-           getsockname(s->cli_fd, (struct sockaddr *)&sockname, &namelen);
-       s->srv_addr.sin_port = htons(ntohs(s->srv_addr.sin_port) + ntohs(sockname.sin_port));
+/* This function assigns a server to session <s> if required, and can add the
+ * connection to either the assigned server's queue or to the proxy's queue.
+ *
+ * Returns :
+ *
+ *   SRV_STATUS_OK       if everything is OK.
+ *   SRV_STATUS_NOSRV    if no server is available
+ *   SRV_STATUS_QUEUED   if the connection has been queued.
+ *   SRV_STATUS_FULL     if the server(s) is/are saturated and the
+ *                       connection could not be queued.
+ *   SRV_STATUS_INTERNAL for other unrecoverable errors.
+ *
+ */
+int assign_server_and_queue(struct session *s) {
+    struct pendconn *p;
+    int err;
+
+    if (s->pend_pos)
+       return SRV_STATUS_INTERNAL;
+
+    if (s->flags & SN_ASSIGNED) {
+       /* a server does not need to be assigned, perhaps because we're in
+        * direct mode, or in dispatch or transparent modes where the server
+        * is not needed.
+        */
+       if (s->srv &&
+           s->srv->maxconn && s->srv->cur_sess >= s->srv->maxconn) {
+           p = pendconn_add(s);
+           if (p)
+               return SRV_STATUS_QUEUED;
+           else
+               return SRV_STATUS_FULL;
+       }
+       return SRV_STATUS_OK;
+    }
+
+    /* a server needs to be assigned */
+    err = assign_server(s);
+    switch (err) {
+    case SRV_STATUS_OK:
+       /* in balance mode, we might have servers with connection limits */
+       if (s->srv != NULL &&
+           s->srv->maxconn && s->srv->cur_sess >= s->srv->maxconn) {
+           p = pendconn_add(s);
+           if (p)
+               return SRV_STATUS_QUEUED;
+           else
+               return SRV_STATUS_FULL;
+       }
+       return SRV_STATUS_OK;
+
+    case SRV_STATUS_FULL:
+       /* queue this session into the proxy's queue */
+       p = pendconn_add(s);
+       if (p)
+           return SRV_STATUS_QUEUED;
+       else
+           return SRV_STATUS_FULL;
+
+    case SRV_STATUS_NOSRV:
+    case SRV_STATUS_INTERNAL:
+       return err;
+    default:
+       return SRV_STATUS_INTERNAL;
+    }
+}
+
+
+/*
+ * This function initiates a connection to the server assigned to this session
+ * (s->srv, s->srv_addr). It will assign a server if none is assigned yet.
+ * It can return one of :
+ *  - SN_ERR_NONE if everything's OK
+ *  - SN_ERR_SRVTO if there are no more servers
+ *  - SN_ERR_SRVCL if the connection was refused by the server
+ *  - SN_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
+ *  - SN_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
+ *  - SN_ERR_INTERNAL for any other purely internal errors
+ * Additionnally, in the case of SN_ERR_RESOURCE, an emergency log will be emitted.
+ */
+int connect_server(struct session *s) {
+    int fd, err;
+
+    if (!(s->flags & SN_ADDR_SET)) {
+       err = assign_server_address(s);
+       if (err != SRV_STATUS_OK)
+           return SN_ERR_INTERNAL;
     }
 
     if ((fd = s->srv_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
@@ -3624,7 +3792,7 @@ int process_cli(struct session *t) {
                            if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) {
                                /* we found the server and it's usable */
                                t->flags &= ~SN_CK_MASK;
-                               t->flags |= SN_CK_VALID | SN_DIRECT;
+                               t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED;
                                t->srv = srv;
                                break;
                            } else {
@@ -3879,7 +4047,7 @@ int process_cli(struct session *t) {
                                    if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) {
                                        /* we found the server and it's usable */
                                        t->flags &= ~SN_CK_MASK;
-                                       t->flags |= SN_CK_VALID | SN_DIRECT;
+                                       t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED;
                                        t->srv = srv;
                                        break;
                                    } else {
@@ -3976,7 +4144,7 @@ int process_cli(struct session *t) {
                                        if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) {
                                            /* we found the server and it's usable */
                                            t->flags &= ~SN_CK_MASK;
-                                           t->flags |= SN_CK_VALID | SN_DIRECT;
+                                           t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED;
                                            t->srv = srv;
                                            break;
                                        } else {
@@ -4312,6 +4480,150 @@ int process_cli(struct session *t) {
     return 0;
 }
 
+/* This function turns the server state into the SV_STCLOSE, and sets
+ * indicators accordingly. Note that if <status> is 0, no message is
+ * returned.
+ */
+void srv_close_with_err(struct session *t, int err, int finst, int status, int msglen, char *msg) {
+    t->srv_state = SV_STCLOSE;
+    if (status > 0) {
+       t->logs.status = status;
+       if (t->proxy->mode == PR_MODE_HTTP)
+           client_return(t, msglen, msg);
+    }
+    if (!(t->flags & SN_ERR_MASK))
+       t->flags |= err;
+    if (!(t->flags & SN_FINST_MASK))
+       t->flags |= finst;
+}
+
+/*
+ * This function checks the retry count during the connect() job.
+ * It updates the session's srv_state and retries, so that the caller knows
+ * what it has to do. It uses the last connection error to set the log when
+ * it expires. It returns 1 when it has expired, and 0 otherwise.
+ */
+int srv_count_retry_down(struct session *t, int conn_err) {
+    /* we are in front of a retryable error */
+    t->conn_retries--;
+    if (t->conn_retries < 0) {
+       /* if not retryable anymore, let's abort */
+       tv_eternity(&t->cnexpire);
+       srv_close_with_err(t, conn_err, SN_FINST_C,
+                          503, t->proxy->errmsg.len503, t->proxy->errmsg.msg503);
+           
+       /* We used to have a free connection slot. Since we'll never use it,
+        * we have to pass it on to another session.
+        */
+       if (t->srv)
+           offer_connection_slot(t);
+       return 1;
+    }
+    return 0;
+}
+
+/*
+ * This function performs the retryable part of the connect() job.
+ * It updates the session's srv_state and retries, so that the caller knows
+ * what it has to do. It returns 1 when it breaks out of the loop, or 0 if
+ * it needs to redispatch.
+ */
+int srv_retryable_connect(struct session *t) {
+    int conn_err;
+
+    /* This loop ensures that we stop before the last retry in case of a
+     * redispatchable server.
+     */
+    do {
+       /* initiate a connection to the server */
+       conn_err = connect_server(t);
+       switch (conn_err) {
+       
+       case SN_ERR_NONE:
+           //fprintf(stderr,"0: c=%d, s=%d\n", c, s);
+           t->srv_state = SV_STCONN;
+           return 1;
+           
+       case SN_ERR_INTERNAL:
+           tv_eternity(&t->cnexpire);
+           srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C,
+                              500, t->proxy->errmsg.len500, t->proxy->errmsg.msg500);
+           /* release other sessions waiting for this server */
+           if (t->srv)
+               offer_connection_slot(t);
+           return 1;
+       }
+       /* ensure that we have enough retries left */
+       if (srv_count_retry_down(t, conn_err))
+           return 1;
+    } while (t->srv == NULL || t->conn_retries > 0 || !(t->proxy->options & PR_O_REDISP));
+
+    /* We're on our last chance, and the REDISP option was specified.
+     * We will ignore cookie and force to balance or use the dispatcher.
+     */
+    t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
+    t->srv = NULL; /* it's left to the dispatcher to choose a server */
+    if ((t->flags & SN_CK_MASK) == SN_CK_VALID) {
+       t->flags &= ~SN_CK_MASK;
+       t->flags |= SN_CK_DOWN;
+    }
+    return 0;
+}
+
+/* This function performs the "redispatch" part of a connection attempt. It
+ * will assign a server if required, queue the connection if required, and
+ * handle errors that might arise at this level. It can change the server
+ * state. It will return 1 if it encounters an error, switches the server
+ * state, or has to queue a connection. Otherwise, it will return 0 indicating
+ * that the connection is ready to use.
+ */
+
+int srv_redispatch_connect(struct session *t) {
+    int conn_err;
+
+    /* We know that we don't have any connection pending, so we will
+     * try to get a new one, and wait in this state if it's queued
+     */
+    conn_err = assign_server_and_queue(t);
+    switch (conn_err) {
+    case SRV_STATUS_OK:
+       break;
+
+    case SRV_STATUS_NOSRV:
+       tv_eternity(&t->cnexpire);
+       srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_C,
+                          503, t->proxy->errmsg.len503, t->proxy->errmsg.msg503);
+
+       /* FIXME-20060501: we should not need this once we flush every session
+        * when the last server goes down.
+        */
+       /* release other sessions waiting for this server */
+       if (t->srv)
+           offer_connection_slot(t);
+       return 1;
+
+    case SRV_STATUS_QUEUED:
+       t->srv_state = SV_STIDLE;
+       /* do nothing else and do not wake any other session up */
+       return 1;
+
+    case SRV_STATUS_FULL:
+    case SRV_STATUS_INTERNAL:
+    default:
+       tv_eternity(&t->cnexpire);
+       srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C,
+                          500, t->proxy->errmsg.len500, t->proxy->errmsg.msg500);
+       /* release other sessions waiting for this server */
+       if (t->srv)
+           offer_connection_slot(t);
+       return 1;
+    }
+    /* if we get here, it's because we got SRV_STATUS_OK, which also
+     * means that the connection has not been queued.
+     */
+    return 0;
+}
+
 
 /*
  * manages the server FSM and its socket. It returns 1 if a state has changed
@@ -4340,51 +4652,37 @@ int process_srv(struct session *t) {
                 c == CL_STSHUTW ||
                 (c == CL_STSHUTR && t->req->l == 0)) { /* give up */
            tv_eternity(&t->cnexpire);
-           t->srv_state = SV_STCLOSE;
-           if (!(t->flags & SN_ERR_MASK))
-               t->flags |= SN_ERR_CLICL;
-           if (!(t->flags & SN_FINST_MASK))
-               t->flags |= SN_FINST_C;
+           srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, 0, NULL);
+
+           /* it might be possible that we have been granted an access to the
+            * server while waiting for a free slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
            return 1;
        }
-       else { /* go to SV_STCONN */
-           /* initiate a connection to the server */
-           conn_err = connect_server(t);
-           if (conn_err == SN_ERR_NONE) {
-               //fprintf(stderr,"0: c=%d, s=%d\n", c, s);
-               t->srv_state = SV_STCONN;
-           }
-           else { /* try again */
-               while (t->conn_retries-- > 0) {
-                   if ((t->proxy->options & PR_O_REDISP) && (t->conn_retries == 0)) {
-                       t->flags &= ~SN_DIRECT; /* ignore cookie and force to use the dispatcher */
-                       t->srv = NULL; /* it's left to the dispatcher to choose a server */
-                       if ((t->flags & SN_CK_MASK) == SN_CK_VALID) {
-                           t->flags &= ~SN_CK_MASK;
-                           t->flags |= SN_CK_DOWN;
-                       }
-                   }
+       else {
+           /* Right now, we will need to create a connection to the server.
+            * We might already have tried, and got a connection pending, in
+            * which case we will not do anything till it's pending. It's up
+            * to any other session to release it and wake us up again.
+            */
+           if (t->pend_pos)
+               return 0;
 
-                   conn_err = connect_server(t);
-                   if (conn_err == SN_ERR_NONE) {
-                       t->srv_state = SV_STCONN;
-                       break;
-                   }
-               }
-               if (t->conn_retries < 0) {
-                   /* if conn_retries < 0 or other error, let's abort */
-                   tv_eternity(&t->cnexpire);
-                   t->srv_state = SV_STCLOSE;
-                   t->logs.status = 503;
-                   if (t->proxy->mode == PR_MODE_HTTP)
-                       client_return(t, t->proxy->errmsg.len503, t->proxy->errmsg.msg503);
-                   if (!(t->flags & SN_ERR_MASK))
-                       t->flags |= conn_err;  /* report the precise connect() error */
-                   if (!(t->flags & SN_FINST_MASK))
-                       t->flags |= SN_FINST_C;
-               }
-           }
-           return 1;
+           do {
+               /* first, get a connection */
+               if (srv_redispatch_connect(t))
+                   return t->srv_state != SV_STIDLE;
+
+               /* try to (re-)connect to the server, and fail if we expire the
+                * number of retries.
+                */
+               if (srv_retryable_connect(t))
+                   return t->srv_state != SV_STIDLE;
+
+           } while (1);
        }
     }
     else if (s == SV_STCONN) { /* connection in progress */
@@ -4393,44 +4691,35 @@ int process_srv(struct session *t) {
            return 0; /* nothing changed */
        }
        else if (t->res_sw == RES_SILENT || t->res_sw == RES_ERROR) {
+           /* timeout, asynchronous connect error or first write error */
            //fprintf(stderr,"2: c=%d, s=%d\n", c, s);
-           /* timeout,  connect error or first write error */
-           //FD_CLR(t->srv_fd, StaticWriteEvent);
+
            fd_delete(t->srv_fd);
            if (t->srv)
                t->srv->cur_sess--;
-           //close(t->srv_fd);
-           t->conn_retries--;
-           if (t->conn_retries >= 0) {
-                   if ((t->proxy->options & PR_O_REDISP) && (t->conn_retries == 0)) {
-                       t->flags &= ~SN_DIRECT; /* ignore cookie and force to use the dispatcher */
-                       t->srv = NULL; /* it's left to the dispatcher to choose a server */
-                       if ((t->flags & SN_CK_MASK) == SN_CK_VALID) {
-                           t->flags &= ~SN_CK_MASK;
-                           t->flags |= SN_CK_DOWN;
-                       }
-                   }
-                   conn_err = connect_server(t);
-                   if (conn_err == SN_ERR_NONE)
-                       return 0; /* no state changed */
-           }
-           else if (t->res_sw == RES_SILENT)
+
+           if (t->res_sw == RES_SILENT)
                conn_err = SN_ERR_SRVTO; // it was a connect timeout.
            else
-               conn_err = SN_ERR_SRVCL; // it was a connect error.
+               conn_err = SN_ERR_SRVCL; // it was an asynchronous connect error.
 
-           /* if conn_retries < 0 or other error, let's abort */
-           tv_eternity(&t->cnexpire);
-           t->srv_state = SV_STCLOSE;
-           t->logs.status = 503;
-           if (t->proxy->mode == PR_MODE_HTTP)
-               client_return(t, t->proxy->errmsg.len503, t->proxy->errmsg.msg503);
-           if (!(t->flags & SN_ERR_MASK))
-               t->flags |= conn_err;
-           if (!(t->flags & SN_FINST_MASK))
-               t->flags |= SN_FINST_C;
-           /* TODO : check if there are pending connections on this server */
-           return 1;
+           /* ensure that we have enough retries left */
+           if (srv_count_retry_down(t, conn_err))
+               return 1;
+
+           do {
+               /* Now we will try to either reconnect to the same server or
+                * connect to another server. If the connection gets queued
+                * because all servers are saturated, then we will go back to
+                * the SV_STIDLE state.
+                */
+               if (srv_retryable_connect(t))
+                   return t->srv_state != SV_STCONN;
+
+               /* we need to redispatch the connection to another server */
+               if (srv_redispatch_connect(t))
+                   return t->srv_state != SV_STCONN;
+           } while (1);
        }
        else { /* no error or write 0 */
            t->logs.t_connect = tv_diff(&t->logs.tv_accept, &now);
@@ -4521,7 +4810,12 @@ int process_srv(struct session *t) {
                        Alert("Blocking cacheable cookie in response from instance %s, server %s.\n", t->proxy->id, t->srv->id);
                        send_log(t->proxy, LOG_ALERT, "Blocking cacheable cookie in response from instance %s, server %s.\n", t->proxy->id, t->srv->id);
 
-                       /* TODO : check if there are pending connections on this server */
+                       /* We used to have a free connection slot. Since we'll never use it,
+                        * we have to pass it on to another session.
+                        */
+                       if (t->srv)
+                           offer_connection_slot(t);
+
                        return 1;
                    }
                }
@@ -4540,7 +4834,12 @@ int process_srv(struct session *t) {
                        t->flags |= SN_ERR_PRXCOND;
                    if (!(t->flags & SN_FINST_MASK))
                        t->flags |= SN_FINST_H;
-                   /* TODO : check if there are pending connections on this server */
+                   /* We used to have a free connection slot. Since we'll never use it,
+                    * we have to pass it on to another session.
+                    */
+                   if (t->srv)
+                       offer_connection_slot(t);
+
                    return 1;
                }
 
@@ -4974,7 +5273,12 @@ int process_srv(struct session *t) {
                t->flags |= SN_ERR_SRVCL;
            if (!(t->flags & SN_FINST_MASK))
                t->flags |= SN_FINST_H;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }
        /* end of client write or end of server read.
@@ -5004,7 +5308,12 @@ int process_srv(struct session *t) {
                t->flags |= SN_ERR_SRVTO;
            if (!(t->flags & SN_FINST_MASK))
                t->flags |= SN_FINST_H;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }       
        /* last client read and buffer empty */
@@ -5099,7 +5408,12 @@ int process_srv(struct session *t) {
                t->flags |= SN_ERR_SRVCL;
            if (!(t->flags & SN_FINST_MASK))
                t->flags |= SN_FINST_D;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }
        /* last read, or end of client write */
@@ -5208,7 +5522,12 @@ int process_srv(struct session *t) {
                t->flags |= SN_ERR_SRVCL;
            if (!(t->flags & SN_FINST_MASK))
                t->flags |= SN_FINST_D;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }
        else if ((c == CL_STSHUTR || c == CL_STCLOSE) && (req->l == 0)) {
@@ -5219,7 +5538,12 @@ int process_srv(struct session *t) {
                t->srv->cur_sess--;
            //close(t->srv_fd);
            t->srv_state = SV_STCLOSE;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }
        else if (tv_cmp2_ms(&t->swexpire, &now) <= 0) {
@@ -5234,7 +5558,12 @@ int process_srv(struct session *t) {
                t->flags |= SN_ERR_SRVTO;
            if (!(t->flags & SN_FINST_MASK))
                t->flags |= SN_FINST_D;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }
        else if (req->l == 0) {
@@ -5271,7 +5600,12 @@ int process_srv(struct session *t) {
                t->flags |= SN_ERR_SRVCL;
            if (!(t->flags & SN_FINST_MASK))
                t->flags |= SN_FINST_D;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }
        else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE) {
@@ -5282,7 +5616,12 @@ int process_srv(struct session *t) {
                t->srv->cur_sess--;
            //close(t->srv_fd);
            t->srv_state = SV_STCLOSE;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }
        else if (tv_cmp2_ms(&t->srexpire, &now) <= 0) {
@@ -5297,7 +5636,12 @@ int process_srv(struct session *t) {
                t->flags |= SN_ERR_SRVTO;
            if (!(t->flags & SN_FINST_MASK))
                t->flags |= SN_FINST_D;
-           /* TODO : check if there are pending connections on this server */
+           /* We used to have a free connection slot. Since we'll never use it,
+            * we have to pass it on to another session.
+            */
+           if (t->srv)
+               offer_connection_slot(t);
+
            return 1;
        }
        else if (rep->l == BUFSIZE) { /* no room to read more data */
@@ -6666,8 +7010,11 @@ int cfg_parse_listen(char *file, int linenum, char **args) {
            Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
            return -1;
        }
+       
        curproxy->next = proxy;
        proxy = curproxy;
+       LIST_INIT(&curproxy->pendconns);
+
        curproxy->id = strdup(args[1]);
 
        /* parse the listener address if any */