[MAJOR] add a connection error state to the stream_interface

Tracking connection status changes was hard, and some code was
redundant. A new SI_ST_CER state was added to the stream interface
to indicate a past connection error, and an SI_FL_ERR flag was
added to report past I/O error. The stream_sock code does not set
the connection to SI_ST_CLO anymore in case of I/O error, it's
the upper layer which does it. This makes it possible to know
exactly when the file descriptors are allocated.

The new SI_ST_CER state permitted to split tcp_connection_status()
in two parts, one processing SI_ST_CON and the other one SI_ST_CER.
Synchronous connection errors now make use of this last state, hence
eliminating duplicate code.

Some ib<->ob copy paste errors were found and fixed, and all entities
setting SI_ST_CLO also shut the buffers down.

Some of these stream_interface specific functions and structures
have migrated to a new stream_interface.c file.

Some types of errors are still not detected by the buffers. For
instance, let's assume the following scenario in one single pass
of process_session: a connection sits in SI_ST_TAR state during
a retry. At TAR expiration, a new connection attempt is made, the
connection is obtained and srv->cur_sess is increased. Then the
buffer timeout is fires and everything is cleared, the new state
becomes SI_ST_CLO. The cleaning code checks that previous state
was either SI_ST_CON or SI_ST_EST to release the connection. But
that's wrong because last state is still SI_ST_TAR. So the
server's connection count does not get decreased.

This means that prev_state must not be used, and must be replaced
by some transition detection instead of level detection.

The following debugging line was useful to track state changes :

  fprintf(stderr, "%s:%d: cs=%d ss=%d(%d) rqf=0x%08x rpf=0x%08x\n", __FUNCTION__, __LINE__,
          s->si[0].state, s->si[1].state, s->si[1].err_type, s->req->flags, s-> rep->flags);
diff --git a/Makefile b/Makefile
index bf172f6..1cdeac1 100644
--- a/Makefile
+++ b/Makefile
@@ -452,7 +452,7 @@
        src/time.o src/fd.o src/regex.o src/cfgparse.o src/server.o \
        src/checks.o src/queue.o src/client.o src/proxy.o src/proto_uxst.o \
        src/proto_http.o src/stream_sock.o src/appsession.o src/backend.o \
-       src/senddata.o src/dumpstats.o src/proto_tcp.o \
+       src/stream_interface.o src/senddata.o src/dumpstats.o src/proto_tcp.o \
        src/session.o src/hdr_idx.o src/ev_select.o \
        src/acl.o src/memory.o \
        src/ebtree.o src/eb32tree.o
diff --git a/include/proto/stream_interface.h b/include/proto/stream_interface.h
new file mode 100644
index 0000000..9967a6e
--- /dev/null
+++ b/include/proto/stream_interface.h
@@ -0,0 +1,42 @@
+/*
+  include/proto/stream_interface.h
+  This file contains stream_interface function prototypes
+
+  Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu
+  
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation, version 2.1
+  exclusively.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef _PROTO_STREAM_INTERFACE_H
+#define _PROTO_STREAM_INTERFACE_H
+
+#include <stdlib.h>
+
+#include <common/config.h>
+#include <types/stream_interface.h>
+
+
+/* main event functions used to move data between sockets and buffers */
+void stream_int_check_timeouts(struct stream_interface *si);
+void stream_int_report_error(struct stream_interface *si);
+
+#endif /* _PROTO_STREAM_INTERFACE_H */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/include/proto/stream_sock.h b/include/proto/stream_sock.h
index 7a2dd3d..ddad638 100644
--- a/include/proto/stream_sock.h
+++ b/include/proto/stream_sock.h
@@ -37,7 +37,6 @@
 int stream_sock_data_finish(int fd);
 int stream_sock_shutr(struct stream_interface *si);
 int stream_sock_shutw(struct stream_interface *si);
-int stream_sock_check_timeouts(struct stream_interface *si);
 
 
 /* This either returns the sockname or the original destination address. Code
diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h
index 9e5e4d9..fbd2ab0 100644
--- a/include/types/stream_interface.h
+++ b/include/types/stream_interface.h
@@ -38,8 +38,9 @@
 	SI_ST_TAR,               /* interface in turn-around state after failed connect attempt */
 	SI_ST_ASS,               /* server just assigned to this interface */
 	SI_ST_CON,               /* initiated connection request (resource exists) */
+	SI_ST_CER,               /* previous connection attempt failed (resource released) */
 	SI_ST_EST,               /* connection established (resource exists) */
-	SI_ST_CLO,               /* stream interface closed, might not existing anymore */
+	SI_ST_CLO,               /* stream intf closed, might not existing anymore. Buffers shut. */
 };
 
 /* error types reported on the streams interface for more accurate reporting */
