]> 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:01:18 +0000 (22:01 +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.

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

index dd536280e765c342b6f1031cd7fd1034948471a2..254382bba2523bdfb5487b017b0614e206d109cd 100644 (file)
@@ -747,6 +747,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-health-     X          -         X         X
             checks
 [no] option log-separate-
@@ -2477,6 +2479,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-health-checks
 no option log-health-checks
   Enable or disable logging of health checks
index 6af0a24ce19c17df5303343d12bb785b3046987b..ed632a26b3d892683ee893d0271ea1720fc6b4b8 100644 (file)
 #define PR_O2_RDPC_PRST        0x00000200      /* Actvate rdp cookie analyser */
 #define PR_O2_CLFLOG   0x00000400      /* log into clf format */
 #define PR_O2_LOGHCHKS 0x00000800      /* log health checks */
+#define PR_O2_INDEPSTR 0x00001000      /* independant streams, don't update rex on write */
 
 struct error_snapshot {
        struct timeval when;            /* date of this event, (tv_sec == 0) means "never" */
index e1e36468d0ea051427762b4542055253808d0c2f..a693497807ebe6935670d9938e465600c0e4c0c9 100644 (file)
@@ -69,6 +69,7 @@ enum {
        SI_FL_WAIT_DATA  = 0x0008,  /* waiting for more data to send */
        SI_FL_CAP_SPLTCP = 0x0010,  /* splicing possible from/to TCP */
        SI_FL_DONT_WAKE  = 0x0020,  /* resync in progress, don't wake up */
+       SI_FL_INDEP_STR  = 0x0040,  /* independant streams = don't update rex on write */
 };
 
 #define SI_FL_CAP_SPLICE (SI_FL_CAP_SPLTCP)
index 7bb94b83637be0b21447b99580c80890c0fdf4e0..ebb07139846a1bae9f6292f8b1faf68c63110ce7 100644 (file)
@@ -141,6 +141,7 @@ static const struct cfg_opt cfg_opts2[] =
        { "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 }
 };
 
index 63c5bd29e6435e2fdfe31b93a06c2b66ced35aba..ba58182ed2bac9df916794541d38f087c69cdca3 100644 (file)
@@ -200,6 +200,8 @@ int event_accept(int fd) {
                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 @@ 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 695f407ea98719b5e6e143d415740c2e4808125a..5c4ac4d922d7609a6ff06ae91f25f2d4e8d3fc5b 100644 (file)
@@ -455,6 +455,8 @@ int uxst_event_accept(int fd) {
                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 @@ 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;
+
                stream_int_register_handler(&s->si[1], stats_io_handler);
                s->si[1].private = s;
                s->si[1].st0 = s->si[1].st1 = 0;
index 05ed05accaa472b2f0000cb1bfc49c2f45dcd2c4..8683e741ae5f2b2aca0934ce387a7ed18501411a 100644 (file)
@@ -653,6 +653,10 @@ int session_set_backend(struct session *s, struct proxy *be)
        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;
index b425419436e06a9239bbc5db4e8bbb482b7eb937..6f026860f56fc47edce982f2d6684b14c707f1dc 100644 (file)
@@ -123,13 +123,17 @@ void stream_int_update_embedded(struct stream_interface *si)
        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);
 
index ed2af6232c891fee29267bf5e6615d0a0706ccc2..c09c8a2669b30d165c5219c7f950340b369590a0 100644 (file)
@@ -764,12 +764,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);
                }
@@ -943,11 +945,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);
                                }
@@ -1069,12 +1072,14 @@ void stream_sock_chk_snd(struct stream_interface *si)
                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);
                }