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.
This commit is contained in:
Christopher Faulet 2026-03-19 10:57:36 +01:00
parent cd363e0246
commit a61ea0f414
5 changed files with 58 additions and 11 deletions

View 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

View 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);

View 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 {

View 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) &&

View 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);