]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: tcpcheck: Use small buffer if possible for healthchecks
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 19 Mar 2026 09:57:36 +0000 (10:57 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 23 Mar 2026 13:02:43 +0000 (14:02 +0100)
If support for small buffers is enabled, we now try to use them for
healthcheck requests. First, we take care the tcpcheck ruleset may use small
buffers. Send rules using LF strings or too large data are excluded. The
ability to use small buffers or not are set on the ruleset. All send rules
of the ruleset must be compatible. This info is then transfer to server's
healthchecks relying on this ruleset.

Then, when a healthcheck is running, when a send rule is evaluated, if
possible, we try to use small buffers. On error, the ability to use small
buffers is removed and we retry with a regular buffer. It means on the first
error, the support is disabled for the healthcheck and all other runs will
use regular buffers.

include/haproxy/check-t.h
include/haproxy/check.h
include/haproxy/tcpcheck-t.h
src/check.c
src/tcpcheck.c

index df75d4acadcaec673cf77345da9f9c1556d56c28..ecb9a6862612dab3817157c86acf1663117c1f85 100644 (file)
@@ -59,6 +59,7 @@ enum chk_result {
 #define CHK_ST_FASTINTER        0x0400  /* force fastinter check */
 #define CHK_ST_READY            0x0800  /* check ready to migrate or run, see below */
 #define CHK_ST_SLEEPING         0x1000  /* check was sleeping, i.e. not currently bound to a thread, see below */
+#define CHK_ST_USE_SMALL_BUFF   0x2000  /* Use small buffers if possible for the request */
 
 /* 4 possible states for CHK_ST_SLEEPING and CHK_ST_READY:
  *   SLP  RDY   State      Description
index 5e34d6519fc47f798669a4a8c67fad0e96d2dc72..09e195a460490fa49e5e46ed39c73f5676351ee5 100644 (file)
@@ -78,7 +78,7 @@ struct task *process_chk(struct task *t, void *context, unsigned int state);
 struct task *srv_chk_io_cb(struct task *t, void *ctx, unsigned int state);
 
 int check_buf_available(void *target);
-struct buffer *check_get_buf(struct check *check, struct buffer *bptr);
+struct buffer *check_get_buf(struct check *check, struct buffer *bptr, unsigned int small_buffer);
 void check_release_buf(struct check *check, struct buffer *bptr);
 const char *init_check(struct check *check, int type);
 void free_check(struct check *check);
index 290a7358464c981bb7d219e181ff8ef1b8ef6c2f..3f48527ed72fd2439176b1419e0f34a7277267d2 100644 (file)
@@ -121,6 +121,7 @@ enum tcpcheck_rule_type {
 /* Unused 0x000000A0..0x00000FF0 (reserved for future proto) */
 #define TCPCHK_RULES_TCP_CHK     0x00000FF0
 #define TCPCHK_RULES_PROTO_CHK   0x00000FF0 /* Mask to cover protocol check */
+#define TCPCHK_RULES_MAY_USE_SBUF 0x00001000 /* checks may try to use small buffers if possible for the request */
 
 struct check;
 struct tcpcheck_connect {
index 57dc74b7f1079bb61f254aa66c393fdf690b8a7d..f89769e6daba7c17b62a6795c0c1e775cf38f3e1 100644 (file)
@@ -1515,13 +1515,15 @@ int check_buf_available(void *target)
 /*
  * Allocate a buffer. If it fails, it adds the check in buffer wait queue.
  */
-struct buffer *check_get_buf(struct check *check, struct buffer *bptr)
+struct buffer *check_get_buf(struct check *check, struct buffer *bptr, unsigned int small_buffer)
 {
        struct buffer *buf = NULL;
 
-       if (likely(!LIST_INLIST(&check->buf_wait.list)) &&
-           unlikely((buf = b_alloc(bptr, DB_CHANNEL)) == NULL)) {
-               b_queue(DB_CHANNEL, &check->buf_wait, check, check_buf_available);
+       if (small_buffer == 0 || (buf = b_alloc_small(bptr)) == NULL) {
+               if (likely(!LIST_INLIST(&check->buf_wait.list)) &&
+                   unlikely((buf = b_alloc(bptr, DB_CHANNEL)) == NULL)) {
+                       b_queue(DB_CHANNEL, &check->buf_wait, check, check_buf_available);
+               }
        }
        return buf;
 }
@@ -1533,8 +1535,11 @@ struct buffer *check_get_buf(struct check *check, struct buffer *bptr)
 void check_release_buf(struct check *check, struct buffer *bptr)
 {
        if (bptr->size) {
+               int defbuf = b_is_default(bptr);
+
                b_free(bptr);
-               offer_buffers(check->buf_wait.target, 1);
+               if (defbuf)
+                       offer_buffers(check->buf_wait.target, 1);
        }
 }
 
@@ -1654,7 +1659,6 @@ int start_check_task(struct check *check, int mininter,
  */
 static int start_checks()
 {
-
        struct proxy *px;
        struct server *s;
        char *errmsg = NULL;
@@ -1681,6 +1685,9 @@ static int start_checks()
         */
        for (px = proxies_list; px; px = px->next) {
                for (s = px->srv; s; s = s->next) {
+                       if (s->check.tcpcheck_rules->flags & TCPCHK_RULES_MAY_USE_SBUF)
+                               s->check.state |= CHK_ST_USE_SMALL_BUFF;
+
                        if (s->check.state & CHK_ST_CONFIGURED) {
                                nbcheck++;
                                if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
index fe8d4f76635d1eef6a7bb3c7d620bd54520ca322..3519a83e1d8f7afbfe77d690540e1437e06d99b9 100644 (file)
@@ -40,6 +40,7 @@
 #include <haproxy/check.h>
 #include <haproxy/chunk.h>
 #include <haproxy/connection.h>
+#include <haproxy/dynbuf.h>
 #include <haproxy/errors.h>
 #include <haproxy/global.h>
 #include <haproxy/h1.h>
@@ -1659,7 +1660,8 @@ enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_r
                goto out;
        }
 
-       if (!check_get_buf(check, &check->bo)) {
+  retry:
+       if (!check_get_buf(check, &check->bo, (check->state & CHK_ST_USE_SMALL_BUFF))) {
                check->state |= CHK_ST_OUT_ALLOC;
                ret = TCPCHK_EVAL_WAIT;
                TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
@@ -1679,6 +1681,13 @@ enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_r
        case TCPCHK_SEND_STRING:
        case TCPCHK_SEND_BINARY:
                if (istlen(send->data) >= b_size(&check->bo)) {
+                       if (b_is_small(&check->bo)) {
+                               check->state &= ~CHK_ST_USE_SMALL_BUFF;
+                               check_release_buf(check, &check->bo);
+                               TRACE_DEVEL("Send fail with small buffer retry with default one", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
+                               goto retry;
+                       }
+
                        chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
                                     (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
                                     tcpcheck_get_step_id(check, rule));
@@ -1689,6 +1698,7 @@ enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_r
                b_putist(&check->bo, send->data);
                break;
        case TCPCHK_SEND_STRING_LF:
+               BUG_ON(check->state & CHK_ST_USE_SMALL_BUFF);
                check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
                if (!b_data(&check->bo))
                        goto out;
@@ -1696,7 +1706,8 @@ enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_r
        case TCPCHK_SEND_BINARY_LF: {
                int len = b_size(&check->bo);
 
-               tmp = alloc_trash_chunk();
+               BUG_ON(check->state & CHK_ST_USE_SMALL_BUFF);
+               tmp = alloc_trash_chunk_sz(len);
                if (!tmp)
                        goto error_lf;
                tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
@@ -1713,7 +1724,7 @@ enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_r
                struct ist meth, uri, vsn, clen, body;
                unsigned int slflags = 0;
 
-               tmp = alloc_trash_chunk();
+               tmp = alloc_trash_chunk_sz(b_size(&check->bo));
                if (!tmp)
                        goto error_htx;
 
@@ -1838,6 +1849,12 @@ enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_r
                htx_reset(htx);
                htx_to_buf(htx, &check->bo);
        }
+       if (b_is_small(&check->bo)) {
+               check->state &= ~CHK_ST_USE_SMALL_BUFF;
+               check_release_buf(check, &check->bo);
+               TRACE_DEVEL("Send fail with small buffer retry with default one", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
+               goto retry;
+       }
        chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
                     tcpcheck_get_step_id(check, rule));
        TRACE_ERROR("failed to build HTTP request", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
@@ -1884,7 +1901,7 @@ enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_r
                goto wait_more_data;
        }
 
-       if (!check_get_buf(check, &check->bi)) {
+       if (!check_get_buf(check, &check->bi, 0)) {
                check->state |= CHK_ST_IN_ALLOC;
                TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
                goto wait_more_data;
@@ -4067,6 +4084,8 @@ static int check_proxy_tcpcheck(struct proxy *px)
                }
        }
 
+       /* Allow small buffer use by default. All send rules must be compatible */
+       px->tcpcheck_rules.flags |= (global.tune.bufsize_small ? TCPCHK_RULES_MAY_USE_SBUF : 0);
 
        /* Remove all comment rules. To do so, when a such rule is found, the
         * comment is assigned to the following rule(s).
@@ -4096,6 +4115,25 @@ static int check_proxy_tcpcheck(struct proxy *px)
                        ha_free(&comment);
                        break;
                case TCPCHK_ACT_SEND:
+                       /* Disable small buffer use for rules using LF stirngs or too large data */
+                       switch (chk->send.type) {
+                       case TCPCHK_SEND_STRING:
+                       case TCPCHK_SEND_BINARY:
+                               if (istlen(chk->send.data) >= global.tune.bufsize_small)
+                                       px->tcpcheck_rules.flags &= ~TCPCHK_RULES_MAY_USE_SBUF;
+                               break;
+                       case TCPCHK_SEND_STRING_LF:
+                       case TCPCHK_SEND_BINARY_LF:
+                               px->tcpcheck_rules.flags &= ~TCPCHK_RULES_MAY_USE_SBUF;
+                               break;
+                       case TCPCHK_SEND_HTTP:
+                               if ((chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) ||
+                                   (istlen(chk->send.http.body) >= global.tune.bufsize_small))
+                                       px->tcpcheck_rules.flags &= ~TCPCHK_RULES_MAY_USE_SBUF;
+                       default:
+                               break;
+                       }
+                       __fallthrough;
                case TCPCHK_ACT_EXPECT:
                        if (!chk->comment && comment)
                                chk->comment = strdup(comment);