[MEDIUM] new option "independant-streams" to stop updating read timeout on writes

By default, when data is sent over a socket, both the write timeout and the
read timeout for that socket are refreshed, because we consider that there is
activity on that socket, and we have no other means of guessing if we should
receive data or not.

While this default behaviour is desirable for almost all applications, there
exists a situation where it is desirable to disable it, and only refresh the
read timeout if there are incoming data. This happens on sessions with large
timeouts and low amounts of exchanged data such as telnet session. If the
server suddenly disappears, the output data accumulates in the system's
socket buffers, both timeouts are correctly refreshed, and there is no way
to know the server does not receive them, so we don't timeout. However, when
the underlying protocol always echoes sent data, it would be enough by itself
to detect the issue using the read timeout. Note that this problem does not
happen with more verbose protocols because data won't accumulate long in the
socket buffers.

When this option is set on the frontend, it will disable read timeout updates
on data sent to the client. There probably is little use of this case. When
the option is set on the backend, it will disable read timeout updates on
data sent to the server. Doing so will typically break large HTTP posts from
slow lines, so use it with caution.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 7bb94b8..ebb0713 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -141,6 +141,7 @@
 	{ "log-health-checks",            PR_O2_LOGHCHKS,  PR_CAP_BE, 0 },
 	{ "tcp-smart-accept",             PR_O2_SMARTACC,  PR_CAP_FE, 0 },
 	{ "tcp-smart-connect",            PR_O2_SMARTCON,  PR_CAP_BE, 0 },
+	{ "independant-streams",          PR_O2_INDEPSTR,  PR_CAP_FE|PR_CAP_BE, 0 },
 	{ NULL, 0, 0, 0 }
 };
 
diff --git a/src/client.c b/src/client.c
index 63c5bd2..ba58182 100644
--- a/src/client.c
+++ b/src/client.c
@@ -200,6 +200,8 @@
 		s->si[0].iohandler = NULL;
 		s->si[0].fd = cfd;
 		s->si[0].flags = SI_FL_NONE | SI_FL_CAP_SPLTCP; /* TCP splicing capable */
+		if (s->fe->options2 & PR_O2_INDEPSTR)
+			s->si[0].flags |= SI_FL_INDEP_STR;
 		s->si[0].exp = TICK_ETERNITY;
 
 		s->si[1].state = s->si[1].prev_state = SI_ST_INI;
@@ -216,6 +218,8 @@
 		s->si[1].exp = TICK_ETERNITY;
 		s->si[1].fd = -1; /* just to help with debugging */
 		s->si[1].flags = SI_FL_NONE;
+		if (s->be->options2 & PR_O2_INDEPSTR)
+			s->si[1].flags |= SI_FL_INDEP_STR;
 
 		s->srv = s->prev_srv = s->srv_conn = NULL;
 		s->pend_pos = NULL;
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index 695f407..5c4ac4d 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -455,6 +455,8 @@
 		s->si[0].iohandler = NULL;
 		s->si[0].fd = cfd;
 		s->si[0].flags = SI_FL_NONE;
+		if (s->fe->options2 & PR_O2_INDEPSTR)
+			s->si[0].flags |= SI_FL_INDEP_STR;
 		s->si[0].exp = TICK_ETERNITY;
 
 		s->si[1].state = s->si[1].prev_state = SI_ST_INI;
@@ -464,6 +466,9 @@
 		s->si[1].exp = TICK_ETERNITY;
 		s->si[1].fd = -1; /* just to help with debugging */
 		s->si[1].flags = SI_FL_NONE;
+		if (s->be->options2 & PR_O2_INDEPSTR)
+			s->si[1].flags |= SI_FL_INDEP_STR;
+
 		stream_int_register_handler(&s->si[1], stats_io_handler);
 		s->si[1].private = s;
 		s->si[1].st0 = s->si[1].st1 = 0;
diff --git a/src/proxy.c b/src/proxy.c
index 05ed05a..8683e74 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -653,6 +653,10 @@
 	s->rep->rto = s->req->wto = be->timeout.server;
 	s->req->cto = be->timeout.connect;
 	s->conn_retries = be->conn_retries;
+	s->si[1].flags &= ~SI_FL_INDEP_STR;
+	if (be->options2 & PR_O2_INDEPSTR)
+		s->si[1].flags |= SI_FL_INDEP_STR;
+
 	if (be->options2 & PR_O2_RSPBUG_OK)
 		s->txn.rsp.err_pos = -1; /* let buggy responses pass */
 	s->flags |= SN_BE_ASSIGNED;
