diff --git a/doc/configuration.txt b/doc/configuration.txt index 1dd76bc36..dce64f07d 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 diff --git a/include/types/proxy.h b/include/types/proxy.h index 2a549ccf6..8b98c4a23 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -117,6 +117,8 @@ #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 { diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h index bb4a9e3b5..a4851ce7c 100644 --- a/include/types/stream_interface.h +++ b/include/types/stream_interface.h @@ -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 { diff --git a/src/cfgparse.c b/src/cfgparse.c index a5127d112..605114426 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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 } }; diff --git a/src/client.c b/src/client.c index 3e156eb78..6d367c7cf 100644 --- a/src/client.c +++ b/src/client.c @@ -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; diff --git a/src/proto_http.c b/src/proto_http.c index 10fb71cfd..07bf629c9 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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; diff --git a/src/proto_uxst.c b/src/proto_uxst.c index b4685d596..f3540722a 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -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; diff --git a/src/stream_sock.c b/src/stream_sock.c index 0ee339c0c..b7a64efcb 100644 --- a/src/stream_sock.c +++ b/src/stream_sock.c @@ -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); }