]> git.ipfire.org Git - thirdparty/haproxy.git/log
thirdparty/haproxy.git
4 days agoMINOR: sched: let's permit to share the local ctx between threads
Willy Tarreau [Tue, 30 Sep 2025 16:56:41 +0000 (18:56 +0200)] 
MINOR: sched: let's permit to share the local ctx between threads

The watchdog timer has to go through complex operations due to not being
able to check if another thread's scheduler is still ticking. This is
simply because the scheduler status is marked as thread-local while it
could in fact also be an array. Let's do that (and align the array to
avoid false sharing) so that it's now possible to check any scheduler's
status.

5 days agoBUG/MEDIUM: stick-tables: Make sure not to free a pending entry
Olivier Houchard [Tue, 30 Sep 2025 13:16:44 +0000 (15:16 +0200)] 
BUG/MEDIUM: stick-tables: Make sure not to free a pending entry

There is a race condition, an entry can be free'd by stksess_kill()
between the time stktable_add_pend_updates() gets the entry from the
mt_list, and the time it adds it to the ebtree.
To prevent this, use the newly implemented MT_LIST_POP_LOCKED() to keep
the stksess locked until it is added to the tree. That way,
__stksess_kill() will wait until we're done with it.

This should be backported to 3.2.

5 days agoMINOR: mt_list: Implement MT_LIST_POP_LOCKED()
Olivier Houchard [Tue, 30 Sep 2025 12:39:01 +0000 (14:39 +0200)] 
MINOR: mt_list: Implement MT_LIST_POP_LOCKED()

Implement MT_LIST_POP_LOCKED(), that behaves as MT_LIST_POP() and
removes the first element from the list, if any, but keeps it locked.

This should be backported to 3.2, as it will be use in a bug fix in the
stick tables that affects 3.2 too.

6 days agoADMIN: reload: introduce -vv mode
William Lallemand [Mon, 29 Sep 2025 17:21:08 +0000 (19:21 +0200)] 
ADMIN: reload: introduce -vv mode

The -v verbose mode displays the loading messages returned by the master
CLI reload command upon error.

The new -vv mode displays the loading messages even upon success,
showing the content of `show startup-logs` after the reload attempt.

6 days agoADMIN: reload: introduce verbose and silent mode
William Lallemand [Sun, 28 Sep 2025 20:45:04 +0000 (22:45 +0200)] 
ADMIN: reload: introduce verbose and silent mode

By default haproxy-reload displays the error that are not emitted by
haproxy, but only emitted by haproxy-reload.

-s silent mode, don't display any error

-v verbose mode, display the loading messages returned by the master CLI
reload command upon error.

6 days agoBUG/MEDIUM: acme: free() of i2d_X509_REQ() with AWS-LC
William Lallemand [Mon, 29 Sep 2025 11:28:11 +0000 (13:28 +0200)] 
BUG/MEDIUM: acme: free() of i2d_X509_REQ() with AWS-LC

When using AWS-LC, the free() of the data ptr resulting from
i2d_X509_REQ() might crash, because it uses the free() of the libc
instead of OPENSSL_free().

It does not seems to be a problem on openssl builds.

Must be backported in 3.2.

7 days agoADMIN: reload: add a synchronous reload helper
William Lallemand [Sun, 28 Sep 2025 20:08:27 +0000 (22:08 +0200)] 
ADMIN: reload: add a synchronous reload helper

haproxy-reload is a utility script which reload synchronously using the
master CLI, instead of asynchronously with kill.

7 days agoADMIN: dump-certs: use same error format as haproxy
William Lallemand [Sun, 28 Sep 2025 18:21:07 +0000 (20:21 +0200)] 
ADMIN: dump-certs: use same error format as haproxy

Replace error/notice by [ALERT]/[WARNING]/[NOTICE] like it's done in
haproxy.

ALERT means a failure and the program will exit 1 just after it
WARNING will continue the execution of the program
NOTICE will continue the execution as well

7 days agoADMIN: dump-certs: fix lack of / in -p
William Lallemand [Sun, 28 Sep 2025 15:18:31 +0000 (17:18 +0200)] 
ADMIN: dump-certs: fix lack of / in -p

Add a trailing / so -p don't fail if it wasn't specified.

7 days agoADMIN: dump-certs: create files in a tmpdir
William Lallemand [Sun, 28 Sep 2025 15:16:43 +0000 (17:16 +0200)] 
ADMIN: dump-certs: create files in a tmpdir

Files dumped from the socket are put in a temporary directory, this
directory is then removed upon exit.

Variable were cleaned to be clearer:
- crt_filename -> prev_crt
- key_filename -> prev_key
- ${crt_filename}.${tmp} -> new_crt
- ${key_filename}.${tmp} -> new_key

7 days agoADMIN: dump-certs: don't update the file if it's up to date
William Lallemand [Sun, 28 Sep 2025 14:33:37 +0000 (16:33 +0200)] 
ADMIN: dump-certs: don't update the file if it's up to date

Compare the fingerprint of the leaf certificate to the previous file to
check if it needs to be updated or not

Also skip the check if no file is on the disk.

7 days agoADMIN: haproxy-dump-certs: implement a certificate dumper
William Lallemand [Sun, 28 Sep 2025 11:33:23 +0000 (13:33 +0200)] 
ADMIN: haproxy-dump-certs: implement a certificate dumper

haproxy-dump0-certs is a bash script that connects to your master socket
or your stat socket in order to dump certificates from haproxy memory to
the corresponding files.

8 days agoMINOR: acme: implement "reuse-key" option
William Lallemand [Sat, 27 Sep 2025 19:41:39 +0000 (21:41 +0200)] 
MINOR: acme: implement "reuse-key" option

The new "reuse-key" option in the "acme" section, allows to keep the
private key instead of generating a new one at each renewal.

8 days agoBUG/MEDIUM: acme: cfg_postsection_acme() don't init correctly acme sections
William Lallemand [Sat, 27 Sep 2025 17:58:44 +0000 (19:58 +0200)] 
BUG/MEDIUM: acme: cfg_postsection_acme() don't init correctly acme sections

The cfg_postsection_acme() redefines its own cur_acme variable, pointing
to the first acme section created. Meaning that the first section would
be init multiple times, and the next sections won't never be
initialized.

It could result in crashes at the first use of all sections that are not
the first one.

Must be backported in 3.2

8 days agoBUG/MINOR: acme: don't unlink from acme_ctx_destroy()
William Lallemand [Sat, 27 Sep 2025 16:38:17 +0000 (18:38 +0200)] 
BUG/MINOR: acme: don't unlink from acme_ctx_destroy()

Unlinking the acme_ctx element from acme_ctx_destroy() requires to have
the element unlocked, because MT_LIST_DELETE() locks the element.

acme_ctx_destroy() frees the data from acme_ctx with the ctx still
linked and unlocked, then lock to unlink. So there's a small risk of
accessing acme_ctx from somewhere else. The only way to do that would be
to use the `acme challenge_ready` CLI command at the same time.

Fix the issue by doing a mt_list_unlock_link() and a
mt_list_unlock_self() to unlink the element under the lock, then destroy
the element.

This must be backported in 3.2.

9 days agoCI: github: build halog on the vtest job 20250926-halog
William Lallemand [Fri, 26 Sep 2025 14:11:06 +0000 (16:11 +0200)] 
CI: github: build halog on the vtest job

halog was not built in the vtest job. Add it to vtest.yml to be able to
track build issues on push.

9 days agoBUILD: halog: misleading indentation in halog.c
William Lallemand [Fri, 26 Sep 2025 13:58:49 +0000 (15:58 +0200)] 
BUILD: halog: misleading indentation in halog.c

admin/halog/halog.c: In function 'filter_count_url':
admin/halog/halog.c:1685:9: error: this 'if' clause does not guard... [-Werror=misleading-indentation]
 1685 |         if (unlikely(!ustat))
      |         ^~
