]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] new option "independant-streams" to stop updating read timeout on writes
authorWilly Tarreau <w@1wt.eu>
Sat, 3 Oct 2009 20:01:18 +0000 (22:01 +0200)
committerWilly Tarreau <w@1wt.eu>
Sat, 3 Oct 2009 20:11:50 +0000 (22:11 +0200)
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.

(cherry picked from commit f27b5ea8dc615bd2a9ffaba90ba3dda66567dbc4)

doc/configuration.txt
include/types/proxy.h
include/types/stream_interface.h
src/cfgparse.c
src/client.c
src/proto_http.c
src/proto_uxst.c
src/stream_sock.c

index 1dd76bc36dd12001cdbee5f0a5055206e53e0913..dce64f07d5a420e28d191df478b53157c87a0331 100644 (file)
@@ -721,6 +721,8 @@ option httpchk              X          -         X         X
 [no] option httpclose       X          X         X         X
 option httplog              X          X         X         X
 [no] option http_proxy      X          X         X         X
+[no] option independant-
+            streams         X          X         X         X
 [no] option log-separate-
             errors          X          X         X         -
 [no] option logasap         X          X         X         -
@@ -2400,6 +2402,39 @@ no option http_proxy
   See also : "option httpclose"
 
 
+option independant-streams
+no option independant-streams
+  Enable or disable independant timeout processing for both directions
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    yes   |   yes  |  yes
+  Arguments : none
+
+  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.
+
+  See also : "timeout client" and "timeout server"
+
+
 option log-separate-errors
 no option log-separate-errors
   Change log level for non-completely successful connections
index 2a549ccf692c1f1c3052424df340c63ffb05e7dc..8b98c4a23466ffb043bb1cfc0b9bb52a5731afa9 100644 (file)
 #define PR_O2_RSPBUG_OK        0x00000010      /* let buggy responses pass through */
 #define PR_O2_NOLOGNORM        0x00000020      /* don't log normal traffic, only errors and retries */
 #define PR_O2_LOGERRORS        0x00000040      /* log errors and retries at level LOG_ERR */
+/* 0x80..0x800 already used in 1.4 */
+#define PR_O2_INDEPSTR 0x00001000      /* independant streams, don't update rex on write */
 
 /* This structure is used to apply fast weighted round robin on a server group */
 struct fwrr_group {
index bb4a9e3b5f2f20867d0a1edd0f8053af55bc267c..a4851ce7c0f1bd397eab68a81d0d5d1753a5100f 100644 (file)
@@ -67,6 +67,8 @@ enum {
        SI_FL_ERR        = 0x0002,  /* a non-recoverable error has occurred */
        SI_FL_WAIT_ROOM  = 0x0004,  /* waiting for space to store incoming data */
        SI_FL_WAIT_DATA  = 0x0008,  /* waiting for more data to send */
+       /* 0x10..0x20 already used in 1.4 */
+       SI_FL_INDEP_STR  = 0x0040,  /* independant streams = don't update rex on write */
 };
 
 struct stream_interface {
index a5127d1126ac68252fa1d9d151e8571204e7ab69..6051144262360ac15e4d4d82a99af119d8b3f676 100644 (file)
@@ -129,6 +129,7 @@ static const struct cfg_opt cfg_opts2[] =
        { "accept-invalid-http-response", PR_O2_RSPBUG_OK, PR_CAP_BE, 0 },
        { "dontlog-normal",               PR_O2_NOLOGNORM, PR_CAP_FE, 0 },
        { "log-separate-errors",          PR_O2_LOGERRORS, PR_CAP_FE, 0 },
+       { "independant-streams",          PR_O2_INDEPSTR,  PR_CAP_FE|PR_CAP_BE, 0 },
        { NULL, 0, 0, 0 }
 };
 
index 3e156eb786c73aad5ffe82a8f1ba1a4b28e55058..6d367c7cfee627e3e0d43869378b8072aa7f5eaf 100644 (file)
@@ -191,6 +191,8 @@ int event_accept(int fd) {
                s->si[0].chk_snd = stream_sock_chk_snd;
                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;
@@ -204,6 +206,8 @@ int event_accept(int fd) {
                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;
index 10fb71cfd08f8f734d7b6de22cae62a2937f6793..07bf629c9ea9e47ed264c4b6eb39156038031aea 100644 (file)
@@ -2060,6 +2060,11 @@ int http_process_request(struct session *s, struct buffer *req)
                                        s->rep->rto = s->req->wto = s->be->timeout.server;
                                        s->req->cto = s->be->timeout.connect;
                                        s->conn_retries = s->be->conn_retries;
+
+                                       s->si[1].flags &= ~SI_FL_INDEP_STR;
+                                       if (s->be->options2 & PR_O2_INDEPSTR)
+                                               s->si[1].flags |= SI_FL_INDEP_STR;
+
                                        if (s->be->options2 & PR_O2_RSPBUG_OK)
                                                s->txn.rsp.err_pos = -1; /* let buggy responses pass */
                                        s->flags |= SN_BE_ASSIGNED;
@@ -2083,6 +2088,11 @@ int http_process_request(struct session *s, struct buffer *req)
                        s->rep->rto = s->req->wto = s->be->timeout.server;
                        s->req->cto = s->be->timeout.connect;
                        s->conn_retries = s->be->conn_retries;
+
+                       s->si[1].flags &= ~SI_FL_INDEP_STR;
+                       if (s->be->options2 & PR_O2_INDEPSTR)
+                               s->si[1].flags |= SI_FL_INDEP_STR;
+
                        if (s->be->options2 & PR_O2_RSPBUG_OK)
                                s->txn.rsp.err_pos = -1; /* let buggy responses pass */
                        s->flags |= SN_BE_ASSIGNED;
index b4685d59676acdcd3d6d5f9756135e7a2af3091d..f3540722aee91f21eab73bd6f4ddb7cf12066660 100644 (file)
@@ -466,6 +466,8 @@ int uxst_event_accept(int fd) {
                s->si[0].chk_snd = stream_sock_chk_snd;
                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;
@@ -479,6 +481,8 @@ int uxst_event_accept(int fd) {
                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;
index 0ee339c0c0da4be34a33ad0019652f814743d676..b7a64efcb6a3f70d1ee86441d2114cec215155d8 100644 (file)
@@ -729,12 +729,14 @@ int stream_sock_write(int fd)
                        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);
                }
@@ -908,11 +910,12 @@ void stream_sock_data_finish(struct stream_interface *si)
                        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);
                                }
@@ -1036,12 +1039,14 @@ void stream_sock_chk_snd(struct stream_interface *si)
                    (ob->flags & (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);
                }