@@ -61,6 +62,7 @@
 enum {
 	SI_FL_NONE       = 0x0000,  /* nothing */
 	SI_FL_EXP        = 0x0001,  /* timeout has expired */
+	SI_FL_ERR        = 0x0002,  /* a non-recoverable error has occurred */
 };
 
 struct stream_interface {
diff --git a/src/proto_http.c b/src/proto_http.c
index b4e7f83..6f2dab3 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -50,6 +50,7 @@
 #include <proto/queue.h>
 #include <proto/senddata.h>
 #include <proto/session.h>
+#include <proto/stream_interface.h>
 #include <proto/stream_sock.h>
 #include <proto/task.h>
 
@@ -205,7 +206,8 @@
 #error "Check if your OS uses bitfields for fd_sets"
 #endif
 
-int tcp_connection_status(struct session *t);
+int sess_update_st_con_tcp(struct session *s, struct stream_interface *si);
+int sess_update_st_cer(struct session *s, struct stream_interface *si);
 
 void init_proto_http()
 {
@@ -649,6 +651,7 @@
 
 /* Update stream interface status for input states SI_ST_ASS, SI_ST_QUE, SI_ST_TAR.
  * Other input states are simply ignored.
+ * Possible output states are SI_ST_CLO, SI_ST_TAR, SI_ST_ASS, SI_ST_REQ, SI_ST_CON.
  * Flags must have previously been updated for timeouts and other conditions.
  */
 void sess_update_stream_int(struct session *s, struct stream_interface *si)
@@ -691,62 +694,23 @@
 				process_srv_queue(s->srv);
 
 			/* Failed and not retryable. */
-			buffer_shutr(s->rep);
-			buffer_shutw(s->req);
-			s->req->flags |= BF_WRITE_ERROR;
+			buffer_shutr(si->ib);
+			buffer_shutw(si->ob);
+			si->ob->flags |= BF_WRITE_ERROR;
 
 			s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now);
 			si->state = SI_ST_CLO;
 			return;
 		}
 
-		/* We are facing a retryable error */
-		s->conn_retries--;
-		if (s->conn_retries < 0) {
-			/* No retries left, abort */
-
-			if (!si->err_type) {
-				si->err_type = SI_ET_CONN_ERR;
-				si->err_loc  = s->srv;
-			}
-
-			if (s->srv)
-				s->srv->failed_conns++;
-			s->be->failed_conns++;
-
-			/* We used to have a free connection slot. Since we'll never use it,
-			 * we have to inform the server that it may be used by another session.
-			 */
-			if (may_dequeue_tasks(s->srv, s->be))
-				process_srv_queue(s->srv);
-
-			buffer_shutr(s->rep);
-			buffer_shutw(s->req);
-			s->req->flags |= BF_WRITE_ERROR;
-
-			si->state = SI_ST_CLO;
-			return;
-		}
-
-		/* If the "redispatch" option is set on the backend, we are allowed to
-		 * retry on another server for the last retry. In order to achieve this,
-		 * we must mark the session unassigned, and eventually clear the DIRECT
-		 * bit to ignore any persistence cookie. We won't count a retry nor a
-		 * redispatch yet, because this will depend on what server is selected.
+		/* We are facing a retryable error, but we don't want to run a
+		 * turn-around now, as the problem is likely a source port
+		 * allocation problem, so we want to retry now.
 		 */
-		if (s->srv && s->conn_retries == 0 && s->be->options & PR_O_REDISP) {
-			if (may_dequeue_tasks(s->srv, s->be))
-				process_srv_queue(s->srv);
-
-			s->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
-			s->prev_srv = s->srv;
-			si->state = SI_ST_REQ;
-		} else {
-			if (s->srv)
-				s->srv->retries++;
-			s->be->retries++;
-			si->state = SI_ST_ASS;
-		}
+		si->state = SI_ST_CER;
+		si->flags &= ~SI_FL_ERR;
+		sess_update_st_cer(s, si);
+		/* now si->state is one of SI_ST_CLO, SI_ST_TAR, SI_ST_ASS, SI_ST_REQ */
 		return;
 	}
 	else if (si->state == SI_ST_QUE) {
@@ -775,7 +739,9 @@
 			if (s->srv)
 				s->srv->failed_conns++;
 			s->be->failed_conns++;
-			s->req->flags |= BF_WRITE_TIMEOUT;
+			buffer_shutr(si->ib);
+			buffer_shutw(si->ob);
+			si->ob->flags |= BF_WRITE_TIMEOUT;
 			if (!si->err_type)
 				si->err_type = SI_ET_QUEUE_TO;
 			si->state = SI_ST_CLO;
@@ -783,14 +749,14 @@
 		}
 
 		/* Connection remains in queue, check if we have to abort it */
