From a61ea0f414ec453255a8663bbf4df03bc8e16bd4 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Thu, 19 Mar 2026 10:57:36 +0100 Subject: [PATCH] MEDIUM: tcpcheck: Use small buffer if possible for healthchecks 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 | 1 + include/haproxy/check.h | 2 +- include/haproxy/tcpcheck-t.h | 1 + src/check.c | 19 ++++++++++----- src/tcpcheck.c | 46 ++++++++++++++++++++++++++++++++---- 5 files changed, 58 insertions(+), 11 deletions(-) diff --git a/include/haproxy/check-t.h b/include/haproxy/check-t.h index df75d4aca..ecb9a6862 100644 --- a/include/haproxy/check-t.h +++ b/include/haproxy/check-t.h @@ -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 diff --git a/include/haproxy/check.h b/include/haproxy/check.h index 5e34d6519..09e195a46 100644 --- a/include/haproxy/check.h +++ b/include/haproxy/check.h @@ -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); diff --git a/include/haproxy/tcpcheck-t.h b/include/haproxy/tcpcheck-t.h index 290a73584..3f48527ed 100644 --- a/include/haproxy/tcpcheck-t.h +++ b/include/haproxy/tcpcheck-t.h @@ -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 { diff --git a/src/check.c b/src/check.c index 57dc74b7f..f89769e6d 100644 --- a/src/check.c +++ b/src/check.c @@ -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) && diff --git a/src/tcpcheck.c b/src/tcpcheck.c index fe8d4f766..3519a83e1 100644 --- a/src/tcpcheck.c +++ b/src/tcpcheck.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -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);