diff --git a/src/stream_interface.c b/src/stream_interface.c
index b425419..6f02686 100644
--- a/src/stream_interface.c
+++ b/src/stream_interface.c
@@ -123,13 +123,17 @@
 	if ((si->ib->flags & (BF_FULL|BF_SHUTR)) == BF_FULL)
 		si->flags |= SI_FL_WAIT_ROOM;
 
-	if (si->ob->flags & BF_WRITE_ACTIVITY || si->ib->flags & BF_READ_ACTIVITY) {
-		if (tick_isset(si->ib->rex))
-			si->ib->rex = tick_add_ifset(now_ms, si->ib->rto);
+	if (si->ob->flags & BF_WRITE_ACTIVITY) {
 		if (tick_isset(si->ob->wex))
 			si->ob->wex = tick_add_ifset(now_ms, si->ob->wto);
 	}
 
+	if (si->ib->flags & BF_READ_ACTIVITY ||
+	    (si->ob->flags & BF_WRITE_ACTIVITY && !(si->flags & SI_FL_INDEP_STR))) {
+		if (tick_isset(si->ib->rex))
+			si->ib->rex = tick_add_ifset(now_ms, si->ib->rto);
+	}
+
 	if (si->ob->flags & BF_WRITE_PARTIAL)
 		si->ob->prod->chk_rcv(si->ob->prod);
 
diff --git a/src/stream_sock.c b/src/stream_sock.c
index ed2af62..c09c8a2 100644
--- a/src/stream_sock.c
+++ b/src/stream_sock.c
@@ -764,12 +764,14 @@
 			b->wex = tick_add_ifset(now_ms, b->wto);
 
 	out_wakeup:
-		if (tick_isset(si->ib->rex)) {
+		if (tick_isset(si->ib->rex) && !(si->flags & SI_FL_INDEP_STR)) {
 			/* Note: to prevent the client from expiring read timeouts
-			 * during writes, we refresh it. A better solution would be
-			 * to merge read+write timeouts into a unique one, although
-			 * that needs some study particularly on full-duplex TCP
-			 * connections.
+			 * during writes, we refresh it. We only do this if the
+			 * interface is not configured for "independant streams",
+			 * because for some applications it's better not to do this,
+			 * for instance when continuously exchanging small amounts
+			 * of data which can full the socket buffers long before a
+			 * write timeout is detected.
 			 */
 			si->ib->rex = tick_add_ifset(now_ms, si->ib->rto);
 		}
@@ -943,11 +945,12 @@
 			EV_FD_COND_S(fd, DIR_WR);
 			if (!tick_isset(ob->wex) || ob->flags & BF_WRITE_ACTIVITY) {
 				ob->wex = tick_add_ifset(now_ms, ob->wto);
-				if (tick_isset(ib->rex)) {
+				if (tick_isset(ib->rex) && !(si->flags & SI_FL_INDEP_STR)) {
 					/* Note: depending on the protocol, we don't know if we're waiting
 					 * for incoming data or not. So in order to prevent the socket from
 					 * expiring read timeouts during writes, we refresh the read timeout,
-					 * except if it was already infinite.
+					 * except if it was already infinite or if we have explicitly setup
+					 * independant streams.
 					 */
 					ib->rex = tick_add_ifset(now_ms, ib->rto);
 				}
@@ -1069,12 +1072,14 @@
 		if ((ob->flags & (BF_OUT_EMPTY|BF_SHUTW|BF_WRITE_PARTIAL)) == BF_WRITE_PARTIAL)
 			ob->wex = tick_add_ifset(now_ms, ob->wto);
 
-		if (tick_isset(si->ib->rex)) {
+		if (tick_isset(si->ib->rex) && !(si->flags & SI_FL_INDEP_STR)) {
 			/* Note: to prevent the client from expiring read timeouts
-			 * during writes, we refresh it. A better solution would be
-			 * to merge read+write timeouts into a unique one, although
-			 * that needs some study particularly on full-duplex TCP
-			 * connections.
+			 * during writes, we refresh it. We only do this if the
+			 * interface is not configured for "independant streams",
+			 * because for some applications it's better not to do this,
+			 * for instance when continuously exchanging small amounts
+			 * of data which can full the socket buffers long before a
+			 * write timeout is detected.
 			 */
 			si->ib->rex = tick_add_ifset(now_ms, si->ib->rto);
 		}