diff --git a/doc/configuration.txt b/doc/configuration.txt index dd536280e..254382bba 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 diff --git a/include/types/proxy.h b/include/types/proxy.h index 6af0a24ce..ed632a26b 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -119,6 +119,7 @@ #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" */ diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h index e1e36468d..a69349780 100644 --- a/include/types/stream_interface.h +++ b/include/types/stream_interface.h @@ -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) diff --git a/src/cfgparse.c b/src/cfgparse.c index 7bb94b836..ebb071398 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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 } }; diff --git a/src/client.c b/src/client.c index 63c5bd29e..ba58182ed 100644 --- a/src/client.c +++ b/src/client.c @@ -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; diff --git a/src/proto_uxst.c b/src/proto_uxst.c index 695f407ea..5c4ac4d92 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -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; diff --git a/src/proxy.c b/src/proxy.c index 05ed05acc..8683e741a 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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; diff --git a/src/stream_interface.c b/src/stream_interface.c index b42541943..6f026860f 100644 --- a/src/stream_interface.c +++ b/src/stream_interface.c @@ -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); diff --git a/src/stream_sock.c b/src/stream_sock.c index ed2af6232..c09c8a266 100644 --- a/src/stream_sock.c +++ b/src/stream_sock.c @@ -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); }