]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: haterm: Properly handle client timeout
authorChristopher Faulet <cfaulet@haproxy.com>
Tue, 5 May 2026 16:13:21 +0000 (18:13 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Tue, 5 May 2026 17:03:31 +0000 (19:03 +0200)
No client timeout was set with haterm. It could be an issue with
unresponsive clients. So the I/O timeout of the SC is initialized to the
frontend client timeout when the hstream is created. Then a read activity is
reported when data are received. This read activity is used to set an
expiration date on the hstream task and test it when the hstream is woken up
with TASK_WOKEN_TIMER reason.

When a client timeout is detected, the hstream try to send a 408 and report
an error.

src/haterm.c

index 386730f880e21fd1ca7218a1ef3da2914f46ff23..0c9ffe17ec77fa23a7c6a6805fa51e3b55f9842a 100644 (file)
@@ -281,6 +281,7 @@ static int hstream_htx_buf_rcv(struct connection *conn, struct hstream *hs)
  end_recv:
        if (cur_read) {
                hs->req_body = ((hs->req_body < cur_read) ? 0 : hs->req_body - cur_read);
+               sc_ep_report_read_activity(hs->sc);
        }
 
        if (((conn->flags & CO_FL_ERROR) || sc_ep_test(hs->sc, SE_FL_ERROR))) {
@@ -879,6 +880,23 @@ static inline int hstream_sl_hdrs_htx_buf_snd(struct hstream *hs,
        return ret;
 }
 
+/* Send an error to the client, if possible */
+static inline void hstream_send_error(struct hstream *hs, struct connection *conn, struct buffer *errmsg)
+{
+       /* Do nothing is the response headers were already sent */
+       if (hs->flags & HS_ST_HTTP_RESP_SL_SENT)
+               return;
+
+       if (!hstream_get_buf(hs, &hs->res)) {
+               TRACE_ERROR("could not allocate response buffer", HS_EV_HSTRM_RESP, hs);
+               return;
+       }
+
+       b_set_data(&hs->res, b_data(errmsg));
+       memcpy(b_orig(&hs->res), b_head(errmsg), b_data(errmsg));
+       hstream_htx_buf_snd(conn, hs);
+}
+
 /* Must be called before sending to determine if the body request must be
  * drained asap before sending. Return 1 if this is the case, 0 if not.
  * This is the case by default before sending the response except if
@@ -929,6 +947,16 @@ static struct task *process_hstream(struct task *t, void *context, unsigned int
                TRACE_STATE("waiting before responding", HS_EV_HSTRM_IO_CB, hs);
                goto leave;
        }
+       if (state & TASK_WOKEN_TIMER) {
+               int exp = (tick_isset(sc_ep_lra(hs->sc)) ? tick_add_ifset(sc_ep_lra(hs->sc), hs->sc->ioto) : TICK_ETERNITY);
+
+               if (tick_is_expired(exp, now_ms)) {
+                       TRACE_ERROR("connection timed out", HS_EV_PROCESS_HSTRM, hs);
+                       hs->flags |= HS_ST_CONN_ERROR;
+                       hstream_send_error(hs, conn, &http_err_chunks[HTTP_ERR_408]);
+                       goto out;
+               }
+       }
 
        if (!(hs->flags & HS_ST_HTTP_GOT_HDRS)) {
                struct htx *htx = htx_from_buf(&hs->req);
@@ -1076,6 +1104,14 @@ static struct task *process_hstream(struct task *t, void *context, unsigned int
                task_destroy(t);
                t = NULL;
        }
+       else {
+               t->expire = (tick_is_expired(t->expire, now_ms) ? TICK_ETERNITY : t->expire);
+               if (!(hs->flags & HS_ST_HTTP_EOM_RCVD)) {
+                       int exp = (tick_isset(sc_ep_lra(hs->sc)) ? tick_add_ifset(sc_ep_lra(hs->sc), hs->sc->ioto) : TICK_ETERNITY);
+
+                       t->expire = tick_first(t->expire, exp);
+               }
+       }
 
  leave:
        TRACE_LEAVE(HS_EV_PROCESS_HSTRM, hs);
@@ -1109,6 +1145,7 @@ void *hstream_new(struct session *sess, struct stconn *sc, struct buffer *input)
        hs->obj_type = OBJ_TYPE_HATERM;
        hs->sess = sess;
        hs->sc = sc;
+       hs->sc->ioto = sess->fe->timeout.client;
        hs->task = t;
        hs->req = BUF_NULL;
        hs->res = BUF_NULL;