admin/halog/halog.c:1687:17: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
 1687 |                 if (unlikely(!ustat)) {
      |                 ^~

This patch fixes the indentation.

Must be backported where fbd0fb20a22 ("BUG/MINOR: halog: Add OOM checks
for calloc() in filter_count_srv_status() and filter_count_url()") was
backported.

9 days agoMINOR: backend: srv_is_up converter
Chris Staite [Wed, 24 Sep 2025 21:21:43 +0000 (22:21 +0100)] 
MINOR: backend: srv_is_up converter

There is currently an srv_queue converter which is capable of taking the
output of a dynamic name and determining the queue length for a given
server.  In addition there is a sample fetcher for whether a server is
currently up.  This simply combines the two such that srv_is_up can be
used as a converter too.

Future work might extend this to other sample fetchers for servers, but
this is probably the most useful for acl routing.

9 days agoMINOR: backend: srv_queue helper
Chris Staite [Fri, 26 Sep 2025 08:04:28 +0000 (09:04 +0100)] 
MINOR: backend: srv_queue helper

In preparation of providing further server converters, split the code
for finding the server from the sample out.

Additionally, update the documentation for srv_queue converter to note
security concerns.

9 days agoBUILD: acme: fix false positive null pointer dereference
William Lallemand [Fri, 26 Sep 2025 08:34:35 +0000 (10:34 +0200)] 
BUILD: acme: fix false positive null pointer dereference

src/acme.c: In function ‘cfg_parse_acme_vars_provider’:
src/acme.c:471:9: error: potential null pointer dereference [-Werror=null-dereference]
  471 |         free(*dst);
      |         ^~~~~~~~~~

gcc13 on ubuntu 24.04 detects a false positive when building
3e72a9f ("MINOR: acme: provider-name for dpapi sink").
Indeed dst can't be NULL. Clarify the code so gcc don't complain
anymore.

9 days agoMINOR: acme: provider-name for dpapi sink
William Lallemand [Fri, 26 Sep 2025 08:09:27 +0000 (10:09 +0200)] 
MINOR: acme: provider-name for dpapi sink

Like "acme-vars", the "provider-name" in the acme section is used in
case of DNS-01 challenge and is sent to the dpapi sink.

This is used to pass the name of a DNS provider in order to chose the
DNS API to use.

This patch implements the cfg_parse_acme_vars_provider() which parses
either acme-vars or provider-name options and escape their strings.

Example:

     $ ( echo "@@1 show events dpapi -w -0"; cat - ) | socat /tmp/master.sock -  | cat -e
     <0>2025-09-18T17:53:58.831140+02:00 acme deploy foobpar.pem thumbprint gDvbPL3w4J4rxb8gj20mGEgtuicpvltnTl6j1kSZ3vQ$
     acme-vars "var1=foobar\"toto\",var2=var2"$
     provider-name "godaddy"$
     {$
       "identifier": {$
         "type": "dns",$
         "value": "example.com"$
       },$
       "status": "pending",$
       "expires": "2025-09-25T14:41:57Z",$
       [...]

9 days agoBUG/MEDIUM: ssl: ca-file directory mode must read every certificates of a file
William Lallemand [Fri, 26 Sep 2025 07:22:55 +0000 (09:22 +0200)] 
BUG/MEDIUM: ssl: ca-file directory mode must read every certificates of a file

The httpclient is configured with @system-ca by default, which uses the
directory returned by X509_get_default_cert_dir().

On debian/ubuntu systems, this directory contains multiple certificate
files that are loaded successfully. However it seems that on other
systems the files in this directory is the direct result of
ca-certificates instead of its source. Meaning that you would only have
a bundle file with every certificates in it.

The loading was not done correctly in case of directory loading, and was
only loading the first certificate of each file.

This patch fixes the issue by using X509_STORE_load_locations() on each
file from the scandir instead of trying to load it manually with BIO.

Not that we can't use X509_STORE_load_locations with the `dir` argument,
which would be simpler, because it uses X509_LOOKUP_hash_dir() which
requires a directory in hash form. That wouldn't be suited for this use
case.

Must be backported in every stable branches.

Fix issue #3137.

10 days agoCI: github: add curl+ech build into openssl-ech job
William Lallemand [Wed, 17 Sep 2025 13:58:54 +0000 (15:58 +0200)] 
CI: github: add curl+ech build into openssl-ech job

Build a curl binary with the ECH function linked with our openssl+ech
library.

10 days agoCI: scripts: build curl with ECH support
William Lallemand [Wed, 17 Sep 2025 13:48:16 +0000 (15:48 +0200)] 
CI: scripts: build curl with ECH support

Add a script to build curl with ECH support, to specify the path of the
openssl+ECH library, you should set the SSL_LIB variable with the prefix
of the library.

Example:
   SSL_LIB=/opt/openssl-ech CURL_DESTDIR=/opt/curl-ech/ ./build-curl.sh

10 days agoBUG/MINOR: pattern: Fix pattern lookup for map with opt@ prefix
Christopher Faulet [Thu, 25 Sep 2025 13:21:04 +0000 (15:21 +0200)] 
BUG/MINOR: pattern: Fix pattern lookup for map with opt@ prefix

When we look for a map file reference, the file@ prefix is removed because
if may be omitted. The same is true with opt@ prefix. However this case was
not properly performed in pat_ref_lookup(). Let's do so.

This patch must be backported as far as 3.0.

10 days agoCLEANUP: acme: acme_will_expire() uses acme_schedule_date()
William Lallemand [Thu, 25 Sep 2025 13:14:31 +0000 (15:14 +0200)] 
CLEANUP: acme: acme_will_expire() uses acme_schedule_date()

Date computation between acme_will_expire() and acme_schedule_date() are
the same. Call acme_schedule_date() from acme_will_expire() and put the
functions as static. The patch also move the functions in the right
order.

10 days agoBUG/MINOR: acme: possible overflow in acme_will_expire()
William Lallemand [Thu, 25 Sep 2025 12:59:19 +0000 (14:59 +0200)] 
BUG/MINOR: acme: possible overflow in acme_will_expire()

acme_will_expire() computes the schedule date using notAfter and
notBefore from the certificate. However notBefore could be greater than
notAfter and could result in an overflow.

This is unlikely to happen and would mean an incorrect certificate.

This patch fixes the issue by checking that notAfter > notBefore.

It also replace the int type by a time_t to avoid overflow on 64bits
architecture which is also unlikely to happen with certificates.

`(date.tv_sec + diff > notAfter)` was also replaced by `if (notAfter -
diff <= date.tv_sec)` to avoid an overflow.

Fix issue #3135.

Need to be backported to 3.2.

10 days agoBUG/MINOR: acme: possible overflow on scheduling computation
William Lallemand [Thu, 25 Sep 2025 12:39:31 +0000 (14:39 +0200)] 
BUG/MINOR: acme: possible overflow on scheduling computation

acme_schedule_date() computes the schedule date using notAfter and
notBefore from the certificate. However notBefore could be greater than
notAfter and could result in an overflow.

This is unlikely to happen and would mean an incorrect certificate.

This patch fixes the issue by checking that notAfter > notBefore.

It also replace the int type by a time_t to avoid overflow on 64bits
architecture which is also unlikely to happen with certificates.

Fix issue #3136.

Need to be backported to 3.2.

10 days agoBUG/MINOR: pattern: Properly flag virtual maps as using samples
Christopher Faulet [Thu, 25 Sep 2025 08:03:41 +0000 (10:03 +0200)] 
BUG/MINOR: pattern: Properly flag virtual maps as using samples

When a map file is load, internally, the pattern reference is flagged as
based on a sample. However it is not performed for virtual maps. This flag
is only used during startup to check the map compatibility when it used at
different places. At runtime this does not change anything. But errors can
be triggered during configuration parsing. For instance, the following valid
config will trigger an error:

    http-request set-map(virt@test) foo bar if !{ str(foo),map(virt@test) -m found }
    http-request set-var(txn.foo) str(foo),map(virt@test)

The fix is quite obvious. PAT_REF_SMP flag must be set for virtual map as
any other map.

A workaround is to use optional map (opt@...) by checking the map id cannot
reference an existing file.

This patch must be backported as far as 3.0.

10 days agoBUG/MINOR: compression: Test payload size only if content-length is specified
Christopher Faulet [Thu, 18 Sep 2025 07:08:11 +0000 (09:08 +0200)] 
BUG/MINOR: compression: Test payload size only if content-length is specified

When a minimum size is defined to performe the comression, the message
payload size is tested. To do so, information from the HTX message a used to
determine the message length. However it is performed regardless the payload
length is fully known or not. Concretely, the test must on be performed when
a content-length value was speficied or when the message was fully received
(EOM flag set). Otherwise, we are unable to really determine the real
payload length.

Because of this bug, compression may be skipped for a large chunked message
because the first chunks received are too small. But this does not mean the
whole message is small.

This patch must be backported to 3.2.

13 days agoBUG/MEDIUM: stick-tables: Don't let table_process_entry() handle refcnt
Olivier Houchard [Thu, 11 Sep 2025 16:22:34 +0000 (18:22 +0200)] 
BUG/MEDIUM: stick-tables: Don't let table_process_entry() handle refcnt

Instead of having table_process_entry() decrement the session's ref
counter, do it outside, from the caller. Some were missed, such as when
an action was invalid, which would lead to the ref counter not being
decremented, and the session not being destroyable.
It makes more sense to do that from the caller, who just obtained the
ref counter, anyway.
This should be backporter up to 2.8.

13 days agoCI: move VTest preparation & friends to dedicated composite action
Ilia Shipitsin [Wed, 17 Sep 2025 20:26:24 +0000 (22:26 +0200)] 
CI: move VTest preparation & friends to dedicated composite action

reference: https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action

preparing coredump limits, installing VTest are now served by dedicated
composite action

13 days agoBUG/MINOR: acme/cli: wrong description for "acme challenge_ready"
William Lallemand [Mon, 22 Sep 2025 17:14:54 +0000 (19:14 +0200)] 
BUG/MINOR: acme/cli: wrong description for "acme challenge_ready"

The "acme challenge_ready" command mistakenly use the description of the
"acme status" command. This patch adds the right description.

Must be backported to 3.2.

2 weeks agoMINOR: acme: check acme-vars allocation during escaping 20250918-acme-vars
William Lallemand [Fri, 19 Sep 2025 15:13:24 +0000 (17:13 +0200)] 
MINOR: acme: check acme-vars allocation during escaping

Handle allocation properly during acme-vars parsing.
Check if we have a allocation failure in both the malloc and the
realloc and emits an error if that's the case.

2 weeks agoMINOR: acme: acme-vars allow to pass data to the dpapi sink
William Lallemand [Thu, 18 Sep 2025 15:54:27 +0000 (17:54 +0200)] 
MINOR: acme: acme-vars allow to pass data to the dpapi sink

In the case of the dns-01 challenge, the agent that handles the
challenge might need some extra information which depends on the DNS
provider.

This patch introduces the "acme-vars" option in the acme section, which
allows to pass these data to the dpapi sink. The double quotes will be
escaped when printed in the sink.

Example:

    global
        setenv VAR1 'foobar"toto"'

    acme LE
        directory https://acme-staging-v02.api.letsencrypt.org/directory
        challenge DNS-01
        acme-vars "var1=${VAR1},var2=var2"

Would output:

    $ ( echo "@@1 show events dpapi -w -0"; cat - ) | socat /tmp/master.sock -  | cat -e
    <0>2025-09-18T17:53:58.831140+02:00 acme deploy foobpar.pem thumbprint gDvbPL3w4J4rxb8gj20mGEgtuicpvltnTl6j1kSZ3vQ$
    acme-vars "var1=foobar\"toto\",var2=var2"$
    {$
      "identifier": {$
        "type": "dns",$
        "value": "example.com"$
      },$
      "status": "pending",$
      "expires": "2025-09-25T14:41:57Z",$
      [...]

2 weeks agoBUG/MEDIUM: http-client: Fix the test on the response start-line
Christopher Faulet [Fri, 19 Sep 2025 12:51:32 +0000 (14:51 +0200)] 
BUG/MEDIUM: http-client: Fix the test on the response start-line

The commit 88aa7a780 ("MINOR: http-client: Trigger an error if first
response block isn't a start-line") introduced a bug. From an endpoint, an
applet or a mux, the <first> index must never be used. It is reserved to the
HTTP analyzers. From endpoint, this value may be undefined or just point on
any other block that the first one. Instead we must always get the head
block.

In taht case, to be sure the first HTX block in a response is a start-line,
we must use htx_get_head_type() function instead of htx_get_first_type().
Otherwise, we can trigger an error while the response is in fact properly
formatted.

It is a 3.3-speific issue. cNo backport needed.

2 weeks agoMEDIUM: stats: consider that shared stats pointers may be NULL
Aurelien DARRAGON [Thu, 18 Sep 2025 14:28:29 +0000 (16:28 +0200)] 
MEDIUM: stats: consider that shared stats pointers may be NULL

This patch looks huge, but it has a very simple goal: protect all
accessed to shared stats pointers (either read or writes), because
we know consider that these pointers may be NULL.

The reason behind this is despite all precautions taken to ensure the
pointers shouldn't be NULL when not expected, there are still corner
cases (ie: frontends stats used on a backend which no FE cap and vice
versa) where we could try to access a memory area which is not
allocated. Willy stumbled on such cases while playing with the rings
servers upon connection error, which eventually led to process crashes
(since 3.3 when shared stats were implemented)

Also, we may decide later that shared stats are optional and should
be disabled on the proxy to save memory and CPU, and this patch is
a step further towards that goal.

So in essence, this patch ensures shared stats pointers are always
initialized (including NULL), and adds necessary guards before shared
stats pointers are de-referenced. Since we already had some checks
for backends and listeners stats, and the pointer address retrieval
should stay in cpu cache, let's hope that this patch doesn't impact
stats performance much.

2 weeks agoBUG/MEDIUM: sink: fix unexpected double postinit of sink backend
Aurelien DARRAGON [Thu, 18 Sep 2025 11:29:53 +0000 (13:29 +0200)] 
BUG/MEDIUM: sink: fix unexpected double postinit of sink backend

Willy experienced an unexpected behavior with the config below:

    global
        stats socket :1514

    ring buf1
        server srv1 127.0.0.1:1514

Indeed, haproxy would connect to the ring server twice since commit 23e5f18b
("MEDIUM: sink: change the sink mode type to PR_MODE_SYSLOG"), and one of the
connection would report errors.

The reason behind is is, despite the above commit saying no change of behavior
is expected, with the sink forward_px proxy now being set with PR_MODE_SYSLOG,
postcheck_log_backend() was being automatically executed in addition to the
manual cfg_post_parse_ring() function for each "ring" section. The consequence
is that sink_finalize() was called twice for a given "ring" section, which
means the connection init would be triggered twice.. which in turn resulted in
the behavior described above, plus possible unexpected side-effects.

To fix the issue, when we create the forward_px proxy, we now set the
PR_CAP_INT capability on it to tell haproxy not to automatically manage the
proxy (ie: to skip the automatic log backend postinit), because we are about
to manually manage the proxy from the sink API.

No backport needed, this bug is specific to 3.3

2 weeks agoOPTIM: ring: avoid reloading the tail_ofs value before the CAS in ring_write()
Willy Tarreau [Thu, 18 Sep 2025 13:23:53 +0000 (15:23 +0200)] 
OPTIM: ring: avoid reloading the tail_ofs value before the CAS in ring_write()

The load followed by the CAS seem to cause two bus cycles, one to
retrieve the cache line in shared state and a second one to get
exclusive ownership of it. Tests show that on x86 it's much better
to just rely on the previous value and preset it to zero before
entering the loop. We just mask the ring lock in case of failure
so as to challenge it on next iteration and that's done.

This little change brings 2.3% extra performance (11.34M msg/s) on
a 64-core AMD.

2 weeks agoOPTIM: ring: check the queue's owner using a CAS on x86
Willy Tarreau [Thu, 18 Sep 2025 13:08:12 +0000 (15:08 +0200)] 
OPTIM: ring: check the queue's owner using a CAS on x86

In the loop where the queue's leader tries to get the tail lock,
we also need to check if another thread took ownership of the queue
the current thread is currently working for. This is currently done
using an atomic load.

Tests show that on x86, using a CAS for this is much more efficient
because it allows to keep the cache line in exclusive state for a
few more cycles that permit the queue release call after the loop
to be done without having to wait again. The measured gain is +5%
for 128 threads on a 64-core AMD system (11.08M msg/s vs 10.56M).
However, ARM loses about 1% on this, and we cannot afford that on
machines without a fast CAS anyway, so the load is performed using
a CAS only on x86_64. It might not be as efficient on low-end models
but we don't care since they are not the ones dealing with high
contention.

2 weeks agoOPTIM: ring: always relax in the ring lock and leader wait loop
Willy Tarreau [Thu, 18 Sep 2025 13:01:29 +0000 (15:01 +0200)] 
OPTIM: ring: always relax in the ring lock and leader wait loop

Tests have shown that AMD systems really need to use a cpu_relax()
in these two loops. The performance improves from 10.03 to 10.56M
messages per second (+5%) on a 128-thread system, without affecting
intel nor ARM, so let's do this.

2 weeks agoCLEANUP: ring: rearrange the wait loop in ring_write()
Willy Tarreau [Thu, 18 Sep 2025 12:58:38 +0000 (14:58 +0200)] 
CLEANUP: ring: rearrange the wait loop in ring_write()

The loop is constructed in a complicated way with a single break
statement in the middle and many continue statements everywhere,
making it hard to better factor between variants. Let's first
reorganize it so as to make it easier to escape when the ring
tail lock is obtained. The sequence of instrucitons remains the
same, it's only better organized.

2 weeks agoOPTIM: sink: don't waste time calling sink_announce_dropped() if busy
Willy Tarreau [Thu, 18 Sep 2025 07:07:35 +0000 (09:07 +0200)] 
OPTIM: sink: don't waste time calling sink_announce_dropped() if busy

If we see that another thread is already busy trying to announce the
dropped counter, there's no point going there, so let's just skip all
that operation from sink_write() and avoid disturbing the other thread.
This results in a boost from 244 to 262k req/s.

2 weeks agoOPTIM: sink: reduce contention on sink_announce_dropped()
Willy Tarreau [Thu, 18 Sep 2025 06:38:34 +0000 (08:38 +0200)] 
OPTIM: sink: reduce contention on sink_announce_dropped()

perf top shows that sink_announce_dropped() consumes most of the CPU
on a 128-thread x86 system. Digging further reveals that the atomic
fetch_or() on the dropped field used to detect the presence of another
thread is entirely responsible for this. Indeed, the compiler implements
it using a CAS that loops without relaxing and makes all threads wait
until they can synchronize on this one, only to discover later that
another thread is there and they need to give up.

Let's just replace this with a hand-crafted CAS loop that will detect
*before* attempting the CAS if another thread is there. Doing so
achieves the same goal without forcing threads to agree. With this
simple change, the sustained request rate on h1 with all traces on
bumped from 110k/s to 244k/s!

This should be backported to stable releases where it's often needed
to help debugging.

2 weeks agoMINOR: trace: don't call strlen() on the function's name
Willy Tarreau [Thu, 18 Sep 2025 06:26:50 +0000 (08:26 +0200)] 
MINOR: trace: don't call strlen() on the function's name

Currently there's a small mistake in the way the trace function and
macros. The calling function name is known as a constant until the
macro and passed as-is to the __trace() function. That one needs to
know its length and will call ist() on it, resulting in a real call
to strlen() while that length was known before the call. Let's use
an ist instead of a const char* for __trace() and __trace_enabled()
so that we can now completely avoid calling strlen() during this
operation. This has significantly reduced the importance of
__trace_enabled() in perf top.

2 weeks agoMINOR: trace: don't call strlen() on the thread-id numeric encoding
Willy Tarreau [Thu, 18 Sep 2025 06:02:59 +0000 (08:02 +0200)] 
MINOR: trace: don't call strlen() on the thread-id numeric encoding

In __trace(), we're making an integer for the thread id but this one
is passed through strlen() in the call to ist() because it's not a
constant. We do know that it's exactly 3 chars long so we can manage
this using ist2() and pass it the length instead in order to reduce
the number of calls to strlen().

Also let's note that the thread number will no longer be numeric for
thread numbers above 100.

2 weeks agoBUG/MEDIUM: ring: invert the length check to avoid an int overflow
Willy Tarreau [Wed, 17 Sep 2025 16:45:13 +0000 (18:45 +0200)] 
BUG/MEDIUM: ring: invert the length check to avoid an int overflow

Vincent Gramer reported in GH issue #3125 a case of crash on a BUG_ON()
condition in the rings. What happens is that a message that is one byte
less than the maximum ring size is emitted, and it passes all the checks,
but once inflated by the extra +1 for the refcount, it can no longer. But
the check was made based on message size compared to space left, except
that this space left can now be negative, which is a high positive for
size_t, so the check remained valid and triggered a BUG_ON() later.

Let's compute the size the other way around instead (i.e. current +
needed) since we can't have rings as large as half of the memory space
anyway, thus we have no risk of overflow on this one.

This needs to be backported to all versions supporting multi-threaded
rings (3.0 and above).

Thanks to Vincent for the easy and working reproducer.

2 weeks agoMINOR: server: add the "cc" keyword to set the TCP congestion controller
Willy Tarreau [Wed, 17 Sep 2025 15:15:12 +0000 (17:15 +0200)] 
MINOR: server: add the "cc" keyword to set the TCP congestion controller

It is possible on at least Linux and FreeBSD to set the congestion control
algorithm to be used with outgoing connections, among the list of supported
and permitted ones. Let's expose this setting with "cc". Unknown or
forbidden algorithms will be ignored and the default one will continue to
be used.

2 weeks agoMINOR: listener: add the "cc" bind keyword to set the TCP congestion controller
Willy Tarreau [Wed, 17 Sep 2025 15:03:42 +0000 (17:03 +0200)] 
MINOR: listener: add the "cc" bind keyword to set the TCP congestion controller

It is possible on at least Linux and FreeBSD to set the congestion control
algorithm to be used with incoming connections, among the list of supported
and permitted ones. Let's expose this setting with "cc". Permission issues
might be reported (as warnings).

2 weeks agoIMPORT: ebtree: replace hand-rolled offsetof to avoid UB
Ben Kallus [Sat, 13 Sep 2025 12:26:39 +0000 (14:26 +0200)] 
IMPORT: ebtree: replace hand-rolled offsetof to avoid UB

The C standard specifies that it's undefined behavior to dereference
NULL (even if you use & right after). The hand-rolled offsetof idiom
&(((s*)NULL)->f) is thus technically undefined. This clutters the
output of UBSan and is simple to fix: just use the real offsetof when
it's available.

Note that there's no clear statement about this point in the spec,
only several points which together converge to this:

- From N3220, 6.5.3.4:
  A postfix expression followed by the -> operator and an identifier
  designates a member of a structure or union object. The value is
  that of the named member of the object to which the first expression
  points, and is an lvalue.

- From N3220, 6.3.2.1:
  An lvalue is an expression (with an object type other than void) that
  potentially designates an object; if an lvalue does not designate an
  object when it is evaluated, the behavior is undefined.

- From N3220, 6.5.4.4 p3:
  The unary & operator yields the address of its operand. If the
  operand has type "type", the result has type "pointer to type". If
  the operand is the result of a unary * operator, neither that operator
  nor the & operator is evaluated and the result is as if both were
  omitted, except that the constraints on the operators still apply and
  the result is not an lvalue. Similarly, if the operand is the result
  of a [] operator, neither the & operator nor the unary * that is
  implied by the [] is evaluated and the result is as if the & operator
  were removed and the [] operator were changed to a + operator.

=> In short, this is saying that C guarantees these identities:
    1. &(*p) is equivalent to p
    2. &(p[n]) is equivalent to p + n

As a consequence, &(*p) doesn't result in the evaluation of *p, only
the evaluation of p (and similar for []). There is no corresponding
special carve-out for ->.

See also: https://pvs-studio.com/en/blog/posts/cpp/0306/

After this patch, HAProxy can run without crashing after building w/
clang-19 -fsanitize=undefined -fno-sanitize=function,alignment

This is ebtree commit bd499015d908596f70277ddacef8e6fa998c01d5.
Signed-off-by: Willy Tarreau <w@1wt.eu>
This is ebtree commit 5211c2f71d78bf546f5d01c8d3c1484e868fac13.

2 weeks agoIMPORT: ebtree: add a definition of offsetof()
Willy Tarreau [Sat, 13 Sep 2025 12:21:54 +0000 (14:21 +0200)] 
IMPORT: ebtree: add a definition of offsetof()

We'll use this to improve the definition of container_of(). Let's define
it if it does not exist. We can rely on __builtin_offsetof() on recent
enough compilers.

This is ebtree commit 1ea273e60832b98f552b9dbd013e6c2b32113aa5.
Signed-off-by: Willy Tarreau <w@1wt.eu>
This is ebtree commit 69b2ef57a8ce321e8de84486182012c954380401.

2 weeks agoIMPORT: ebtree: Fix UB from clz(0)
Ben Kallus [Sat, 13 Sep 2025 12:00:03 +0000 (14:00 +0200)] 
IMPORT: ebtree: Fix UB from clz(0)

From 'man gcc': passing 0 as the argument to "__builtin_ctz" or
"__builtin_clz" invokes undefined behavior. This triggers UBsan
in HAProxy.

[wt: tested in treebench and verified not to cause any performance
 regression with opstime-u32 nor stress-u32]
Signed-off-by: Willy Tarreau <w@1wt.eu>
This is ebtree commit 8c29daf9fa6e34de8c7684bb7713e93dcfe09029.
Signed-off-by: Willy Tarreau <w@1wt.eu>
This is ebtree commit cf3b93736cb550038325e1d99861358d65f70e9a.

2 weeks agoIMPORT: ebst: use prefetching in lookup() and insert()
Willy Tarreau [Wed, 17 Sep 2025 12:14:38 +0000 (14:14 +0200)] 
IMPORT: ebst: use prefetching in lookup() and insert()

While the previous optimizations couldn't be preserved due to the
possibility of out-of-bounds accesses, at least the prefetch is useful.
A test on treebench shows that for 64k short strings, the lookup time
falls from 276 to 199ns per lookup (28% savings), and the insert falls
from 311 to 296ns (4.9% savings), which are pretty respectable, so
let's do this.

This is ebtree commit b44ea5d07dc1594d62c3a902783ed1fb133f568d.

2 weeks agoIMPORT: ebtree: only use __builtin_prefetch() when supported
Willy Tarreau [Sat, 5 Jul 2025 19:57:33 +0000 (21:57 +0200)] 
IMPORT: ebtree: only use __builtin_prefetch() when supported

It looks like __builtin_prefetch() appeared in gcc-3.1 as there's no
mention of it in 3.0's doc. Let's replace it with eb_prefetch() which
maps to __builtin_prefetch() on supported compilers and falls back to
the usual do{}while(0) on other ones. It was tested to properly build
with tcc as well as gcc-2.95.

This is ebtree commit 7ee6ede56a57a046cb552ed31302b93ff1a21b1a.

2 weeks agoIMPORT: eb32/64: optimize insert for modern CPUs
Willy Tarreau [Thu, 12 Jun 2025 22:13:06 +0000 (00:13 +0200)] 
IMPORT: eb32/64: optimize insert for modern CPUs

Similar to previous patches, let's improve the insert() descent loop to
avoid discovering mandatory data too late. The change here is even
simpler than previous ones, a prefetch was installed and troot is
calculated before last instruction in a speculative way. This was enough
to gain +50% insertion rate on random data.

This is ebtree commit e893f8cc4d44b10f406b9d1d78bd4a9bd9183ccf.

2 weeks agoIMPORT: ebmb: optimize the lookup for modern CPUs
Willy Tarreau [Sun, 8 Jun 2025 09:50:59 +0000 (11:50 +0200)] 
IMPORT: ebmb: optimize the lookup for modern CPUs

This is the same principles as for the latest improvements made on
integer trees. Applying the same recipes made the ebmb_lookup()
function jump from 10.07 to 12.25 million lookups per second on a
10k random values tree (+21.6%).

It's likely that the ebmb_lookup_longest() code could also benefit
from this, though this was neither explored nor tested.

This is ebtree commit a159731fd6b91648a2fef3b953feeb830438c924.

2 weeks agoIMPORT: eb32/eb64: place an unlikely() on the leaf test
Willy Tarreau [Sun, 8 Jun 2025 17:51:49 +0000 (19:51 +0200)] 
IMPORT: eb32/eb64: place an unlikely() on the leaf test

In the loop we can help the compiler build slightly more efficient code
by placing an unlikely() around the leaf test. This shows a consistent
0.5% performance gain both on eb32 and eb64.

This is ebtree commit 6c9cdbda496837bac1e0738c14e42faa0d1b92c4.

2 weeks agoIMPORT: eb32: drop the now useless node_bit variable
Willy Tarreau [Sun, 8 Jun 2025 17:47:02 +0000 (19:47 +0200)] 
IMPORT: eb32: drop the now useless node_bit variable

This one was previously used to preload from the node and keep a copy
in a register on i386 machines with few registers. With the new more
optimal code it's totally useless, so let's get rid of it. By the way
the 64 bit code didn't use that at all already.

This is ebtree commit 1e219a74cfa09e785baf3637b6d55993d88b47ef.

2 weeks agoIMPORT: eb32/eb64: use a more parallelizable check for lack of common bits
Willy Tarreau [Sat, 7 Jun 2025 11:12:40 +0000 (13:12 +0200)] 
IMPORT: eb32/eb64: use a more parallelizable check for lack of common bits

Instead of shifting the XOR value right and comparing it to 1, which
roughly requires 2 sequential instructions, better test if the XOR has
any bit above the current bit, which means any bit set among those
strictly higher, or in other words that XOR & (-bit << 1) is non-zero.
This is one less instruction in the fast path and gives another nice
performance gain on random keys (in million lookups/s):

    eb32   1k:  33.17 -> 37.30   +12.5%
          10k:  15.74 -> 17.08   +8.51%
         100k:   8.00 ->  9.00   +12.5%
    eb64   1k:  34.40 -> 38.10   +10.8%
          10k:  16.17 -> 17.10   +5.75%
         100k:   8.38 ->  8.87   +5.85%

This is ebtree commit c942a2771758eed4f4584fe23cf2914573817a6b.

2 weeks agoIMPORT: eb32/eb64: reorder the lookup loop for modern CPUs
Willy Tarreau [Sat, 7 Jun 2025 12:36:16 +0000 (14:36 +0200)] 
IMPORT: eb32/eb64: reorder the lookup loop for modern CPUs

The current code calculates the next troot based on a calculation.
This was efficient when the algorithm was developed many years ago
on K6 and K7 CPUs running at low frequencies with few registers and
limited branch prediction units but nowadays with ultra-deep pipelines
and high latency memory that's no longer efficient, because the CPU
needs to have completed multiple operations before knowing which
address to start fetching from. It's sad because we only have two
branches each time but the CPU cannot know it. In addition, the
calculation is performed late in the loop, which does not help the
address generation unit to start prefetching next data.

Instead we should help the CPU by preloading data early from the node
and calculing troot as soon as possible. The CPU will be able to
postpone that processing until the dependencies are available and it
really needs to dereference it. In addition we must absolutely avoid
serializing instructions such as "(a >> b) & 1" because there's no
way for the compiler to parallelize that code nor for the CPU to pre-
process some early data.

What this patch does is relatively simple:

  - we try to prefetch the next two branches as soon as the
    node is known, which will help dereference the selected node in
    the next iteration; it was shown that it only works with the next
    changes though, otherwise it can reduce the performance instead.
    In practice the prefetching will start a bit later once the node
    is really in the cache, but since there's no dependency between
    these instructions and any other one, we let the CPU optimize as
    it wants.

  - we preload all important data from the node (next two branches,
    key and node.bit) very early even if not immediately needed.
    This is cheap, it doesn't cause any pipeline stall and speeds
    up later operations.

  - we pre-calculate 1<<bit that we assign into a register, so as
    to avoid serializing instructions when deciding which branch to
    take.

  - we assign the troot based on a ternary operation (or if/else) so
    that the CPU knows upfront the two possible next addresses without
    waiting for the end of a calculation and can prefetch their contents
    every time the branch prediction unit guesses right.

Just doing this provides significant gains at various tree sizes on
random keys (in million lookups per second):

  eb32   1k:  29.07 -> 33.17  +14.1%
        10k:  14.27 -> 15.74  +10.3%
       100k:   6.64 ->  8.00  +20.5%
  eb64   1k:  27.51 -> 34.40  +25.0%
        10k:  13.54 -> 16.17  +19.4%
       100k:   7.53 ->  8.38  +11.3%

The performance is now much closer to the sequential keys. This was
done for all variants ({32,64}{,i,le,ge}).

Another point, the equality test in the loop improves the performance
when looking up random keys (since we don't need to reach the leaf),
but is counter-productive for sequential keys, which can gain ~17%
without that test. However sequential keys are normally not used with
exact lookups, but rather with lookup_ge() that spans a time frame,
and which does not have that test for this precise reason, so in the
end both use cases are served optimally.

It's interesting to note that everything here is solely based on data
dependencies, and that trying to perform *less* operations upfront
always ends up with lower performance (typically the original one).

This is ebtree commit 05a0613e97f51b6665ad5ae2801199ad55991534.

2 weeks agoIMPORT: ebtree: delete unusable ebpttree.c
Willy Tarreau [Wed, 11 Jun 2025 20:00:18 +0000 (22:00 +0200)] 
IMPORT: ebtree: delete unusable ebpttree.c

Since commit 21fd162 ("[MEDIUM] make ebpttree rely solely on eb32/eb64
trees") it was no longer used and no longer builds. The commit message
mentions that the file is no longer needed, probably that a rebase failed
and left the file there.

This is ebtree commit fcfaf8df90e322992f6ba3212c8ad439d3640cb7.

2 weeks agoDOC: internals: document the shm-stats-file format/mapping
Aurelien DARRAGON [Tue, 16 Sep 2025 16:44:02 +0000 (18:44 +0200)] 
DOC: internals: document the shm-stats-file format/mapping

Add some documentation about shm stats file structure to help writing
tools that can parse the file to use the shared stats counters.

This file was written for shm stats file version 1.0 specifically,
it may need to be updated when the shm stats file structure changes
in the future.

2 weeks agoMINOR: counters: document that tg shared counters are tied to shm-stats-file mapping
Aurelien DARRAGON [Tue, 16 Sep 2025 16:30:51 +0000 (18:30 +0200)] 
MINOR: counters: document that tg shared counters are tied to shm-stats-file mapping

Let's explicitly mention that fe_counters_shared_tg and
be_counters_shared_tg structs are embedded in shm_stats_file_object
struct so any change in those structs will result in shm stats file
incompatibility between processes, thus extra precaution must be
taken when making changes to them.

Note that the provisionning made in shm_stats_file_object struct could
be used to add members to {fe,be}_counters_shared_tg without changing
shm_stats_file_object struct size if needed in order to preserve
shm stats file version.

2 weeks agoCLEANUP: log: remove deadcode in px_parse_log_steps()
Aurelien DARRAGON [Tue, 16 Sep 2025 06:17:03 +0000 (08:17 +0200)] 
CLEANUP: log: remove deadcode in px_parse_log_steps()

When logsteps proxy storage was migrated from eb nodes to bitmasks in
6a92b14 ("MEDIUM: log/proxy: store log-steps selection using a bitmask,
not an eb tree"), some unused eb node related code was left over in
px_parse_log_steps()

Not only this code is unused, it also resulted in wasted memory since
an eb node was allocated for nothing.

This should fix GH #3121

2 weeks agoBUG/MEDIUM: pattern: fix possible infinite loops on deletion (try 2)
Willy Tarreau [Tue, 16 Sep 2025 09:49:01 +0000 (11:49 +0200)] 
BUG/MEDIUM: pattern: fix possible infinite loops on deletion (try 2)

Commit e36b3b60b3 ("MEDIUM: migrate the patterns reference to cebs_tree")
changed the construction of the loops used to look up matching nodes, and
since we don't need two elements anymore, the "continue" statement now
loops on the same element when deleting. Let's fix this to make sure it
passes through the next one.

While this bug is 3.3 only, it turns out that 3.2 is also affected by
the incorrect loop construct in pat_ref_set_from_node(), where it's
possible to run an infinite loop since commit 010c34b8c7 ("MEDIUM:
pattern: consider gen_id in pat_ref_set_from_node()") due to the
"continue" statement being placed before the ebmb_next_dup() call.

As such the relevant part of this fix (pat_ref_set_from_elt) will
need to be backported to 3.2.

2 weeks agoRevert "BUG/MEDIUM: pattern: fix possible infinite loops on deletion"
Willy Tarreau [Tue, 16 Sep 2025 14:27:21 +0000 (16:27 +0200)] 
Revert "BUG/MEDIUM: pattern: fix possible infinite loops on deletion"

This reverts commit 359a829ccb8693e0b29808acc0fa7975735c0353.
The fix is neither sufficient nor correct (it triggers ASAN). Better
redo it cleanly rather than accumulate invalid fixes.

2 weeks agoCI: scripts: mkdir BUILDSSL_TMPDIR
William Lallemand [Tue, 16 Sep 2025 13:32:08 +0000 (15:32 +0200)] 
CI: scripts: mkdir BUILDSSL_TMPDIR

Creates the BUILDSSL_TMPDIR at the beginning of the script instead of
having to create it in each download functions

2 weeks agoCI: github: add an OpenSSL + ECH job
William Lallemand [Tue, 16 Sep 2025 10:01:23 +0000 (12:01 +0200)] 
CI: github: add an OpenSSL + ECH job

The upcoming ECH feature need a patched OpenSSL with the "feature/ech"
branch.

This daily job launches an openssl build, as well as haproxy build with
reg-tests.

2 weeks agoCI: scripts: add support for git in openssl builds
William Lallemand [Tue, 16 Sep 2025 09:50:34 +0000 (11:50 +0200)] 
CI: scripts: add support for git in openssl builds

Add support for git releases downloaded from github in openssl builds:

- GIT_TYPE variable allow you to chose between "branch" or "commit"
- OPENSSL_VERSION variable supports a "git-" prefix
- "git-${commit_id}" is stored in .openssl_version instead of the branch
  name for version comparison.

2 weeks agoBUG/MEDIUM: pattern: fix possible infinite loops on deletion
Willy Tarreau [Tue, 16 Sep 2025 09:49:01 +0000 (11:49 +0200)] 
BUG/MEDIUM: pattern: fix possible infinite loops on deletion

Commit e36b3b60b3 ("MEDIUM: migrate the patterns reference to cebs_tree")
changed the construction of the loops used to look up matching nodes, and
since we don't need two elements anymore, the "continue" statement now
loops on the same element when deleting. Let's fix this to make sure it
passes through the next one.

No backport is needed, this is only 3.3.

2 weeks agoCLEANUP: vars: use the item API for the variables trees
Willy Tarreau [Tue, 16 Sep 2025 08:47:52 +0000 (10:47 +0200)] 
CLEANUP: vars: use the item API for the variables trees

The variables trees use the immediate cebtree API, better use the
item one which is more expressive and safer. The "node" field was
renamed to "name_node" to avoid any ambiguity.

2 weeks agoCLEANUP: tools: use the item API for the file names tree
Willy Tarreau [Tue, 16 Sep 2025 08:41:19 +0000 (10:41 +0200)] 
CLEANUP: tools: use the item API for the file names tree

The file names tree uses the immediate cebtree API, better use the
item one which is more expressive and safer.

2 weeks agoMEDIUM: connection: reintegrate conn_hash_node into connection
Willy Tarreau [Fri, 12 Sep 2025 16:12:18 +0000 (18:12 +0200)] 
MEDIUM: connection: reintegrate conn_hash_node into connection

Previously the conn_hash_node was placed outside the connection due
to the big size of the eb64_node that could have negatively impacted
frontend connections. But having it outside also means that one
extra allocation is needed for each backend connection, and that one
memory indirection is needed for each lookup.

With the compact trees, the tree node is smaller (16 bytes vs 40) so
the overhead is much lower. By integrating it into the connection,
We're also eliminating one pointer from the connection to the hash
node and one pointer from the hash node to the connection (in addition
to the extra object bookkeeping). This results in saving at least 24
bytes per total backend connection, and only inflates connections by
16 bytes (from 240 to 256), which is a reasonable compromise.

Tests on a 64-core EPYC show a 2.4% increase in the request rate
(from 2.08 to 2.13 Mrps).

2 weeks agoMEDIUM: connection: move idle connection trees to ceb64
Willy Tarreau [Fri, 12 Sep 2025 13:28:33 +0000 (15:28 +0200)] 
MEDIUM: connection: move idle connection trees to ceb64

Idle connection trees currently require a 56-byte conn_hash_node per
connection, which can be reduced to 32 bytes by moving to ceb64. While
ceb64 is theoretically slower, in practice here we're essentially
dealing with trees that almost always contain a single key and many
duplicates. In this case, ceb64 insert and lookup functions become
faster than eb64 ones because all duplicates are a list accessed in
O(1) while it's a subtree for eb64. In tests it is impossible to tell
the difference between the two, so it's worth reducing the memory
usage.

This commit brings the following memory savings to conn_hash_node
(one per backend connection), and to srv_per_thread (one per thread
and per server):

     struct       before  after  delta
  conn_hash_nodea   56     32     -24
  srv_per_thread    96     72     -24

The delicate part is conn_delete_from_tree(), because we need to
know the tree root the connection is attached to. But thanks to
recent cleanups, it's now clear enough (i.e. idle/safe/avail vs
session are easy to distinguish).

2 weeks agoMINOR: connection: pass the thread number to conn_delete_from_tree()
Willy Tarreau [Fri, 12 Sep 2025 14:47:12 +0000 (16:47 +0200)] 
MINOR: connection: pass the thread number to conn_delete_from_tree()

We'll soon need to choose the server's root based on the connection's
flags, and for this we'll need the thread it's attached to, which is
not always the current one. This patch simply passes the thread number
from all callers. They know it because they just set the idle_conns
lock on it prior to calling the function.

2 weeks agoCLEANUP: backend: use a single variable for removed in srv_cleanup_idle_conns()
Willy Tarreau [Fri, 12 Sep 2025 14:28:58 +0000 (16:28 +0200)] 
CLEANUP: backend: use a single variable for removed in srv_cleanup_idle_conns()

Probably due to older code, there's a boolean variable used to set
another one which is then checked. Also the first check is made under
the lock, which is unnecessary. Let's simplify this and use a single
variable. This only makes the code clearer, it doesn't change the output
code.

2 weeks agoMINOR: server: pass the server and thread to srv_migrate_conns_to_remove()
Willy Tarreau [Fri, 12 Sep 2025 14:24:16 +0000 (16:24 +0200)] 
MINOR: server: pass the server and thread to srv_migrate_conns_to_remove()

We'll need to have access to the srv_per_thread element soon from this
function, and there's no particular reason for passing it list pointers
so let's pass the server and the thread so that it is autonomous. It
also makes the calling code simpler.

2 weeks agoCLEANUP: server: use eb64_entry() not ebmb_entry() to convert an eb64
Willy Tarreau [Fri, 12 Sep 2025 13:49:48 +0000 (15:49 +0200)] 
CLEANUP: server: use eb64_entry() not ebmb_entry() to convert an eb64

There were a few leftovers from an earlier version of the conn_hash_node
that was using ebmb nodes. A few calls to ebmb_first() and ebmb_entry()
were still present while acting on an eb64 tree. These are harmless as
one is just eb_first() and the other container_of(), but it's confusing
so let's clean them up.

2 weeks agoCLEANUP: backend: factor the connection lookup loop
Willy Tarreau [Fri, 12 Sep 2025 13:30:30 +0000 (15:30 +0200)] 
CLEANUP: backend: factor the connection lookup loop

The connection lookup loop is made of two identical blocks, one looking
in the idle or safe lists and the other one looking into the safe list
only. The second one is skipped if a connection was found or if the request
looks for a safe one (since already done). Also the two are slightly
different due to leftovers from earlier versions in that the second one
checks for safe connections and not the first one, and the second one
sets is_safe which is not used later.

Let's just rationalize all this by placing them in a loop which checks
first from the idle conns and second from the safe ones, or skips the
first step if the request wants a safe connection. This reduces the
code and shortens the time spent under the lock.

2 weeks agoCLEANUP: proxy: slightly reorganize fields to plug some holes
Willy Tarreau [Sun, 24 Aug 2025 10:38:18 +0000 (12:38 +0200)] 
CLEANUP: proxy: slightly reorganize fields to plug some holes

The proxy struct has several small holes that deserved being plugged by
moving a few fields around. Now we're down to 3056 from 3072 previously,
and the remaining holes are small.

At the moment, compared to before this series, we're seeing these
sizes:

    type\size   7d554ca62   current  delta
    listener       752        704     -48  (-6.4%)
    server        4032       3840    -192  (-4.8%)
    proxy         3184       3056    -128  (-4%)
    stktable      3392       3328     -64  (-1.9%)

Configs with many servers have shrunk by about 4% in RAM and configs
with many proxies by about 3%.

2 weeks agoCLEANUP: server: slightly reorder fields in the struct to plug holes
Willy Tarreau [Sun, 24 Aug 2025 10:25:51 +0000 (12:25 +0200)] 
CLEANUP: server: slightly reorder fields in the struct to plug holes

The struct server still has a lot of holes and padding that make it
quite big. By moving a few fields aronud between areas which do not
interact (e.g. boot vs aligned areas), it's quite easy to plug some
of them and/or to arrange larger ones which could be reused later with
a bit more effort. Here we've reduced holes by 40 bytes, allowing the
struct to shrink by one more cache line (64 bytes). The new size is
3840 bytes.

2 weeks agoMEDIUM: server: index server ID using compact trees
Willy Tarreau [Sun, 24 Aug 2025 09:21:02 +0000 (11:21 +0200)] 
MEDIUM: server: index server ID using compact trees

The server ID is currently stored as a 32-bit int using an eb32 tree.
It's used essentially to find holes in order to automatically assign IDs,
and to detect duplicates. Let's change this to use compact trees instead
in order to save 24 bytes in struct server for this node, plus 8 bytes in
struct proxy. The server struct is still 3904 bytes large (due to
alignment) and the proxy struct is 3072.

2 weeks agoMEDIUM: listener: index listener ID using compact trees
Willy Tarreau [Sun, 24 Aug 2025 09:12:49 +0000 (11:12 +0200)] 
MEDIUM: listener: index listener ID using compact trees

The listener ID is currently stored as a 32-bit int using an eb32 tree.
It's used essentially to find holes in order to automatically assign IDs,
and to detect duplicates. Let's change this to use compact trees instead
in order to save 24 bytes in struct listener for this node, plus 8 bytes
in struct proxy. The struct listener is now 704 bytes large, and the
struct proxy 3080.

2 weeks agoMEDIUM: proxy: index proxy ID using compact trees
Willy Tarreau [Sat, 23 Aug 2025 17:57:29 +0000 (19:57 +0200)] 
MEDIUM: proxy: index proxy ID using compact trees

The proxy ID is currently stored as a 32-bit int using an eb32 tree.
It's used essentially to find holes in order to automatically assign IDs,
and to detect duplicates. Let's change this to use compact trees instead
in order to save 24 bytes in struct proxy for this node, plus 8 bytes in
the root (which is static so not much relevant here). Now the proxy is
3088 bytes large.

2 weeks agoMINOR: proxy: add proxy_index_id() to index a proxy by its ID
Willy Tarreau [Sat, 23 Aug 2025 17:45:03 +0000 (19:45 +0200)] 
MINOR: proxy: add proxy_index_id() to index a proxy by its ID

This avoids needlessly exposing the tree's root and the mechanics outside
of the low-level code.

2 weeks agoMINOR: listener: add listener_index_id() to index a listener by its ID
Willy Tarreau [Sat, 23 Aug 2025 17:37:26 +0000 (19:37 +0200)] 
MINOR: listener: add listener_index_id() to index a listener by its ID

This avoids needlessly exposing the tree's root and the mechanics outside
of the low-level code.

2 weeks agoMINOR: server: add server_index_id() to index a server by its ID
Willy Tarreau [Sat, 23 Aug 2025 17:33:52 +0000 (19:33 +0200)] 
MINOR: server: add server_index_id() to index a server by its ID

This avoids needlessly exposing the tree's root and the mechanics outside
of the low-level code.

2 weeks agoCLEANUP: server: use server_find_by_id() when looking for already used IDs
Willy Tarreau [Sat, 23 Aug 2025 17:26:54 +0000 (19:26 +0200)] 
CLEANUP: server: use server_find_by_id() when looking for already used IDs

In srv_parse_id(), there's no point doing all the low-level work with
the tree functions to check for the existence of an ID, we already have
server_find_by_id() which does exactly this, so let's use it.

2 weeks agoMINOR: server: add server_get_next_id() to find next free server ID
Willy Tarreau [Sat, 23 Aug 2025 17:26:08 +0000 (19:26 +0200)] 
MINOR: server: add server_get_next_id() to find next free server ID

This was previously achieved via the generic get_next_id() but we'll soon
get rid of generic ID trees so let's have a dedicated server_get_next_id().
As a bonus it reduces the exposure of the tree's root outside of the functions.

2 weeks agoMINOR: listener: add listener_get_next_id() to find next free listener ID
Willy Tarreau [Sat, 23 Aug 2025 17:25:03 +0000 (19:25 +0200)] 
MINOR: listener: add listener_get_next_id() to find next free listener ID

This was previously achieved via the generic get_next_id() but we'll soon
get rid of generic ID trees so let's have a dedicated listener_get_next_id().
As a bonus it reduces the exposure of the tree's root outside of the functions.

2 weeks agoMINOR: proxy: add proxy_get_next_id() to find next free proxy ID
Willy Tarreau [Sat, 23 Aug 2025 17:24:21 +0000 (19:24 +0200)] 
MINOR: proxy: add proxy_get_next_id() to find next free proxy ID

This was previously achieved via the generic get_next_id() but we'll soon
get rid of generic ID trees so let's have a dedicated proxy_get_next_id().

2 weeks agoMEDIUM: stktable: index table names using compact trees
Willy Tarreau [Tue, 15 Jul 2025 11:50:03 +0000 (13:50 +0200)] 
MEDIUM: stktable: index table names using compact trees

Here we're saving 64 bytes per stick-table, from 3392 to 3328, and the
change was really straightforward so there's no reason not to do it.

2 weeks agoMEDIUM: proxy: switch conf.name to cebis_tree
Willy Tarreau [Tue, 15 Jul 2025 09:47:54 +0000 (11:47 +0200)] 
MEDIUM: proxy: switch conf.name to cebis_tree

This is used to index the proxy's name and it contains a copy of the
pointer to the proxy's name in <id>. Changing that for a ceb_node placed
just before <id> saves 32 bytes to the struct proxy, which is now 3112
bytes large.

Here we need to continue to support duplicates since they're still
allowed between type-incompatible proxies.

Interestingly, the use of cebis_next_dup() instead of cebis_next() in
proxy_find_by_name() allows us to get rid of an strcmp() that was
performed for each use_backend rule. A test with a large config
(100k backends) shows that we can get 3% extra performance on a
config involving a static use_backend rule (3.09M to 3.18M rps),
and even 4.5% on a dynamic rule selecting a random backend (2.47M
to 2.59M).

2 weeks agoMEDIUM: server: switch the host_dn member to cebis_tree
Willy Tarreau [Mon, 7 Jul 2025 15:11:33 +0000 (17:11 +0200)] 
MEDIUM: server: switch the host_dn member to cebis_tree

This member is used to index the hostname_dn contents for DNS resolution.
Let's replace it with a cebis_tree to save another 32 bytes (24 for the
node + 8 by avoiding the duplication of the pointer). The struct server is
now at 3904 bytes.

2 weeks agoMEDIUM: server: switch conf.name to cebis_tree
Willy Tarreau [Mon, 7 Jul 2025 13:33:40 +0000 (15:33 +0200)] 
MEDIUM: server: switch conf.name to cebis_tree

This is used to index the server name and it contains a copy of the
pointer to the server's name in <id>. Changing that for a ceb_node placed
just before <id> saves 32 bytes to the struct server, which remains 3968
bytes large due to alignment. The proxy struct shrinks by 8 bytes to 3144.

It's worth noting that the current way duplicate names are handled remains
based on the previous mechanism where dups were permitted. Ideally we
should now reject them during insertion and use unique key trees instead.

2 weeks agoMEDIUM: server: switch addr_node to cebis_tree
Willy Tarreau [Mon, 7 Jul 2025 13:13:15 +0000 (15:13 +0200)] 
MEDIUM: server: switch addr_node to cebis_tree

This contains the text representation of the server's address, for use
with stick-tables with "srvkey addr". Switching them to a compact node
saves 24 more bytes from this structure. The key was moved to an external
pointer "addr_key" right after the node.

The server struct is now 3968 bytes (down from 4032) due to alignment, and
the proxy struct shrinks by 8 bytes to 3152.

2 weeks agoMEDIUM: guid: switch guid to more compact cebuis_tree
Willy Tarreau [Mon, 17 Feb 2025 08:39:04 +0000 (09:39 +0100)] 
MEDIUM: guid: switch guid to more compact cebuis_tree

The current guid struct size is 56 bytes. Once reduced using compact
trees, it goes down to 32 (almost half). We're not on a critical path
and size matters here, so better switch to this.

It's worth noting that the name part could also be stored in the
guid_node at the end to save 8 extra byte (no pointer needed anymore),
however the purpose of this struct is to be embedded into other ones,
which is not compatible with having a dynamic size.

Affected struct sizes in bytes:

           Before     After   Diff
  server    4032       4032     0*
  proxy     3184       3160    -24
  listener   752        728    -24

*: struct server is full of holes and padding (176 bytes) and is
64-byte aligned. Moving the guid_node elsewhere such as after sess_conn
reduces it to 3968, or one less cache line. There's no point in moving
anything now because forthcoming patches will arrange other parts.

2 weeks agoMEDIUM: migrate the patterns reference to cebs_tree
Willy Tarreau [Sun, 12 Jan 2025 18:38:28 +0000 (19:38 +0100)] 
MEDIUM: migrate the patterns reference to cebs_tree

cebs_tree are 24 bytes smaller than ebst_tree (16B vs 40B), and pattern
references are only used during map/acl updates, so their storage is
pure loss between updates (which most of the time never happen). By
switching their indexing to compact trees, we can save 16 to 24 bytes
per entry depending on alightment (here it's 24 per struct but 16
practical as malloc's alignment keeps 8 unused).

Tested on core i7-8650U running at 3.0 GHz, with a file containing
17.7M IP addresses (16.7M different):

   $ time  ./haproxy -c -f acl-ip.cfg

Save 280 MB RAM for 17.7M IP addresses, and slightly speeds up the
startup (5.8%, from 19.2s to 18.2s), a part of which possible being
attributed to having to write less memory. Note that this is on small
strings. On larger ones such as user-agents, ebtree doesn't reread
the whole key and might be more efficient.

Before:
  RAM (VSZ/RSS): 4443912 3912444

  real    0m19.211s
  user    0m18.138s
  sys     0m1.068s

  Overhead  Command         Shared Object      Symbol
    44.79%  haproxy  haproxy            [.] ebst_insert
    25.07%  haproxy  haproxy            [.] ebmb_insert_prefix
     3.44%  haproxy  libc-2.33.so       [.] __libc_calloc
     2.71%  haproxy  libc-2.33.so       [.] _int_malloc
     2.33%  haproxy  haproxy            [.] free_pattern_tree
     1.78%  haproxy  libc-2.33.so       [.] inet_pton4
     1.62%  haproxy  libc-2.33.so       [.] _IO_fgets
     1.58%  haproxy  libc-2.33.so       [.] _int_free
     1.56%  haproxy  haproxy            [.] pat_ref_push
     1.35%  haproxy  libc-2.33.so       [.] malloc_consolidate
     1.16%  haproxy  libc-2.33.so       [.] __strlen_avx2
     0.79%  haproxy  haproxy            [.] pat_idx_tree_ip
     0.76%  haproxy  haproxy            [.] pat_ref_read_from_file
     0.60%  haproxy  libc-2.33.so       [.] __strrchr_avx2
     0.55%  haproxy  libc-2.33.so       [.] unlink_chunk.constprop.0
     0.54%  haproxy  libc-2.33.so       [.] __memchr_avx2
     0.46%  haproxy  haproxy            [.] pat_ref_append

After:
  RAM (VSZ/RSS): 4166108 3634768

  real    0m18.114s
  user    0m17.113s
  sys     0m0.996s

  Overhead  Command  Shared Object       Symbol
    38.99%  haproxy  haproxy             [.] cebs_insert
    27.09%  haproxy  haproxy             [.] ebmb_insert_prefix
     3.63%  haproxy  libc-2.33.so        [.] __libc_calloc
     3.18%  haproxy  libc-2.33.so        [.] _int_malloc
     2.69%  haproxy  haproxy             [.] free_pattern_tree
     1.99%  haproxy  libc-2.33.so        [.] inet_pton4
     1.74%  haproxy  libc-2.33.so        [.] _IO_fgets
     1.73%  haproxy  libc-2.33.so        [.] _int_free
     1.57%  haproxy  haproxy             [.] pat_ref_push
     1.48%  haproxy  libc-2.33.so        [.] malloc_consolidate
     1.22%  haproxy  libc-2.33.so        [.] __strlen_avx2
     1.05%  haproxy  libc-2.33.so        [.] __strcmp_avx2
     0.80%  haproxy  haproxy             [.] pat_idx_tree_ip
     0.74%  haproxy  libc-2.33.so        [.] __memchr_avx2
     0.69%  haproxy  libc-2.33.so        [.] __strrchr_avx2
     0.69%  haproxy  libc-2.33.so        [.] _IO_getline_info
     0.62%  haproxy  haproxy             [.] pat_ref_read_from_file
     0.56%  haproxy  libc-2.33.so        [.] unlink_chunk.constprop.0
     0.56%  haproxy  libc-2.33.so        [.] cfree@GLIBC_2.2.5
     0.46%  haproxy  haproxy             [.] pat_ref_append

If the addresses are totally disordered (via "shuf" on the input file),
we see both implementations reach exactly 68.0s (slower due to much
higher cache miss ratio).

On large strings such as user agents (1 million here), it's now slightly
slower (+9%):

Before:
  real    0m2.475s
  user    0m2.316s
  sys     0m0.155s

After:
  real    0m2.696s
  user    0m2.544s
  sys     0m0.147s

But such patterns are much less common than short ones, and the memory
savings do still count.

Note that while it could be tempting to get rid of the list that chains
all these pat_ref_elt together and only enumerate them by walking along
the tree to save 16 extra bytes per entry, that's not possible due to
the problem that insertion ordering is critical (think overlapping regex
such as /index.* and /index.html). Currently it's not possible to proceed
differently because patterns are first pre-loaded into the pat_ref via
pat_ref_read_from_file_smp() and later indexed by pattern_read_from_file(),
which has to only redo the second part anyway for maps/acls declared
multiple times.

2 weeks agoIMPORT: cebtree: import version 0.5.0 to support duplicates
Willy Tarreau [Mon, 7 Jul 2025 08:58:21 +0000 (10:58 +0200)] 
IMPORT: cebtree: import version 0.5.0 to support duplicates

The support for duplicates is necessary for various use cases related
to config names, so let's upgrade to the latest version which brings
this support. This updates the cebtree code to commit 808ed67 (tag
0.5.0). A few tiny adaptations were needed:
  - replace a few ceb_node** with ceb_root** since pointers are now
    tagged ;
  - replace cebu*.h with ceb*.h since both are now merged in the same
    include file. This way we can drop the unused cebu*.h files from
    cebtree that are provided only for compatibility.
  - rename immediate storage functions to cebXX_imm_XXX() as per the API
    change in 0.5 that makes immediate explicit rather than implicit.
    This only affects vars and tools.c:copy_file_name().

The tests continue to work.

2 weeks agoBUILD: makefile: implement support for running a command in range
Willy Tarreau [Tue, 16 Sep 2025 07:21:34 +0000 (09:21 +0200)] 
BUILD: makefile: implement support for running a command in range

When running "make range", it would be convenient to support running
reg tests or anything else such as "size", "pahole" or even benchmarks.
Such commands are usually specific to the developer's environment, so
let's just pass a generic variable TEST_CMD that is executed as-is if
not empty.

This way it becomes possible to run "make range RANGE=... TEST_CMD=...".