-		if ((s->req->flags & (BF_READ_ERROR|BF_SHUTW_NOW)) |  /* abort requested */
-		    ((s->req->flags & BF_SHUTR) &&           /* empty and client stopped */
-		     (s->req->flags & BF_EMPTY || s->be->options & PR_O_ABRT_CLOSE))) {
+		if ((si->ob->flags & (BF_READ_ERROR|BF_SHUTW_NOW)) || /* abort requested */
+		    ((si->ob->flags & BF_SHUTR) &&           /* empty and client stopped */
+		     (si->ob->flags & BF_EMPTY || s->be->options & PR_O_ABRT_CLOSE))) {
 			/* give up */
 			si->exp = TICK_ETERNITY;
 			s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now);
-			buffer_shutr(s->rep);
-			buffer_shutw(s->req);
+			buffer_shutr(si->ib);
+			buffer_shutw(si->ob);
 			si->err_type |= SI_ET_QUEUE_ABRT;
 			si->state = SI_ST_CLO;
 			return;
@@ -801,13 +767,13 @@
 	}
 	else if (si->state == SI_ST_TAR) {
 		/* Connection request might be aborted */
-		if ((s->req->flags & (BF_READ_ERROR|BF_SHUTW_NOW)) |  /* abort requested */
-		    ((s->req->flags & BF_SHUTR) &&           /* empty and client stopped */
-		     (s->req->flags & BF_EMPTY || s->be->options & PR_O_ABRT_CLOSE))) {
+		if ((si->ob->flags & (BF_READ_ERROR|BF_SHUTW_NOW)) || /* abort requested */
+		    ((si->ob->flags & BF_SHUTR) &&           /* empty and client stopped */
+		     (si->ob->flags & BF_EMPTY || s->be->options & PR_O_ABRT_CLOSE))) {
 			/* give up */
 			si->exp = TICK_ETERNITY;
-			buffer_shutr(s->rep);
-			buffer_shutw(s->req);
+			buffer_shutr(si->ib);
+			buffer_shutw(si->ob);
 			si->err_type |= SI_ET_CONN_ABRT;
 			si->state = SI_ST_CLO;
 			return;
@@ -870,6 +836,8 @@
 	rdr.len += 4;
 
 	/* prepare to return without error. */
+	buffer_shutr(si->ib);
+	buffer_shutw(si->ob);
 	si->err_type = SI_ET_NONE;
 	si->err_loc  = NULL;
 	si->state    = SI_ST_CLO;
@@ -909,9 +877,9 @@
 			return;
 
 		/* we did not get any server, let's check the cause */
-		buffer_shutr(s->rep);
-		buffer_shutw(s->req);
-		s->req->flags |= BF_WRITE_ERROR;
+		buffer_shutr(si->ib);
+		buffer_shutw(si->ob);
+		si->ob->flags |= BF_WRITE_ERROR;
 		if (!si->err_type)
 			si->err_type = SI_ET_CONN_OTHER;
 		si->state = SI_ST_CLO;
@@ -975,77 +943,94 @@
 	unsigned int rqf_cli, rpf_cli;
 	unsigned int rqf_srv, rpf_srv;
 
-	/* 1a: Check for lower layer timeouts if needed */
-	if (unlikely(t->state & TASK_WOKEN_TIMER)) {
-		stream_sock_check_timeouts(&s->si[0]);
-		stream_sock_check_timeouts(&s->si[1]);
-	}
+	//DPRINTF(stderr, "%s:%d: cs=%d ss=%d(%d) rqf=0x%08x rpf=0x%08x\n", __FUNCTION__, __LINE__,
+	//        s->si[0].state, s->si[1].state, s->si[1].err_type, s->req->flags, s->rep->flags);
 
-	/* 1b: Check for upper layer timeouts if needed */
+	/* 1a: Check for low level timeouts if needed. We just set a flag on
+	 * buffers and/or stream interfaces when their timeouts have expired.
+	 */
 	if (unlikely(t->state & TASK_WOKEN_TIMER)) {
+		stream_int_check_timeouts(&s->si[0]);
+		stream_int_check_timeouts(&s->si[1]);
+
 		buffer_check_timeouts(s->req);
 		buffer_check_timeouts(s->rep);
+	}
 
-		if (unlikely(s->req->flags & (BF_READ_TIMEOUT|BF_WRITE_TIMEOUT))) {
-			if (s->req->flags & BF_READ_TIMEOUT) {
-				buffer_shutw(s->req);
-				s->req->cons->shutr(s->req->prod);
-			}
-			if (s->req->flags & BF_WRITE_TIMEOUT) {
-				buffer_shutw(s->req);
-				s->req->cons->shutw(s->req->cons);
-			}
+	/* 1b: check for low-level errors reported at the stream interface.
+	 * First we check if it's a retryable error (in which case we don't
+	 * want to tell the buffer). Otherwise we report the error one level
+	 * upper by setting flags into the buffers. Note that the side towards
+	 * the client cannot have connect (hence retryable) errors.
+	 */
+	if (unlikely(s->si[0].state == SI_ST_EST)) {
+		if (s->si[0].flags & SI_FL_ERR) {
+			s->si[0].state = SI_ST_CLO;
+			fd_delete(s->si[0].fd);
+			stream_int_report_error(&s->si[0]);
 		}
+	}
 
-		if (unlikely(s->rep->flags & (BF_READ_TIMEOUT|BF_WRITE_TIMEOUT))) {
-			if (s->rep->flags & BF_READ_TIMEOUT) {
-				buffer_shutw(s->rep);
-				s->rep->cons->shutr(s->rep->prod);
-			}
-			if (s->rep->flags & BF_WRITE_TIMEOUT) {
-				buffer_shutw(s->rep);
-				s->rep->cons->shutw(s->rep->cons);
-			}
+	if (s->si[1].state == SI_ST_EST) {
+		if (s->si[1].flags & SI_FL_ERR) {
+			s->si[1].state = SI_ST_CLO;
+			fd_delete(s->si[1].fd);
+			stream_int_report_error(&s->si[1]);
 		}
-		/* Note that we don't check nor indicate if we wake up because
-		 * of a timeout on a stream interface.
-		 */
 	}
+	else if (s->si[1].state != SI_ST_INI && s->si[1].state != SI_ST_CLO) {
+		/* Maybe we were trying to establish a connection on the server side ? */
+		if (s->si[1].state == SI_ST_CON)
+			sess_update_st_con_tcp(s, &s->si[1]);
 
-	/* Maybe we were trying to establish a connection on the server side ? */
-	if (s->si[1].state == SI_ST_CON)
-		tcp_connection_status(s);
+		if (s->si[1].state == SI_ST_CER)
+			sess_update_st_cer(s, &s->si[1]);
 
-	/* now try to complete any initiated connection setup */
-	if (s->si[1].state >= SI_ST_REQ && s->si[1].state < SI_ST_CON) {
-		do {
-			/* nb: step 1 might switch from QUE to ASS, but we first want
-			 * to give a chance to step 2 to perform a redirect if needed.
-			 */
-			sess_update_stream_int(s, &s->si[1]);
-			if (s->si[1].state == SI_ST_REQ)
-				sess_prepare_conn_req(s, &s->si[1]);
+		/* now try to complete any initiated connection setup */
+		if (s->si[1].state >= SI_ST_REQ && s->si[1].state < SI_ST_CON) {
+			do {
+				/* nb: step 1 might switch from QUE to ASS, but we first want
+				 * to give a chance to step 2 to perform a redirect if needed.
+				 */
+				sess_update_stream_int(s, &s->si[1]);
+				if (s->si[1].state == SI_ST_REQ)
+					sess_prepare_conn_req(s, &s->si[1]);
 
-			if (s->si[1].state == SI_ST_ASS && s->srv &&
-			    s->srv->rdr_len && (s->flags & SN_REDIRECTABLE))
-				perform_http_redirect(s, &s->si[1]);
+				if (s->si[1].state == SI_ST_ASS && s->srv &&
+				    s->srv->rdr_len && (s->flags & SN_REDIRECTABLE))
+					perform_http_redirect(s, &s->si[1]);
 
-		} while (s->si[1].state == SI_ST_ASS);
+			} while (s->si[1].state == SI_ST_ASS);
+		}
 	}
 
 	/* FIXME: we might have got an error above, and we should process them below */
-	if (s->si[1].state == SI_ST_CLO && s->si[1].err_type != SI_ET_NONE)
+	if (s->si[1].state == SI_ST_CLO && s->si[1].prev_state != SI_ST_CLO &&
+	    s->si[1].err_type != SI_ET_NONE)
 		return_srv_error(s, s->si[1].err_type);
 
-	/* Forward errors from stream interface to buffers */
-	if (s->si[0].state == SI_ST_CLO && s->si[0].err_type != SI_ET_NONE) {
-		s->req->flags |= BF_READ_ERROR;
-		s->rep->flags |= BF_WRITE_ERROR;
+
+	/* 1c: Manage buffer timeouts. */
+	if (unlikely(s->req->flags & (BF_READ_TIMEOUT|BF_WRITE_TIMEOUT))) {
+		if (s->req->flags & BF_READ_TIMEOUT) {
+			buffer_shutr(s->req);
+			s->req->cons->shutr(s->req->prod);
+		}
+		if (s->req->flags & BF_WRITE_TIMEOUT) {
+			buffer_shutw(s->req);
+			s->req->cons->shutw(s->req->cons);
+		}
 	}
 
-	if (s->si[1].state == SI_ST_CLO && s->si[1].err_type != SI_ET_NONE) {
-		s->req->flags |= BF_WRITE_ERROR;
-		s->rep->flags |= BF_READ_ERROR;
+	if (unlikely(s->rep->flags & (BF_READ_TIMEOUT|BF_WRITE_TIMEOUT))) {
+		if (s->rep->flags & BF_READ_TIMEOUT) {
+			buffer_shutr(s->rep);
+			s->rep->cons->shutr(s->rep->prod);
+		}
+		if (s->rep->flags & BF_WRITE_TIMEOUT) {
+			buffer_shutw(s->rep);
+			s->rep->cons->shutw(s->rep->cons);
+		}
 	}
 
 	/* 2: Check if we need to close the write side. This can only happen
@@ -1067,9 +1052,14 @@
 
 	/* 3: When a server-side connection is released, we have to
 	 * count it and check for pending connections on this server.
+	 * FIXME: the test below is not accurate. An audit is needed
+	 * to find all uncaught transitions. We need a way to ensure
+	 * that shutdowns called right after connect() after TAR will
+	 * correctly be caught for instance. In fact we need a way to
+	 * track when the connection is assigned to the server.
 	 */
 	if (unlikely(s->req->cons->state == SI_ST_CLO &&
-		     s->req->cons->prev_state == SI_ST_EST)) {
+		     (s->req->cons->prev_state == SI_ST_EST || s->req->cons->prev_state == SI_ST_CON))) {
 		/* Count server-side errors (but not timeouts). */
 		if (s->req->flags & BF_WRITE_ERROR) {
 			s->be->failed_resp++;
@@ -1128,7 +1118,8 @@
 		if (s->req->cons->state != SI_ST_CLO) {
 			if (((rpf_srv ^ s->rep->flags) & BF_MASK_INTERFACE_I) ||
 			    ((rqf_srv ^ s->req->flags) & BF_MASK_INTERFACE_O)) {
-				if (s->req->cons->state == SI_ST_INI && s->req->flags & BF_WRITE_ENA) {
+				if (s->req->cons->state == SI_ST_INI &&
+				    (s->req->flags & (BF_WRITE_ENA|BF_SHUTW|BF_SHUTW_NOW)) == BF_WRITE_ENA) {
 					s->req->cons->state = SI_ST_REQ;
 					do {
 						sess_prepare_conn_req(s, &s->si[1]);
@@ -1282,7 +1273,7 @@
 		s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT;
 		s->si[0].prev_state = s->si[0].state;
 		s->si[1].prev_state = s->si[1].state;
-		s->si[0].flags = s->si[1].flags = 0;
+		s->si[0].flags = s->si[1].flags = SI_FL_NONE;
 
 		/* Trick: if a request is being waiting for the server to respond,
 		 * and if we know the server can timeout, we don't want the timeout
@@ -3714,176 +3705,201 @@
 }
 
 
-/* Return 1 if the pending connection has failed AND should be retried,
- * otherwise zero. We may only come here in SI_ST_CON state, which means that
- * the socket's file descriptor is known.
+/* This function is called with (si->state == SI_ST_CON) meaning that a
+ * connection was attempted and that the file descriptor is already allocated.
+ * We must check for establishment, error and abort. Possible output states
+ * are SI_ST_EST (established), SI_ST_CER (error), SI_ST_CLO (abort), and
+ * SI_ST_CON (no change). The function returns 0 if it switches to SI_ST_CER,
+ * otherwise 1.
  */
-int tcp_connection_status(struct session *t)
+int sess_update_st_con_tcp(struct session *s, struct stream_interface *si)
 {
-	struct buffer *req = t->req;
-	struct buffer *rep = t->rep;
-	int conn_err = 0;
+	struct buffer *req = si->ob;
+	struct buffer *rep = si->ib;
 
 	DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d, fds=%d\n",
 		now_ms, __FUNCTION__,
-		cli_stnames[t->cli_state],
+		cli_stnames[s->cli_state],
 		rep->rex, req->wex,
 		req->flags, rep->flags,
 		req->l, rep->l,
-		fdtab[req->cons->fd].state);
+		fdtab[si->fd].state);
 
-	if ((req->flags & BF_SHUTW_NOW) ||
-	    (rep->flags & BF_SHUTW) ||
-	    ((req->flags & BF_SHUTR) && /* FIXME: this should not prevent a connection from establishing */
-	     ((req->flags & BF_EMPTY && !(req->flags & BF_WRITE_ACTIVITY)) ||
-	      t->be->options & PR_O_ABRT_CLOSE))) {
+
+	/* If we got an error, or if nothing happened and the connection timed
+	 * out, we must give up. The CER state handler will take care of retry
+	 * attempts and error reports.
+	 */
+	if (unlikely(si->flags & (SI_FL_EXP|SI_FL_ERR))) {
+		si->state = SI_ST_CER;
+		fd_delete(si->fd);
+
+		if (s->srv) {
+			s->srv->cur_sess--;
+			sess_change_server(s, NULL);
+			si->err_loc = s->srv;
+		}
+
+		if (si->err_type)
+			return 0;
+
+		if (si->flags & SI_FL_ERR)
+			si->err_type = SI_ET_CONN_ERR;
+		else
+			si->err_type = SI_ET_CONN_TO;
+		return 0;
+	}
+
+	/* OK, maybe we want to abort */
+	if (unlikely((req->flags & BF_SHUTW_NOW) ||
+		     (rep->flags & BF_SHUTW) ||
+		     ((req->flags & BF_SHUTR) && /* FIXME: this should not prevent a connection from establishing */
+		      ((req->flags & BF_EMPTY && !(req->flags & BF_WRITE_ACTIVITY)) ||
+		       s->be->options & PR_O_ABRT_CLOSE)))) {
 		/* give up */
-		trace_term(t, TT_HTTP_SRV_5);
 		req->wex = TICK_ETERNITY;
-		fd_delete(req->cons->fd);
-		if (t->srv) {
-			t->srv->cur_sess--;
-			sess_change_server(t, NULL);
+		fd_delete(si->fd);
+		if (s->srv) {
+			s->srv->cur_sess--;
+			sess_change_server(s, NULL);
 		}
 		buffer_shutw(req);
 		buffer_shutr(rep);
-		req->cons->state = SI_ST_CLO;
-		req->cons->err_type |= SI_ET_CONN_ABRT;
-		req->cons->err_loc  = t->srv;
-		return 0;
+		si->state = SI_ST_CLO;
+		si->err_type |= SI_ET_CONN_ABRT;
+		si->err_loc  = s->srv;
+		return 1;
 	}
 
-	/* check for timeouts and asynchronous connect errors */
-	if (fdtab[req->cons->fd].state == FD_STERROR) {
-		conn_err = SI_ET_CONN_ERR;
-		if (!req->cons->err_type)
-			req->cons->err_type = SI_ET_CONN_ERR;
-	}
-	else if (!(req->flags & BF_WRITE_ACTIVITY)) {
-		/* nothing happened, maybe we timed out */
-		if (req->flags & BF_WRITE_TIMEOUT) {
-			conn_err = SI_ET_CONN_TO;
-			if (!req->cons->err_type)
-				req->cons->err_type = SI_ET_CONN_TO;
+	/* we need to wait a bit more if there was no activity either */
+	if (!(req->flags & BF_WRITE_ACTIVITY))
+		return 1;
+
+	/* OK, this means that a connection succeeded */
+	s->logs.t_connect = tv_ms_elapsed(&s->logs.tv_accept, &now);
+	si->state    = SI_ST_EST;
+	si->err_type = SI_ET_NONE;
+	si->err_loc  = NULL;
+
+	if (req->flags & BF_EMPTY) {
+		EV_FD_CLR(si->fd, DIR_WR);
+		req->wex = TICK_ETERNITY;
+	} else {
+		EV_FD_SET(si->fd, DIR_WR);
+		req->wex = tick_add_ifset(now_ms, s->be->timeout.server);
+		if (tick_isset(req->wex)) {
+			/* FIXME: to prevent the server from expiring read
+			 * timeouts during writes, we refresh it. */
+			rep->rex = req->wex;
 		}
-		else
-			return 0; /* let's wait a bit more */
 	}
 
-	if (conn_err) {
-		fd_delete(req->cons->fd);
+	if (s->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */
+		if (!(rep->flags & BF_HIJACK)) {
+			EV_FD_SET(si->fd, DIR_RD);
+			rep->rex = tick_add_ifset(now_ms, s->be->timeout.server);
+		}
+		buffer_set_rlim(rep, BUFSIZE); /* no rewrite needed */
 
-		if (t->srv) {
-			t->srv->cur_sess--;
-			sess_change_server(t, NULL);
-			req->cons->err_loc  = t->srv;
+		/* if the user wants to log as soon as possible, without counting
+		 * bytes from the server, then this is the right moment. */
+		if (s->fe->to_log && !(s->logs.logwait & LW_BYTES)) {
+			s->logs.t_close = s->logs.t_connect; /* to get a valid end date */
+			tcp_sess_log(s);
+		}
+#ifdef CONFIG_HAP_TCPSPLICE
+		if ((s->fe->options & s->be->options) & PR_O_TCPSPLICE) {
+			/* TCP splicing supported by both FE and BE */
+			tcp_splice_splicefd(req->prod->fd, si->fd, 0);
 		}
+#endif
+	}
+	else {
+		rep->analysers |= AN_RTR_HTTP_HDR;
+		buffer_set_rlim(rep, BUFSIZE - MAXREWRITE); /* rewrite needed */
+		s->txn.rsp.msg_state = HTTP_MSG_RPBEFORE;
+		/* reset hdr_idx which was already initialized by the request.
+		 * right now, the http parser does it.
+		 * hdr_idx_init(&s->txn.hdr_idx);
+		 */
+	}
 
-		/* ensure that we have enough retries left */
-		t->conn_retries--;
-		if (t->conn_retries < 0) {
-			if (!t->req->cons->err_type) {
-				t->req->cons->err_type = SI_ET_CONN_ERR;
-				t->req->cons->err_loc = t->srv;
-			}
+	rep->flags |= BF_READ_ATTACHED; /* producer is now attached */
+	req->wex = TICK_ETERNITY;
+	return 1;
+}
 
-			if (t->srv)
-				t->srv->failed_conns++;
-			t->be->failed_conns++;
-			if (may_dequeue_tasks(t->srv, t->be))
-				process_srv_queue(t->srv);
-			req->cons->state = SI_ST_CLO;
-			return 0;
+
+/* This function is called with (si->state == SI_ST_CER) meaning that a
+ * previous connection attempt has failed and that the file descriptor
+ * has already been released. Possible causes include asynchronous error
+ * notification and time out. Possible output states are SI_ST_CLO when
+ * retries are exhausted, SI_ST_TAR when a delay is wanted before a new
+ * connection attempt, SI_ST_ASS when it's wise to retry on the same server,
+ * and SI_ST_REQ when an immediate redispatch is wanted. The buffers are
+ * marked as in error state. It returns 0.
+ */
+int sess_update_st_cer(struct session *s, struct stream_interface *si)
+{
+	/* ensure that we have enough retries left */
+	s->conn_retries--;
+	if (s->conn_retries < 0) {
+		if (!si->err_type) {
+			si->err_type = SI_ET_CONN_ERR;
+			si->err_loc = s->srv;
 		}
 
-		/* If the "redispatch" option is set on the backend, we are allowed to
-		 * retry on another server for the last retry. In order to achieve this,
-		 * we must mark the session unassigned, and eventually clear the DIRECT
-		 * bit to ignore any persistence cookie. We won't count a retry nor a
-		 * redispatch yet, because this will depend on what server is selected.
-		 */
-		if (t->srv && t->conn_retries == 0 && t->be->options & PR_O_REDISP) {
-			if (may_dequeue_tasks(t->srv, t->be))
-				process_srv_queue(t->srv);
+		if (s->srv)
+			s->srv->failed_conns++;
+		s->be->failed_conns++;
+		if (may_dequeue_tasks(s->srv, s->be))
+			process_srv_queue(s->srv);
 
-			t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
-			t->prev_srv = t->srv;
-			req->cons->state = SI_ST_REQ;
-		} else {
-			if (t->srv)
-				t->srv->retries++;
-			t->be->retries++;
-			req->cons->state = SI_ST_ASS;
-		}
+		buffer_shutw(si->ob);
+		si->ob->flags |= BF_WRITE_ERROR;
 
-		if (conn_err == SI_ET_CONN_ERR) {
-			/* The error was an immediate connection error, and we
-			 * will likely have to retry connecting to the same
-			 * server, most likely leading to the same result. To
-			 * avoid this, we wait one second before retrying.
-			 */
-			req->cons->state = SI_ST_TAR;
-			req->cons->exp = tick_add(now_ms, MS_TO_TICKS(1000));
-			return 0;
-		}
+		buffer_shutr(si->ib);
+		si->ib->flags |= BF_READ_ERROR;
 
-		/* We'll rely on the caller to try to get a connection again */
-		return 1;
+		si->state = SI_ST_CLO;
+		return 0;
 	}
-	else {
-		/* no error and write OK : connection succeeded */
-		t->logs.t_connect = tv_ms_elapsed(&t->logs.tv_accept, &now);
-		req->cons->state    = SI_ST_EST;
-		req->cons->err_type = SI_ET_NONE;
-		req->cons->err_loc  = NULL;
 
-		if (req->flags & BF_EMPTY) {
-			EV_FD_CLR(req->cons->fd, DIR_WR);
-			req->wex = TICK_ETERNITY;
-		} else {
-			EV_FD_SET(req->cons->fd, DIR_WR);
-			req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
-			if (tick_isset(req->wex)) {
-				/* FIXME: to prevent the server from expiring read timeouts during writes,
-				 * we refresh it. */
-				rep->rex = req->wex;
-			}
-		}
+	/* If the "redispatch" option is set on the backend, we are allowed to
+	 * retry on another server for the last retry. In order to achieve this,
+	 * we must mark the session unassigned, and eventually clear the DIRECT
+	 * bit to ignore any persistence cookie. We won't count a retry nor a
+	 * redispatch yet, because this will depend on what server is selected.
+	 */
+	if (s->srv && s->conn_retries == 0 && s->be->options & PR_O_REDISP) {
+		if (may_dequeue_tasks(s->srv, s->be))
+			process_srv_queue(s->srv);
 
-		if (t->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */
-			if (!(rep->flags & BF_HIJACK)) {
-				EV_FD_SET(req->cons->fd, DIR_RD);
-				rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
-			}
-			buffer_set_rlim(rep, BUFSIZE); /* no rewrite needed */
+		s->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
+		s->prev_srv = s->srv;
+		si->state = SI_ST_REQ;
+	} else {
+		if (s->srv)
+			s->srv->retries++;
+		s->be->retries++;
+		si->state = SI_ST_ASS;
+	}
 
-			/* if the user wants to log as soon as possible, without counting
-			   bytes from the server, then this is the right moment. */
-			if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) {
-				t->logs.t_close = t->logs.t_connect; /* to get a valid end date */
-				tcp_sess_log(t);
-			}
-#ifdef CONFIG_HAP_TCPSPLICE
-			if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) {
-				/* TCP splicing supported by both FE and BE */
-				tcp_splice_splicefd(req->prod->fd, req->cons->fd, 0);
-			}
-#endif
-		}
-		else {
-			rep->analysers |= AN_RTR_HTTP_HDR;
-			buffer_set_rlim(rep, BUFSIZE - MAXREWRITE); /* rewrite needed */
-			t->txn.rsp.msg_state = HTTP_MSG_RPBEFORE;
-			/* reset hdr_idx which was already initialized by the request.
-			 * right now, the http parser does it.
-			 * hdr_idx_init(&t->txn.hdr_idx);
-			 */
-		}
+	if (si->flags & SI_FL_ERR) {
+		/* The error was an asynchronous connection error, and we will
+		 * likely have to retry connecting to the same server, most
+		 * likely leading to the same result. To avoid this, we wait
+		 * one second before retrying.
+		 */
 
-		rep->flags |= BF_READ_ATTACHED; /* producer is now attached */
-		req->wex = TICK_ETERNITY;
+		if (!si->err_type)
+			si->err_type = SI_ET_CONN_ERR;
+
+		si->state = SI_ST_TAR;
+		si->exp = tick_add(now_ms, MS_TO_TICKS(1000));
 		return 0;
 	}
+	return 0;
 }
 
 
diff --git a/src/stream_interface.c b/src/stream_interface.c
new file mode 100644
index 0000000..8f075ba
--- /dev/null
+++ b/src/stream_interface.c
@@ -0,0 +1,67 @@
+/*
+ * Functions managing stream_interface structures
+ *
+ * Copyright 2000-2008 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/standard.h>
+#include <common/ticks.h>
+#include <common/time.h>
+
+#include <proto/buffers.h>
+#include <proto/client.h>
+#include <proto/fd.h>
+#include <proto/stream_sock.h>
+#include <proto/task.h>
+
+/*
+ * This function only has to be called once after a wakeup event in case of
+ * suspected timeout. It controls the stream interface timeouts and sets
+ * si->flags accordingly. It does NOT close anything, as this timeout may
+ * be used for any purpose. It returns 1 if the timeout fired, otherwise
+ * zero.
+ */
+int stream_int_check_timeouts(struct stream_interface *si)
+{
+	if (tick_is_expired(si->exp, now_ms)) {
+		si->flags |= SI_FL_EXP;
+		return 1;
+	}
+	return 0;
+}
+
+void stream_int_report_error(struct stream_interface *si)
+{
+	if (!si->err_type)
+		si->err_type = SI_ET_DATA_ERR;
+
+	buffer_shutw(si->ob);
+	si->ob->flags |= BF_WRITE_ERROR;
+	buffer_shutr(si->ib);
+	si->ib->flags |= BF_READ_ERROR;
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/src/stream_sock.c b/src/stream_sock.c
index 3418e98..4de51ee 100644
--- a/src/stream_sock.c
+++ b/src/stream_sock.c
@@ -259,31 +259,22 @@
 	goto out_wakeup;
 
  out_error:
-	/* There was an error. we must wakeup the task. No need to clear
-	 * the events, the task will do it.
+	/* Read error on the file descriptor. We mark the FD as STERROR so
+	 * that we don't use it anymore. The error is reported to the stream
+	 * interface which will take proper action. We must not perturbate the
+	 * buffer because the stream interface wants to ensure transparent
+	 * connection retries.
 	 */
+
 	fdtab[fd].state = FD_STERROR;
 	fdtab[fd].ev &= ~FD_POLL_STICKY;
-	b->rex = TICK_ETERNITY;
-
-	/* Read error on the file descriptor. We close the FD and set
-	 * the error on both buffers.
-	 * Note: right now we only support connected sockets.
-	 */
-	if (si->state != SI_ST_EST)
-		goto out_wakeup;
-
-	if (!si->err_type)
-		si->err_type = SI_ET_DATA_ERR;
-
-	buffer_shutr(b);
-	b->flags |= BF_READ_ERROR;
-	buffer_shutw(si->ob);
-	si->ob->flags |= BF_WRITE_ERROR;
+	si->flags |= SI_FL_ERR;
+	goto wakeup_return;
 
  do_close_and_return:
-	fd_delete(fd);
 	si->state = SI_ST_CLO;
+	fd_delete(fd);
+ wakeup_return:
 	task_wakeup(si->owner, TASK_WOKEN_IO);
 	return 1;
 }
@@ -457,29 +448,22 @@
 	return retval;
 
  out_error:
-	/* There was an error. we must wakeup the task. No need to clear
-	 * the events, the task will do it.
+	/* Write error on the file descriptor. We mark the FD as STERROR so
+	 * that we don't use it anymore. The error is reported to the stream
+	 * interface which will take proper action. We must not perturbate the
+	 * buffer because the stream interface wants to ensure transparent
+	 * connection retries.
 	 */
+
 	fdtab[fd].state = FD_STERROR;
 	fdtab[fd].ev &= ~FD_POLL_STICKY;
-	b->wex = TICK_ETERNITY;
-	/* Read error on the file descriptor. We close the FD and set
-	 * the error on both buffers.
-	 * Note: right now we only support connected sockets.
-	 */
-	if (si->state != SI_ST_EST)
-		goto out_wakeup;
-
-	if (!si->err_type)
-		si->err_type = SI_ET_DATA_ERR;
+	si->flags |= SI_FL_ERR;
+	goto wakeup_return;
 
-	buffer_shutw(b);
-	b->flags |= BF_WRITE_ERROR;
-	buffer_shutr(si->ib);
-	si->ib->flags |= BF_READ_ERROR;
  do_close_and_return:
-	fd_delete(fd);
 	si->state = SI_ST_CLO;
+	fd_delete(fd);
+ wakeup_return:
 	task_wakeup(si->owner, TASK_WOKEN_IO);
 	return 1;
 }
@@ -524,7 +508,7 @@
 	if (si->state != SI_ST_EST && si->state != SI_ST_CON)
 		return 0;
 
-	if (si->ib->flags & BF_SHUTW) {
+	if (si->ob->flags & BF_SHUTW) {
 		fd_delete(si->fd);
 		si->state = SI_ST_CLO;
 		return 1;
@@ -534,22 +518,6 @@
 }
 
 /*
- * This function only has to be called once after a wakeup event in case of
- * suspected timeout. It controls the stream interface timeouts and sets
- * si->flags accordingly. It does NOT close anything, as this timeout may
- * be used for any purpose. It returns 1 if the timeout fired, otherwise
- * zero.
- */
-int stream_sock_check_timeouts(struct stream_interface *si)
-{
-	if (tick_is_expired(si->exp, now_ms)) {
-		si->flags |= SI_FL_EXP;
-		return 1;
-	}
-	return 0;
-}
-
-/*
  * Manages a stream_sock connection during its data phase. The buffers are
  * examined for various cases of shutdown, then file descriptor and buffers'
  * flags are updated accordingly.