Control: tags 1102673 + patch
Control: tags 1102673 + pending

Dear maintainer,

I've prepared an NMU for haproxy (versioned as 3.0.10-0.1) and uploaded 
it to DELAYED/7. Please feel free to tell me if I should cancel it.

Upgrading to 3.0.10 looked more reasonable to me than backporting just 
the (one-line) CVE fix, but either is fine for me.

A maintainer upload of either would be my preferred option.

cu
Adrian
diffstat for haproxy-3.0.9 haproxy-3.0.10

 CHANGELOG                    |   49 ++++++++++++
 SUBVERS                      |    2 
 VERDATE                      |    4 -
 VERSION                      |    2 
 debian/changelog             |    9 ++
 doc/configuration.txt        |   33 +++++++-
 include/haproxy/compiler.h   |    1 
 include/haproxy/fd-t.h       |    6 +
 include/haproxy/fd.h         |    4 +
 include/haproxy/mux_fcgi-t.h |    3 
 include/haproxy/quic_conn.h  |   10 +-
 include/haproxy/task-t.h     |    9 +-
 include/haproxy/task.h       |   62 ++++++++++++----
 include/import/plock.h       |   20 ++++-
 src/backend.c                |   36 ++++++---
 src/cli.c                    |    4 -
 src/debug.c                  |   17 +++-
 src/ev_epoll.c               |   95 ++++++++++++++++++++++++
 src/fd.c                     |    7 +
 src/h3.c                     |   84 ++++++++++++++++++++--
 src/hlua.c                   |   21 ++++-
 src/hlua_fcn.c               |   58 ++-------------
 src/http_ana.c               |   46 ++++++++----
 src/log.c                    |   41 ++++++----
 src/mux_fcgi.c               |  164 ++++++++++++++++++++++++++++++-------------
 src/mux_h2.c                 |   12 +++
 src/mux_quic.c               |   41 ++++++----
 src/peers.c                  |    5 +
 src/proto_rhttp.c            |    2 
 src/sample.c                 |    2 
 src/sink.c                   |    2 
 src/stick_table.c            |   13 +++
 src/stream.c                 |   29 +++++++
 src/tools.c                  |   10 +-
 tests/exp/filltab25.c        |    2 
 35 files changed, 704 insertions(+), 201 deletions(-)

diff -Nru haproxy-3.0.9/CHANGELOG haproxy-3.0.10/CHANGELOG
--- haproxy-3.0.9/CHANGELOG	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/CHANGELOG	2025-04-22 14:53:02.000000000 +0300
@@ -1,6 +1,55 @@
 ChangeLog :
 ===========
 
+2025/04/22 : 3.0.10
+    - MINOR: log: support "raw" logformat node typecast
+    - BUG/MINOR: peers: fix expire learned from a peer not converted from ms to ticks
+    - BUG/MEDIUM: peers: prevent learning expiration too far in futur from unsync node
+    - BUG/MEDIUM: mux-quic: fix crash on RS/SS emission if already close local
+    - BUG/MINOR: mux-quic: remove extra BUG_ON() in _qcc_send_stream()
+    - BUG/MINOR: log: fix gcc warn about truncating NUL terminator while init char arrays
+    - DOC: config: fix two missing "content" in "tcp-request" examples
+    - BUILD: compiler: undefine the CONCAT() macro if already defined
+    - BUG/MINOR: rhttp: fix incorrect dst/dst_port values
+    - BUG/MINOR: backend: do not overwrite srv dst address on reuse
+    - BUG/MEDIUM: backend: fix reuse with set-dst/set-dst-port
+    - BUG/MEDIUM: stream: Fix a possible freeze during a forced shut on a stream
+    - TESTS: Fix build for filltab25.c
+    - MINOR: task: add thread safe notification_new and notification_wake variants
+    - BUG/MINOR: hlua_fcn: fix potential UAF with Queue:pop_wait()
+    - CLEANUP: log: adjust _lf_cbor_encode_byte() comment
+    - BUG/MINOR: log: fix CBOR encoding with LOG_VARTEXT_START() + lf_encode_chunk()
+    - BUG/MEDIUM: sample: fix risk of overflow when replacing multiple regex back-refs
+    - BUG/MINOR: backend: do not use the source port when hashing clientip
+    - BUG/MINOR: hlua: fix invalid errmsg use in hlua_init()
+    - DOC: config: add the missing "profiling.memory" to the global kw index
+    - BUG/MINOR: http-ana: Properly detect client abort when forwarding the response
+    - BUG/MEDIUM: http-ana: Report 502 from req analyzer only during rsp forwarding
+    - BUG/MINOR: sink: add tempo between 2 connection attempts for sft servers (2)
+    - BUG/MEDIUM: h3: trim whitespaces when parsing headers value
+    - BUG/MEDIUM: h3: trim whitespaces in header value prior to QPACK encoding
+    - BUG/MINOR: h3: filter upgrade connection header
+    - BUG/MINOR: h3: reject invalid :path in request
+    - BUG/MINOR: h3: reject request URI with invalid characters
+    - BUG/MEDIUM: hlua: fix hlua_applet_{http,tcp}_fct() yield regression (lost data)
+    - BUG/MINOR: quic: do not crash on CRYPTO ncbuf alloc failure
+    - DEBUG: stream: Add debug counters to track some client/server aborts
+    - BUG/MINOR: stktable: invalid use of stkctr_set_entry() with mixed table types
+    - BUG/MEDIUM: mux-fcgi: Properly handle read0 on partial records
+    - DEBUG: fd: add a counter of takeovers of an FD since it was last opened
+    - MINOR: fd: add a generation number to file descriptors
+    - MINOR: epoll: permit to mask certain specific events
+    - DEBUG: epoll: store and compare the FD's generation count with reported event
+    - MEDIUM: epoll: skip reports of stale file descriptors
+    - IMPORT: plock: give higher precedence to W than S
+    - IMPORT: plock: lower the slope of the exponential back-off
+    - IMPORT: plock: use cpu_relax() for a shorter time in EBO
+    - BUG/MINOR debug: fix !USE_THREAD_DUMP in ha_thread_dump_fill()
+    - BUG/MINOR: mux-h2: prevent past scheduling with idle connections
+    - BUG/MINOR: rhttp: fix reconnect if timeout connect unset
+    - BUG/MINOR: rhttp: ensure GOAWAY can be emitted after reversal
+    - MINOR: tools: also protect the library name resolution against concurrent accesses
+
 2025/03/20 : 3.0.9
     - BUG/MEDIUM: ssl: chosing correct certificate using RSA-PSS with TLSv1.3
     - BUG/MEDIUM: mux-quic: do not attach on already closed stream
diff -Nru haproxy-3.0.9/debian/changelog haproxy-3.0.10/debian/changelog
--- haproxy-3.0.9/debian/changelog	2025-03-22 18:21:15.000000000 +0200
+++ haproxy-3.0.10/debian/changelog	2025-04-23 21:47:45.000000000 +0300
@@ -1,3 +1,12 @@
+haproxy (3.0.10-0.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * New upstream release.
+    - CVE-2025-32464: heap buffer overflow in sample_conv_regsub()
+      (Closes: #1102673)
+
+ -- Adrian Bunk <b...@debian.org>  Wed, 23 Apr 2025 21:47:45 +0300
+
 haproxy (3.0.9-1) unstable; urgency=medium
 
   * New upstream release.
diff -Nru haproxy-3.0.9/doc/configuration.txt haproxy-3.0.10/doc/configuration.txt
--- haproxy-3.0.9/doc/configuration.txt	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/doc/configuration.txt	2025-04-22 14:53:02.000000000 +0300
@@ -3,7 +3,7 @@
                           Configuration Manual
                          ----------------------
                               version 3.0
-                              2025/03/20
+                              2025/04/22
 
 
 This document covers the configuration language as implemented in the version
@@ -1409,6 +1409,7 @@
    - nopoll
    - noreuseport
    - nosplice
+   - profiling.memory
    - profiling.tasks
    - server-state-base
    - server-state-file
@@ -1422,6 +1423,7 @@
    - tune.comp.maxlevel
    - tune.disable-fast-forward
    - tune.disable-zero-copy-forwarding
+   - tune.epoll.mask-events
    - tune.events.max-events-at-once
    - tune.fail-alloc
    - tune.fd.edge-triggered
@@ -3328,6 +3330,31 @@
             tune.h1.zero-copy-fwd-recv, tune.h1.zero-copy-fwd-send,
             tune.h2.zero-copy-fwd-send, tune.quic.zero-copy-fwd-send
 
+tune.epoll.mask-events <event[,...]>
+  Along HAProxy's history, a few complex issues were met that were caused by
+  bugs in the epoll mechanism in the Linux kernel. These ones usually are very
+  rare and unreproducible outside the reporter's environment, and may only be
+  worked around by disabling epoll and switching to poll instead, which is not
+  very satisfying for high performance environments. Each time, issues affect
+  only very specific (and rare) event types, and offering the ability to mask
+  them can constitute a more acceptable work-around. This options offers this
+  possibility by permitting to silently ignore events a few uncommon events
+  and replace them with an input (which reports an unspecified incoming event).
+  The effect is to avoid the fast error processing paths in certain places and
+  only use the common paths. This should never be used unless being invited to
+  do so by an expert in order to diagnose or work around a kernel bug.
+
+  The option takes a single argument which is a comma-delimited list of words
+  each designating an event to be masked. The currently supported list of
+  events is:
+    - "err":   mask the EPOLLERR event
+    - "hup":   mask the EPOLLHUP events
+    - "rdhup": mask the EPOLLRDHUP events
+
+  Example:
+     # mask all non-traffic epoll events:
+     tune.epoll.mask-events err,hup,rdhup
+
 tune.events.max-events-at-once <number>
   Sets the number of events that may be processed at once by an asynchronous
   task handler (from event_hdl API). <number> should be included between 1
@@ -13396,8 +13423,8 @@
         # and reject everything else. (works for HTTP/1 and HTTP/2 connections)
         acl is_host_com hdr(Host) -i example.com
         tcp-request inspect-delay 5s
-        tcp-request switch-mode http if HTTP
-        tcp-request reject   # non-HTTP traffic is implicit here
+        tcp-request content switch-mode http if HTTP
+        tcp-request content reject   # non-HTTP traffic is implicit here
         ...
         http-request reject unless is_host_com
 
diff -Nru haproxy-3.0.9/include/haproxy/compiler.h haproxy-3.0.10/include/haproxy/compiler.h
--- haproxy-3.0.9/include/haproxy/compiler.h	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/include/haproxy/compiler.h	2025-04-22 14:53:02.000000000 +0300
@@ -248,6 +248,7 @@
 #define TOSTR(x) _TOSTR(x)
 
 /* concatenates the two strings after resolving possible macros */
+#undef CONCAT // Turns out NetBSD defines it to the same in exec_elf.h
 #define _CONCAT(a,b) a ## b
 #define CONCAT(a,b) _CONCAT(a,b)
 
diff -Nru haproxy-3.0.9/include/haproxy/fd.h haproxy-3.0.10/include/haproxy/fd.h
--- haproxy-3.0.9/include/haproxy/fd.h	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/include/haproxy/fd.h	2025-04-22 14:53:02.000000000 +0300
@@ -489,6 +489,10 @@
 	fdtab[fd].iocb = iocb;
 	fdtab[fd].state = newstate;
 	fdtab[fd].thread_mask = thread_mask;
+
+	/* just for debugging: how many times taken over since last fd_insert() */
+	fdtab[fd].nb_takeover = 0;
+
 	fd_drop_tgid(fd);
 
 #ifdef DEBUG_FD
diff -Nru haproxy-3.0.9/include/haproxy/fd-t.h haproxy-3.0.10/include/haproxy/fd-t.h
--- haproxy-3.0.9/include/haproxy/fd-t.h	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/include/haproxy/fd-t.h	2025-04-22 14:53:02.000000000 +0300
@@ -193,6 +193,12 @@
 	void *owner;                         /* the connection or listener associated with this fd, NULL if closed */
 	unsigned int state;                  /* FD state for read and write directions (FD_EV_*) + FD_POLL_* */
 	unsigned int refc_tgid;              /* refcounted tgid, updated atomically */
+	/* the info below are mainly used for epoll debugging/strengthening.
+	 * they're filling the rest of the cache line but may easily be dropped
+	 * if the room is needed for more important stuff.
+	 */
+	unsigned int nb_takeover;            /* number of times this FD was taken over since inserted (used for debugging) */
+	unsigned int generation;             /* number of times this FD was closed before (used for epoll strengthening) */
 #ifdef DEBUG_FD
 	unsigned int event_count;            /* number of events reported */
 #endif
diff -Nru haproxy-3.0.9/include/haproxy/mux_fcgi-t.h haproxy-3.0.10/include/haproxy/mux_fcgi-t.h
--- haproxy-3.0.9/include/haproxy/mux_fcgi-t.h	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/include/haproxy/mux_fcgi-t.h	2025-04-22 14:53:02.000000000 +0300
@@ -58,6 +58,9 @@
 #define FCGI_CF_ERR_PENDING     0x00008000  /* A write error was detected (block sends but not reads) */
 #define FCGI_CF_ERROR           0x00010000  /* A read error was detected (handled has an abort) */
 
+#define FCGI_CF_DEM_SHORT_READ  0x00020000  /* demux blocked on incomplete frame */
+#define FCGI_CF_END_REACHED     0x00040000  /* pending data too short with RCVD_SHUT present */
+
 
 /* This function is used to report flags in debugging tools. Please reflect
  * below any single-bit flag addition above in the same order via the
diff -Nru haproxy-3.0.9/include/haproxy/quic_conn.h haproxy-3.0.10/include/haproxy/quic_conn.h
--- haproxy-3.0.9/include/haproxy/quic_conn.h	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/include/haproxy/quic_conn.h	2025-04-22 14:53:02.000000000 +0300
@@ -130,7 +130,11 @@
 
 }
 
-/* Allocate the underlying required memory for <ncbuf> non-contiguous buffer */
+/* Allocate the underlying required memory for <ncbuf> non-contiguous buffer.
+ * Does nothing if buffer is already allocated.
+ *
+ * Returns the buffer instance or NULL on allocation failure.
+ */
 static inline struct ncbuf *quic_get_ncbuf(struct ncbuf *ncbuf)
 {
 	struct buffer buf = BUF_NULL;
@@ -138,8 +142,8 @@
 	if (!ncb_is_null(ncbuf))
 		return ncbuf;
 
-	b_alloc(&buf, DB_MUX_RX);
-	BUG_ON(b_is_null(&buf));
+	if (!b_alloc(&buf, DB_MUX_RX))
+		return NULL;
 
 	*ncbuf = ncb_make(buf.area, buf.size, 0);
 	ncb_init(ncbuf, 0);
diff -Nru haproxy-3.0.9/include/haproxy/task.h haproxy-3.0.10/include/haproxy/task.h
--- haproxy-3.0.9/include/haproxy/task.h	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/include/haproxy/task.h	2025-04-22 14:53:02.000000000 +0300
@@ -751,6 +751,17 @@
 	}
 }
 
+static inline struct notification *_notification_new(struct list *purge, struct task *wakeup)
+{
+	struct notification *com = pool_alloc(pool_head_notification);
+	if (!com)
+		return NULL;
+	LIST_APPEND(purge, &com->purge_me);
+	HA_SPIN_INIT(&com->lock);
+	com->task = wakeup;
+	return com;
+}
+
 /* This function register a new signal. "lua" is the current lua
  * execution context. It contains a pointer to the associated task.
  * "link" is a list head attached to an other task that must be wake
@@ -760,13 +771,20 @@
  */
 static inline struct notification *notification_new(struct list *purge, struct list *event, struct task *wakeup)
 {
-	struct notification *com = pool_alloc(pool_head_notification);
+	struct notification *com = _notification_new(purge, wakeup);
 	if (!com)
 		return NULL;
-	LIST_APPEND(purge, &com->purge_me);
 	LIST_APPEND(event, &com->wake_me);
-	HA_SPIN_INIT(&com->lock);
-	com->task = wakeup;
+	return com;
+}
+
+/* thread safe variant */
+static inline struct notification *notification_new_mt(struct list *purge, struct mt_list *event, struct task *wakeup)
+{
+	struct notification *com = _notification_new(purge, wakeup);
+	if (!com)
+		return NULL;
+	MT_LIST_APPEND(event, &com->wake_me_mt);
 	return com;
 }
 
@@ -815,6 +833,19 @@
 	}
 }
 
+static inline void _notification_wake(struct notification *com)
+{
+	HA_SPIN_LOCK(NOTIF_LOCK, &com->lock);
+	if (!com->task) {
+		HA_SPIN_UNLOCK(NOTIF_LOCK, &com->lock);
+		pool_free(pool_head_notification, com);
+		return;
+	}
+	task_wakeup(com->task, TASK_WOKEN_MSG);
+	com->task = NULL;
+	HA_SPIN_UNLOCK(NOTIF_LOCK, &com->lock);
+
+}
 /* This function sends signals. It wakes all the tasks attached
  * to a list head, and remove the signal, and free the used
  * memory. The wake list is not locked because it is owned by
@@ -827,16 +858,21 @@
 
 	/* Wake task and delete all pending communication signals. */
 	list_for_each_entry_safe(com, back, wake, wake_me) {
-		HA_SPIN_LOCK(NOTIF_LOCK, &com->lock);
 		LIST_DELETE(&com->wake_me);
-		if (!com->task) {
-			HA_SPIN_UNLOCK(NOTIF_LOCK, &com->lock);
-			pool_free(pool_head_notification, com);
-			continue;
-		}
-		task_wakeup(com->task, TASK_WOKEN_MSG);
-		com->task = NULL;
-		HA_SPIN_UNLOCK(NOTIF_LOCK, &com->lock);
+		_notification_wake(com);
+	}
+}
+
+/* thread safe variant */
+static inline void notification_wake_mt(struct mt_list *wake)
+{
+	struct notification *com;
+	struct mt_list *elt1, elt2;
+
+	/* Wake task and delete all pending communication signals. */
+	mt_list_for_each_entry_safe(com, wake, wake_me_mt, elt1, elt2) {
+		MT_LIST_DELETE_SAFE(elt1);
+		_notification_wake(com);
 	}
 }
 
diff -Nru haproxy-3.0.9/include/haproxy/task-t.h haproxy-3.0.10/include/haproxy/task-t.h
--- haproxy-3.0.9/include/haproxy/task-t.h	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/include/haproxy/task-t.h	2025-04-22 14:53:02.000000000 +0300
@@ -105,8 +105,13 @@
 struct notification {
 	struct list purge_me; /* Part of the list of signals to be purged in the
 	                         case of the LUA execution stack crash. */
-	struct list wake_me; /* Part of list of signals to be targeted if an
-	                        event occurs. */
+	union {
+		struct list wake_me; /* Part of list of signals to be targeted if an
+	                                event occurs. */
+		struct mt_list wake_me_mt; /* thread safe signal list */
+	} wake;
+# define wake_me wake.wake_me
+# define wake_me_mt wake.wake_me_mt
 	struct task *task; /* The task to be wake if an event occurs. */
 	__decl_thread(HA_SPINLOCK_T lock);
 };
diff -Nru haproxy-3.0.9/include/import/plock.h haproxy-3.0.10/include/import/plock.h
--- haproxy-3.0.9/include/import/plock.h	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/include/import/plock.h	2025-04-22 14:53:02.000000000 +0300
@@ -93,7 +93,7 @@
 			loops -= 32768;
 		}
 #endif
-		for (; loops >= 60; loops --)
+		for (; loops >= 90; loops --)
 			pl_cpu_relax();
 
 		for (; loops >= 1; loops--)
@@ -107,7 +107,7 @@
 		 * values and still growing. This allows competing threads to
 		 * wait different times once the threshold is reached.
 		 */
-		m = ((m + (m >> 1)) + 2) & 0x3ffff;
+		m = ((m + (m >> 2)) + 1) & 0x1ffff;
 	} while (1);
 
 	return ret;
@@ -176,7 +176,7 @@
 		 * values and still growing. This allows competing threads to
 		 * wait different times once the threshold is reached.
 		 */
-		m = ((m + (m >> 1)) + 2) & 0x3ffff;
+		m = ((m + (m >> 2)) + 1) & 0x1ffff;
 	} while (1);
 
 	return ret;
@@ -549,6 +549,13 @@
 			__pl_r = pl_ldadd_acq(__lk_r, __set_r);                                \
 			if (!__builtin_expect(__pl_r & __msk_r, 0))                            \
 				break;                                                         \
+			if (!__builtin_expect(__pl_r & PLOCK64_WL_ANY, 0)) {                   \
+				/* S only: let it finish but impose ourselves */               \
+				pl_sub_noret_lax(__lk_r, PLOCK64_RL_1);                        \
+				__pl_r = pl_wait_unlock_long(__lk_r, PLOCK64_RL_ANY);          \
+				__pl_r = pl_ldadd_acq(__lk_r, PLOCK64_RL_1);                   \
+				break;                                                         \
+			}                                                                      \
 			pl_sub_noret_lax(__lk_r, __set_r);                                     \
 			__pl_r = pl_wait_unlock_long(__lk_r, __msk_r);                         \
 		}                                                                              \
@@ -566,6 +573,13 @@
 			__pl_r = pl_ldadd_acq(__lk_r, __set_r);                                \
 			if (!__builtin_expect(__pl_r & __msk_r, 0))                            \
 				break;                                                         \
+			if (!__builtin_expect(__pl_r & PLOCK32_WL_ANY, 0)) {                   \
+				/* S only: let it finish but impose ourselves */               \
+				pl_sub_noret_lax(__lk_r, PLOCK32_RL_1);                        \
+				__pl_r = pl_wait_unlock_int(__lk_r, PLOCK32_RL_ANY);          \
+				__pl_r = pl_ldadd_acq(__lk_r, PLOCK32_RL_1);                   \
+				break;                                                         \
+			}                                                                      \
 			pl_sub_noret_lax(__lk_r, __set_r);                                     \
 			__pl_r = pl_wait_unlock_int(__lk_r, __msk_r);                          \
 		}                                                                              \
diff -Nru haproxy-3.0.9/src/backend.c haproxy-3.0.10/src/backend.c
--- haproxy-3.0.9/src/backend.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/backend.c	2025-04-22 14:53:02.000000000 +0300
@@ -858,13 +858,14 @@
 	return err;
 }
 
-/* Allocate an address for the destination endpoint
- * The address is taken from the currently assigned server, or from the
- * dispatch or transparent address.
+/* Allocate <*ss> address unless already set. Address is then set to the
+ * destination endpoint of <srv> server, or via <s> from a dispatch or
+ * transparent address.
  *
- * Returns SRV_STATUS_OK on success. Does nothing if the address was
- * already set.
- * On error, no address is allocated and SRV_STATUS_INTERNAL is returned.
+ * Note that no address is allocated if server relies on reverse HTTP.
+ *
+ * Returns SRV_STATUS_OK on success, or if already already set. Else an error
+ * code is returned and <*ss> is not allocated.
  */
 static int alloc_dst_address(struct sockaddr_storage **ss,
                              struct server *srv, struct stream *s)
@@ -874,6 +875,11 @@
 	if (*ss)
 		return SRV_STATUS_OK;
 
+	if (srv && (srv->flags & SRV_F_RHTTP)) {
+		/* For reverse HTTP, destination address is unknown. */
+		return SRV_STATUS_OK;
+	}
+
 	if ((s->flags & SF_DIRECT) || (s->be->lbprm.algo & BE_LB_KIND)) {
 		/* A server is necessarily known for this stream */
 		if (!(s->flags & SF_ASSIGNED))
@@ -1151,6 +1157,15 @@
 			return SRV_STATUS_INTERNAL;
 
 		**ss = *addr;
+		if ((src->opts & CO_SRC_TPROXY_MASK) == CO_SRC_TPROXY_CIP) {
+			/* always set port to zero when using "clientip", or
+			 * the idle connection hash will include the port part.
+			 */
+			if (addr->ss_family == AF_INET)
+				((struct sockaddr_in *)*ss)->sin_port = 0;
+			else if (addr->ss_family == AF_INET6)
+				((struct sockaddr_in6 *)*ss)->sin6_port = 0;
+		}
 		break;
 
 	case CO_SRC_TPROXY_DYN:
@@ -1433,8 +1448,7 @@
 	}
 
 	/* 3. destination address */
-	if (srv && srv_is_transparent(srv))
-		hash_params.dst_addr = s->scb->dst;
+	hash_params.dst_addr = s->scb->dst;
 
 	/* 4. source address */
 	hash_params.src_addr = bind_addr;
@@ -1665,6 +1679,9 @@
 			srv_conn->src = bind_addr;
 			bind_addr = NULL;
 
+			/* copy the target address into the connection */
+			*srv_conn->dst = *s->scb->dst;
+
 			/* mark? */
 			if (s->flags & SF_BC_MARK) {
 				srv_conn->mark = s->bc_mark;
@@ -1688,9 +1705,6 @@
 	if (!srv_conn)
 		return SF_ERR_RESOURCE;
 
-	/* copy the target address into the connection */
-	*srv_conn->dst = *s->scb->dst;
-
 	/* Copy network namespace from client connection */
 	srv_conn->proxy_netns = cli_conn ? cli_conn->proxy_netns : NULL;
 
diff -Nru haproxy-3.0.9/src/cli.c haproxy-3.0.10/src/cli.c
--- haproxy-3.0.9/src/cli.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/cli.c	2025-04-22 14:53:02.000000000 +0300
@@ -1411,7 +1411,7 @@
 			suspicious = 1;
 
 		chunk_printf(&trash,
-			     "  %5d : st=0x%06x(%c%c %c%c%c%c%c W:%c%c%c R:%c%c%c) ref=%#x gid=%d tmask=0x%lx umask=0x%lx prmsk=0x%lx pwmsk=0x%lx owner=%p iocb=%p(",
+			     "  %5d : st=0x%06x(%c%c %c%c%c%c%c W:%c%c%c R:%c%c%c) ref=%#x gid=%d tmask=0x%lx umask=0x%lx prmsk=0x%lx pwmsk=0x%lx owner=%p gen=%u tkov=%u iocb=%p(",
 			     fd,
 			     fdt.state,
 			     (fdt.state & FD_CLONED) ? 'C' : 'c',
@@ -1433,6 +1433,8 @@
 			     polled_mask[fd].poll_recv,
 			     polled_mask[fd].poll_send,
 			     fdt.owner,
+			     fdt.generation,
+			     fdt.nb_takeover,
 			     fdt.iocb);
 		resolve_sym_name(&trash, NULL, fdt.iocb);
 
diff -Nru haproxy-3.0.9/src/debug.c haproxy-3.0.10/src/debug.c
--- haproxy-3.0.9/src/debug.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/debug.c	2025-04-22 14:53:02.000000000 +0300
@@ -348,6 +348,7 @@
 {
 	struct buffer *old = NULL;
 
+#ifdef USE_THREAD_DUMP
 	/* A thread that's currently dumping other threads cannot be dumped, or
 	 * it will very likely cause a deadlock.
 	 */
@@ -366,14 +367,12 @@
 		old = NULL;
 	} while (!HA_ATOMIC_CAS(&ha_thread_ctx[thr].thread_dump_buffer, &old, buf));
 
-#ifdef USE_THREAD_DUMP
 	/* asking the remote thread to dump itself allows to get more details
 	 * including a backtrace.
 	 */
 	if (thr != tid)
 		ha_tkill(thr, DEBUGSIG);
 	else
-#endif
 		ha_thread_dump_one(thr, thr != tid);
 
 	/* now wait for the dump to be done (or cancelled) */
@@ -388,6 +387,20 @@
 		}
 		ha_thread_relax();
 	}
+#else /* !USE_THREAD_DUMP below, we're on the target thread */
+	/* when thread-dump is not supported, we can only dump our own thread */
+	if (thr != tid)
+		return NULL;
+
+	/* the buffer might not be valid in case of a panic, since we
+	 * have to allocate it ourselves in this case.
+	 */
+	if ((ulong)buf == 0x2UL)
+		buf = get_trash_chunk();
+	HA_ATOMIC_STORE(&th_ctx->thread_dump_buffer, buf);
+	old = buf;
+	ha_thread_dump_one(tid, 0);
+#endif
 	return (struct buffer *)((ulong)old & ~0x1UL);
 }
 
diff -Nru haproxy-3.0.9/src/ev_epoll.c haproxy-3.0.10/src/ev_epoll.c
--- haproxy-3.0.9/src/ev_epoll.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/ev_epoll.c	2025-04-22 14:53:02.000000000 +0300
@@ -16,6 +16,7 @@
 
 #include <haproxy/activity.h>
 #include <haproxy/api.h>
+#include <haproxy/cfgparse.h>
 #include <haproxy/clock.h>
 #include <haproxy/fd.h>
 #include <haproxy/global.h>
@@ -28,6 +29,7 @@
 /* private data */
 static THREAD_LOCAL struct epoll_event *epoll_events = NULL;
 static int epoll_fd[MAX_THREADS] __read_mostly; // per-thread epoll_fd
+static uint epoll_mask = 0; // events to be masked and turned to EPOLLIN
 
 #ifndef EPOLLRDHUP
 /* EPOLLRDHUP was defined late in libc, and it appeared in kernel 2.6.17 */
@@ -150,7 +152,8 @@
 		ev.events |= EPOLLOUT;
 
  done:
-	ev.data.fd = fd;
+	ev.events &= ~epoll_mask;
+	ev.data.u64 = ((u64)fdtab[fd].generation << 32) + fd;
 	epoll_ctl(epoll_fd[tid], opcode, fd, &ev);
 }
 
@@ -249,9 +252,55 @@
 
 	for (count = 0; count < status; count++) {
 		unsigned int n, e;
+		uint64_t epoll_data;
+		uint ev_gen, fd_gen;
 
 		e = epoll_events[count].events;
-		fd = epoll_events[count].data.fd;
+		epoll_data = epoll_events[count].data.u64;
+
+		/* epoll_data contains the fd's generation in the 32 upper bits
+		 * and the fd in the 32 lower ones.
+		 */
+		fd = (uint32_t)epoll_data;
+		ev_gen = epoll_data >> 32;
+		fd_gen = _HA_ATOMIC_LOAD(&fdtab[fd].generation);
+
+		if (unlikely(ev_gen != fd_gen)) {
+			/* this is a stale report for an older instance of this FD,
+			 * we must ignore it.
+			 */
+
+			if (_HA_ATOMIC_LOAD(&fdtab[fd].owner)) {
+				ulong tmask = _HA_ATOMIC_LOAD(&fdtab[fd].thread_mask);
+				if (!(tmask & ti->ltid_bit)) {
+					/* thread has change. quite common, that's already handled
+					 * by fd_update_events(), let's just report sensitivive
+					 * events for statistics purposes.
+					 */
+					if (e & (EPOLLRDHUP|EPOLLHUP|EPOLLERR))
+						COUNT_IF(1, "epoll report of HUP/ERR on a stale fd reopened on another thread (harmless)");
+				} else {
+					/* same thread but different generation, this smells bad,
+					 * maybe that could be caused by crossed takeovers with a
+					 * close() in between or something like this, but this is
+					 * something fd_update_events() cannot detect. It still
+					 * remains relatively safe for HUP because we consider it
+					 * once we've read all pending data.
+					 */
+					if (e & EPOLLERR)
+						COUNT_IF(1, "epoll report of ERR on a stale fd reopened on the same thread (suspicious)");
+					else if (e & (EPOLLRDHUP|EPOLLHUP))
+						COUNT_IF(1, "epoll report of HUP on a stale fd reopened on the same thread (suspicious)");
+					else
+						COUNT_IF(1, "epoll report of a harmless event on a stale fd reopened on the same thread (suspicious)");
+				}
+			} else if (ev_gen + 1 != fd_gen) {
+				COUNT_IF(1, "epoll report of event on a closed recycled fd (rare)");
+			} else {
+				COUNT_IF(1, "epoll report of event on a just closed fd (harmless)");
+			}
+			continue;
+		}
 
 		if ((e & EPOLLRDHUP) && !(cur_poller.flags & HAP_POLL_F_RDHUP))
 			_HA_ATOMIC_OR(&cur_poller.flags, HAP_POLL_F_RDHUP);
@@ -259,6 +308,11 @@
 #ifdef DEBUG_FD
 		_HA_ATOMIC_INC(&fdtab[fd].event_count);
 #endif
+		if (e & epoll_mask) {
+			e |= EPOLLIN;
+			e &= ~epoll_mask;
+		}
+
 		n = ((e & EPOLLIN)    ? FD_EV_READY_R : 0) |
 		    ((e & EPOLLOUT)   ? FD_EV_READY_W : 0) |
 		    ((e & EPOLLRDHUP) ? FD_EV_SHUT_R  : 0) |
@@ -404,6 +458,43 @@
 	p->fork = _do_fork;
 }
 
+/* config parser for global "tune.epoll.mask-events", accepts "err", "hup", "rdhup" */
+static int cfg_parse_tune_epoll_mask_events(char **args, int section_type, struct proxy *curpx,
+                                            const struct proxy *defpx, const char *file, int line,
+                                            char **err)
+{
+	char *comma, *kw;
+
+	if (too_many_args(1, args, err, NULL))
+		return -1;
+
+	epoll_mask = 0;
+	for (kw = args[1]; kw && *kw; kw = comma) {
+		comma = strchr(kw, ',');
+		if (comma)
+			*(comma++) = 0;
+
+		if (strcmp(kw, "err") == 0)
+			epoll_mask |= EPOLLERR;
+		else if (strcmp(kw, "hup") == 0)
+			epoll_mask |= EPOLLHUP;
+		else if (strcmp(kw, "rdhup") == 0)
+			epoll_mask |= EPOLLRDHUP;
+		else {
+			memprintf(err, "'%s' expects a comma-delimited list of 'err', 'hup' and 'rdhup' but got '%s'.", args[0], kw);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+/* config keyword parsers */
+static struct cfg_kw_list cfg_kws = {ILH, {
+	{ CFG_GLOBAL, "tune.epoll.mask-events",   cfg_parse_tune_epoll_mask_events },
+	{ 0, NULL, NULL }
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
 INITCALL0(STG_REGISTER, _do_register);
 
 
diff -Nru haproxy-3.0.9/src/fd.c haproxy-3.0.10/src/fd.c
--- haproxy-3.0.9/src/fd.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/fd.c	2025-04-22 14:53:02.000000000 +0300
@@ -354,8 +354,10 @@
 	/* perform the close() call last as it's what unlocks the instant reuse
 	 * of this FD by any other thread.
 	 */
-	if (!fd_disown)
+	if (!fd_disown) {
+		fdtab[fd].generation++;
 		close(fd);
+	}
 	_HA_ATOMIC_DEC(&ha_used_fds);
 }
 
@@ -541,6 +543,9 @@
 	 */
 	fd_stop_recv(fd);
 
+	/* essentially for debugging */
+	fdtab[fd].nb_takeover++;
+
 	/* we're done with it */
 	HA_ATOMIC_AND(&fdtab[fd].running_mask, ~ti->ltid_bit);
 
diff -Nru haproxy-3.0.9/src/h3.c haproxy-3.0.10/src/h3.c
--- haproxy-3.0.9/src/h3.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/h3.c	2025-04-22 14:53:02.000000000 +0300
@@ -529,6 +529,7 @@
 	unsigned int flags = HTX_SL_F_NONE;
 	struct ist meth = IST_NULL, path = IST_NULL;
 	struct ist scheme = IST_NULL, authority = IST_NULL;
+	struct ist v;
 	int hdr_idx, ret;
 	int cookie = -1, last_cookie = -1, i;
 	const char *ctl;
@@ -732,6 +733,37 @@
 	flags |= HTX_SL_F_VER_11;
 	flags |= HTX_SL_F_XFER_LEN;
 
+	/* RFC 9114 4.3.1. Request Pseudo-Header Fields
+	 *
+	 * This pseudo-header field MUST NOT be empty for "http" or "https"
+	 * URIs; "http" or "https" URIs that do not contain a path component
+	 * MUST include a value of / (ASCII 0x2f). An OPTIONS request that
+	 * does not include a path component includes the value * (ASCII
+	 * 0x2a) for the :path pseudo-header field; see Section 7.1 of
+	 * [HTTP].
+	 */
+	if ((isteqi(scheme, ist("http")) || isteqi(scheme, ist("https"))) &&
+	    (!istlen(path) ||
+	     (istptr(path)[0] != '/' && !isteq(path, ist("*"))))) {
+		TRACE_ERROR("invalid ':path' pseudo-header", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
+		h3s->err = H3_ERR_MESSAGE_ERROR;
+		qcc_report_glitch(h3c->qcc, 1);
+		len = -1;
+		goto out;
+	}
+
+	/* Ensure that final URI does not contains LWS nor CTL characters. */
+	for (i = 0; i < path.len; i++) {
+		unsigned char c = istptr(path)[i];
+		if (HTTP_IS_LWS(c) || HTTP_IS_CTL(c)) {
+			TRACE_ERROR("invalid character in path", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
+			h3s->err = H3_ERR_MESSAGE_ERROR;
+			qcc_report_glitch(h3c->qcc, 1);
+			len = -1;
+			goto out;
+		}
+	}
+
 	sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth, path, ist("HTTP/3.0"));
 	if (!sl) {
 		len = -1;
@@ -838,6 +870,7 @@
 		else if (isteq(list[hdr_idx].n, ist("connection")) ||
 		         isteq(list[hdr_idx].n, ist("proxy-connection")) ||
 		         isteq(list[hdr_idx].n, ist("keep-alive")) ||
+			 isteq(list[hdr_idx].n, ist("upgrade")) ||
 		         isteq(list[hdr_idx].n, ist("transfer-encoding"))) {
 			/* RFC 9114 4.2. HTTP Fields
 		         *
@@ -869,7 +902,15 @@
 			goto out;
 		}
 
-		if (!htx_add_header(htx, list[hdr_idx].n, list[hdr_idx].v)) {
+		/* trim leading/trailing LWS */
+		for (v = list[hdr_idx].v; v.len; v.len--) {
+			if (unlikely(HTTP_IS_LWS(*v.ptr)))
+				v.ptr++;
+			else if (!unlikely(HTTP_IS_LWS(v.ptr[v.len - 1])))
+				break;
+		}
+
+		if (!htx_add_header(htx, list[hdr_idx].n, v)) {
 			len = -1;
 			goto out;
 		}
@@ -972,6 +1013,7 @@
 	int hdr_idx, ret;
 	const char *ctl;
 	int qpack_err;
+	struct ist v;
 	int i;
 
 	TRACE_ENTER(H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
@@ -1048,6 +1090,7 @@
 		    isteq(list[hdr_idx].n, ist("connection")) ||
 		    isteq(list[hdr_idx].n, ist("proxy-connection")) ||
 		    isteq(list[hdr_idx].n, ist("keep-alive")) ||
+		    isteq(list[hdr_idx].n, ist("upgrade")) ||
 		    isteq(list[hdr_idx].n, ist("te")) ||
 		    isteq(list[hdr_idx].n, ist("transfer-encoding"))) {
 			TRACE_ERROR("forbidden HTTP/3 headers", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
@@ -1077,7 +1120,15 @@
 			goto out;
 		}
 
-		if (!htx_add_trailer(htx, list[hdr_idx].n, list[hdr_idx].v)) {
+		/* trim leading/trailing LWS */
+		for (v = list[hdr_idx].v; v.len; v.len--) {
+			if (unlikely(HTTP_IS_LWS(*v.ptr)))
+				v.ptr++;
+			else if (!unlikely(HTTP_IS_LWS(v.ptr[v.len - 1])))
+				break;
+		}
+
+		if (!htx_add_trailer(htx, list[hdr_idx].n, v)) {
 			TRACE_ERROR("cannot add trailer", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
 			len = -1;
 			goto out;
@@ -1565,6 +1616,29 @@
 	return -1;
 }
 
+/* Encode header field name <n> value <v> into <buf> buffer using QPACK. Strip
+ * any leading/trailing WS in value prior to encoding.
+ *
+ * Returns 0 on success else non zero.
+ */
+static int h3_encode_header(struct buffer *buf,
+                            const struct ist n, const struct ist v)
+{
+	struct ist v_strip;
+	char *ptr;
+
+	/* trim leading/trailing LWS */
+	for (v_strip = v; istlen(v_strip); --v_strip.len) {
+		ptr = istptr(v_strip);
+		if (unlikely(HTTP_IS_LWS(*ptr)))
+			++v_strip.ptr;
+		else if (!unlikely(HTTP_IS_LWS(ptr[istlen(v_strip) - 1])))
+			break;
+	}
+
+	return qpack_encode_header(buf, n, v_strip);
+}
+
 static int h3_resp_headers_send(struct qcs *qcs, struct htx *htx)
 {
 	int err;
@@ -1662,6 +1736,7 @@
 		if (isteq(list[hdr].n, ist("connection")) ||
 		    isteq(list[hdr].n, ist("proxy-connection")) ||
 		    isteq(list[hdr].n, ist("keep-alive")) ||
+		    isteq(list[hdr].n, ist("upgrade")) ||
 		    isteq(list[hdr].n, ist("transfer-encoding"))) {
 			continue;
 		}
@@ -1675,7 +1750,7 @@
 			list[hdr].v = ist("trailers");
 		}
 
-		if (qpack_encode_header(&headers_buf, list[hdr].n, list[hdr].v))
+		if (h3_encode_header(&headers_buf, list[hdr].n, list[hdr].v))
 			goto err;
 	}
 
@@ -1824,12 +1899,13 @@
 		    isteq(list[hdr].n, ist("connection")) ||
 		    isteq(list[hdr].n, ist("proxy-connection")) ||
 		    isteq(list[hdr].n, ist("keep-alive")) ||
+		    isteq(list[hdr].n, ist("upgrade")) ||
 		    isteq(list[hdr].n, ist("te")) ||
 		    isteq(list[hdr].n, ist("transfer-encoding"))) {
 			continue;
 		}
 
-		if (qpack_encode_header(&headers_buf, list[hdr].n, list[hdr].v)) {
+		if (h3_encode_header(&headers_buf, list[hdr].n, list[hdr].v)) {
 			TRACE_STATE("not enough room for all trailers", H3_EV_TX_FRAME|H3_EV_TX_HDR, qcs->qcc->conn, qcs);
 			if (qcc_release_stream_txbuf(qcs))
 				goto end;
diff -Nru haproxy-3.0.9/src/hlua.c haproxy-3.0.10/src/hlua.c
--- haproxy-3.0.9/src/hlua.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/hlua.c	2025-04-22 14:53:02.000000000 +0300
@@ -10897,6 +10897,7 @@
 	struct act_rule *rule = ctx->rule;
 	struct proxy *px = strm->be;
 	struct hlua *hlua = tcp_ctx->hlua;
+	int yield = 0;
 
 	if (unlikely(se_fl_test(ctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW))))
 		goto out;
@@ -10915,6 +10916,7 @@
 
 	/* yield. */
 	case HLUA_E_AGAIN:
+		yield = 1;
 		if (hlua->wake_time != TICK_ETERNITY)
 			task_schedule(tcp_ctx->task, hlua->wake_time);
 		break;
@@ -10960,7 +10962,12 @@
 	}
 
 out:
-	/* eat the whole request */
+	/* eat the whole request unless yield was requested which means
+	 * we are not done yet
+	 */
+	if (yield)
+		return;
+
 	if (ctx->flags & APPCTX_FL_INOUT_BUFS)
 		b_reset(&ctx->inbuf);
 	else
@@ -11102,6 +11109,7 @@
 	struct proxy *px = strm->be;
 	struct hlua *hlua = http_ctx->hlua;
 	struct htx *req_htx, *res_htx;
+	int yield = 0;
 
 	res_htx = htx_from_buf(&res->buf);
 
@@ -11136,6 +11144,7 @@
 
 		/* yield. */
 		case HLUA_E_AGAIN:
+			yield = 1;
 			if (hlua->wake_time != TICK_ETERNITY)
 				task_schedule(http_ctx->task, hlua->wake_time);
 			goto out;
@@ -11208,7 +11217,13 @@
 
   out:
 	htx_to_buf(res_htx, &res->buf);
-	/* eat the whole request */
+
+	/* eat the whole request unless yield was requested which means
+	 * we are not done yet
+	 */
+	if (yield)
+		return;
+
 	if (co_data(req)) {
 		req_htx = htx_from_buf(&req->buf);
 		co_htx_skip(req, req_htx, co_data(req));
@@ -14290,7 +14305,7 @@
 
 void hlua_init(void) {
 	int i;
-	char *errmsg;
+	char *errmsg = NULL;
 #ifdef USE_OPENSSL
 	struct srv_kw *kw;
 	int tmp_error;
diff -Nru haproxy-3.0.9/src/hlua_fcn.c haproxy-3.0.10/src/hlua_fcn.c
--- haproxy-3.0.9/src/hlua_fcn.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/hlua_fcn.c	2025-04-22 14:53:02.000000000 +0300
@@ -513,22 +513,10 @@
 	struct mt_list list;
 };
 
-/* used to store wait entries in queue->wait_tasks */
-struct hlua_queue_wait
-{
-	struct task *task;
-	struct mt_list entry;
-};
-
 /* This is the memory pool containing struct hlua_queue_item (queue items)
  */
 DECLARE_STATIC_POOL(pool_head_hlua_queue, "hlua_queue", sizeof(struct hlua_queue_item));
 
-/* This is the memory pool containing struct hlua_queue_wait
- * (queue waiting tasks)
- */
-DECLARE_STATIC_POOL(pool_head_hlua_queuew, "hlua_queuew", sizeof(struct hlua_queue_wait));
-
 static struct hlua_queue *hlua_check_queue(lua_State *L, int ud)
 {
 	return hlua_checkudata(L, ud, class_queue_ref);
@@ -555,8 +543,6 @@
 {
 	struct hlua_queue *queue = hlua_check_queue(L, 1);
 	struct hlua_queue_item *item;
-	struct mt_list *elt1, elt2;
-	struct hlua_queue_wait *waiter;
 
 	if (lua_gettop(L) != 2 || lua_isnoneornil(L, 2)) {
 		luaL_error(L, "unexpected argument");
@@ -581,9 +567,7 @@
 	MT_LIST_APPEND(&queue->list, &item->list);
 
 	/* notify tasks waiting on queue:pop_wait() (if any) */
-	mt_list_for_each_entry_safe(waiter, &queue->wait_tasks, entry, elt1, elt2) {
-		task_wakeup(waiter->task, TASK_WOKEN_MSG);
-	}
+	notification_wake_mt(&queue->wait_tasks);
 
 	lua_pushboolean(L, 1);
 	return 1;
@@ -639,24 +623,25 @@
 static int _hlua_queue_pop_wait(lua_State *L, int status, lua_KContext ctx)
 {
 	struct hlua_queue *queue = hlua_check_queue(L, 1);
-	struct hlua_queue_wait *wait = lua_touserdata(L, 2);
 
 	/* new pop attempt */
 	if (!_hlua_queue_pop(L, queue)) {
+		struct hlua *hlua;
+
+		hlua = hlua_gethlua(L);
+		if (!notification_new_mt(&hlua->com, &queue->wait_tasks, hlua->task)) {
+			lua_pushnil(L);
+			return 1; /* memory error, return nil */
+		}
 		hlua_yieldk(L, 0, 0, _hlua_queue_pop_wait, TICK_ETERNITY, 0); // wait retry
 		return 0; // never reached, yieldk won't return
 	}
 
-	/* remove task from waiting list */
-	MT_LIST_DELETE(&wait->entry);
-	pool_free(pool_head_hlua_queuew, wait);
-
 	return 1; // success
 }
 static int hlua_queue_pop_wait(lua_State *L)
 {
 	struct hlua_queue *queue = hlua_check_queue(L, 1);
-	struct hlua_queue_wait *wait;
 	struct hlua *hlua;
 
 	BUG_ON(!queue);
@@ -676,21 +661,6 @@
 
 	/* no pending items, waiting required */
 
-	wait = pool_alloc(pool_head_hlua_queuew);
-	if (!wait) {
-		lua_pushnil(L);
-		return 1; /* memory error, return nil */
-	}
-
-	wait->task = hlua->task;
-	MT_LIST_INIT(&wait->entry);
-
-	/* add task to queue's wait list */
-	MT_LIST_TRY_APPEND(&queue->wait_tasks, &wait->entry);
-
-	/* push wait entry at index 2 on the stack (queue is already there) */
-	lua_pushlightuserdata(L, wait);
-
 	/* Go to waiting loop which immediately performs a new attempt to make
 	 * sure we didn't miss a push during the wait entry initialization.
 	 *
@@ -736,20 +706,8 @@
 static int hlua_queue_gc(struct lua_State *L)
 {
 	struct hlua_queue *queue = hlua_check_queue(L, 1);
-	struct hlua_queue_wait *wait;
 	struct hlua_queue_item *item;
 
-	/* Purge waiting tasks (if any)
-	 *
-	 * It is normally not expected to have waiting tasks, except if such
-	 * task has been aborted while in the middle of a queue:pop_wait()
-	 * function call.
-	 */
-	while ((wait = MT_LIST_POP(&queue->wait_tasks, typeof(wait), entry))) {
-		/* free the wait entry */
-		pool_free(pool_head_hlua_queuew, wait);
-	}
-
 	/* purge remaining (unconsumed) items in the queue */
 	while ((item = MT_LIST_POP(&queue->list, typeof(item), list))) {
 		/* free the queue item */
diff -Nru haproxy-3.0.9/src/http_ana.c haproxy-3.0.10/src/http_ana.c
--- haproxy-3.0.9/src/http_ana.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/http_ana.c	2025-04-22 14:53:02.000000000 +0300
@@ -501,8 +501,9 @@
 			if (!ret)
 				continue;
 		}
-		if (!http_apply_redirect_rule(rule, s, txn))
+		if (!http_apply_redirect_rule(rule, s, txn)) {
 			goto return_int_err;
+		}
 		goto done;
 	}
 
@@ -989,12 +990,15 @@
 
 	if ((s->scb->flags & SC_FL_SHUT_DONE) && co_data(req)) {
 		/* request errors are most likely due to the server aborting the
-		 * transfer.Bit handle server aborts only if there is no
-		 * response. Otherwise, let a change to forward the response
-		 * first.
+		 * transfer. But handle server aborts only if the response was
+		 * not received yet. Otherwise, let the response analyzer the
+		 * responsability to handle the error. It is especially
+		 * important to properly handle L7-retries but also K/A silent close.
 		 */
-		if (txn->rsp.msg_state >= HTTP_MSG_BODY && htx_is_empty(htxbuf(&s->res.buf)))
+		if (txn->rsp.msg_state >= HTTP_MSG_BODY && htx_is_empty(htxbuf(&s->res.buf))) {
+			COUNT_IF(1, "Server abort during request forwarding");
 			goto return_srv_abort;
+		}
 	}
 
 	http_end_request(s);
@@ -1026,17 +1030,23 @@
 
  missing_data_or_waiting:
 	/* stop waiting for data if the input is closed before the end */
-	if (msg->msg_state < HTTP_MSG_ENDING && (s->scf->flags & (SC_FL_ABRT_DONE|SC_FL_EOS)))
+	if (msg->msg_state < HTTP_MSG_ENDING && (s->scf->flags & (SC_FL_ABRT_DONE|SC_FL_EOS))) {
+		COUNT_IF(1, "Client abort during request forwarding");
 		goto return_cli_abort;
+	}
 
  waiting:
 	/* waiting for the last bits to leave the buffer */
 	if (s->scb->flags & SC_FL_SHUT_DONE) {
-		/* Handle server aborts only if there is no response. Otherwise,
-		 * let a change to forward the response first.
+		/* Handle server aborts only if the response was not received
+		 * yet. Otherwise, let the response analyzer the responsability
+		 * to handle the error. It is especially important to properly
+		 * handle L7-retries but also K/A silent close.
 		 */
-		if (htx_is_empty(htxbuf(&s->res.buf)))
+		if (txn->rsp.msg_state >= HTTP_MSG_BODY && htx_is_empty(htxbuf(&s->res.buf))) {
+			COUNT_IF(1, "Server abort during request forwarding");
 			goto return_srv_abort;
+		}
 	}
 
 	/* When TE: chunked is used, we need to get there again to parse remaining
@@ -1099,6 +1109,7 @@
 	if (objt_server(s->target))
 		_HA_ATOMIC_INC(&__objt_server(s->target)->counters.internal_errors);
 	status = 500;
+	COUNT_IF(1, "Internal error during request forwarding");
 	goto return_prx_cond;
 
   return_bad_req:
@@ -1106,6 +1117,7 @@
 	if (sess->listener && sess->listener->counters)
 		_HA_ATOMIC_INC(&sess->listener->counters->failed_req);
 	status = 400;
+	COUNT_IF(1, "Request parsing error during request forwarding");
 	/* fall through */
 
   return_prx_cond:
@@ -2137,6 +2149,7 @@
 	if ((s->scf->flags & SC_FL_SHUT_DONE) && co_data(res)) {
 		/* response errors are most likely due to the client aborting
 		 * the transfer. */
+		COUNT_IF(1, "Client abort during response forwarding");
 		goto return_cli_abort;
 	}
 
@@ -2150,8 +2163,10 @@
 	return 0;
 
   missing_data_or_waiting:
-	if (s->scf->flags & SC_FL_SHUT_DONE)
+	if (s->scf->flags & SC_FL_SHUT_DONE) {
+		COUNT_IF(1, "Client abort during response forwarding");
 		goto return_cli_abort;
+	}
 
 	/* stop waiting for data if the input is closed before the end. If the
 	 * client side was already closed, it means that the client has aborted,
@@ -2159,12 +2174,15 @@
 	 * server abort.
 	 */
 	if (msg->msg_state < HTTP_MSG_ENDING && (s->scb->flags & (SC_FL_EOS|SC_FL_ABRT_DONE))) {
-		if ((s->scf->flags & SC_FL_ABRT_DONE) &&
-		    (s->scb->flags & SC_FL_SHUT_DONE))
+		if ((s->scb->flags & (SC_FL_ABRT_DONE|SC_FL_SHUT_DONE)) == (SC_FL_ABRT_DONE|SC_FL_SHUT_DONE)) {
+			COUNT_IF(1, "Client abort during response forwarding");
 			goto return_cli_abort;
+		}
 		/* If we have some pending data, we continue the processing */
-		if (htx_is_empty(htx))
+		if (htx_is_empty(htx)) {
+			COUNT_IF(1, "Server abort during response forwarding");
 			goto return_srv_abort;
+		}
 	}
 
 	/* When TE: chunked is used, we need to get there again to parse
@@ -2224,6 +2242,7 @@
 		_HA_ATOMIC_INC(&__objt_server(s->target)->counters.internal_errors);
 	if (!(s->flags & SF_ERR_MASK))
 		s->flags |= SF_ERR_INTERNAL;
+	COUNT_IF(1, "Internal error during response forwarding");
 	goto return_error;
 
   return_bad_res:
@@ -2233,6 +2252,7 @@
 		health_adjust(__objt_server(s->target), HANA_STATUS_HTTP_RSP);
 	}
 	stream_inc_http_fail_ctr(s);
+	COUNT_IF(1, "Response parsing error during response forwarding");
 	/* fall through */
 
    return_error:
diff -Nru haproxy-3.0.9/src/log.c haproxy-3.0.10/src/log.c
--- haproxy-3.0.9/src/log.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/log.c	2025-04-22 14:53:02.000000000 +0300
@@ -117,8 +117,8 @@
 	"warning", "notice", "info", "debug"
 };
 
-const char sess_term_cond[16] = "-LcCsSPRIDKUIIII"; /* normal, Local, CliTo, CliErr, SrvTo, SrvErr, PxErr, Resource, Internal, Down, Killed, Up, -- */
-const char sess_fin_state[8]  = "-RCHDLQT";	/* cliRequest, srvConnect, srvHeader, Data, Last, Queue, Tarpit */
+const char sess_term_cond[] = "-LcCsSPRIDKUIIII"; /* normal, Local, CliTo, CliErr, SrvTo, SrvErr, PxErr, Resource, Internal, Down, Killed, Up, -- */
+const char sess_fin_state[]  = "-RCHDLQT";        /* cliRequest, srvConnect, srvHeader, Data, Last, Queue, Tarpit */
 const struct buffer empty = { };
 
 
@@ -682,11 +682,17 @@
 				else {
 					/* custom type */
 					*str = 0; // so that typecast_str is 0 terminated
-					typecast = type_to_smp(typecast_str);
-					if (typecast != SMP_T_STR && typecast != SMP_T_SINT &&
-					    typecast != SMP_T_BOOL) {
-						memprintf(err, "unexpected output type '%.*s' at position %d line : '%s'. Supported types are: str, sint, bool", (int)(str - typecast_str), typecast_str, (int)(typecast_str - backfmt), fmt);
-						goto fail;
+					if (strcmp(typecast_str, "raw") == 0) {
+						/* special case */
+						typecast = SMP_TYPES;
+					}
+					else {
+						typecast = type_to_smp(typecast_str);
+						if (typecast != SMP_T_STR && typecast != SMP_T_SINT &&
+						    typecast != SMP_T_BOOL) {
+							memprintf(err, "unexpected output type '%.*s' at position %d line : '%s'. Supported types are: str, sint, bool, raw", (int)(str - typecast_str), typecast_str, (int)(typecast_str - backfmt), fmt);
+							goto fail;
+						}
 					}
 				}
 				cformat = LF_EDONAME;
@@ -1873,8 +1879,6 @@
 
 /* lf cbor function ptr used to encode a single byte according to RFC8949
  *
- * for now only hex form is supported.
- *
  * The function may only be called under CBOR context (that is when
  * LOG_OPT_ENCODE_CBOR option is set).
  *
@@ -2129,7 +2133,7 @@
 		encode_byte = _lf_map_escape_byte;
 
 	if (ctx->options & LOG_OPT_ENCODE_CBOR) {
-		if (!bytes_stop) {
+		if (!bytes_stop || ctx->in_text) {
 			/* printable chars: use cbor text */
 			cbor_string_prefix = 0x60;
 		}
@@ -3202,8 +3206,8 @@
 	return process_send_log(loggers, level, -1, metadata, message, size);
 }
 
-const char sess_cookie[8]     = "NIDVEOU7";	/* No cookie, Invalid cookie, cookie for a Down server, Valid cookie, Expired cookie, Old cookie, Unused, unknown */
-const char sess_set_cookie[8] = "NPDIRU67";	/* No set-cookie, Set-cookie found and left unchanged (passive),
+const char sess_cookie[]     = "NIDVEOU7";      /* No cookie, Invalid cookie, cookie for a Down server, Valid cookie, Expired cookie, Old cookie, Unused, unknown */
+const char sess_set_cookie[] = "NPDIRU67";      /* No set-cookie, Set-cookie found and left unchanged (passive),
 						   Set-cookie Deleted, Set-Cookie Inserted, Set-cookie Rewritten,
 						   Set-cookie Updated, unknown, unknown */
 
@@ -3822,9 +3826,16 @@
 			 * know how to deal with binary data.
 			 */
 			if (ctx->options & LOG_OPT_ENCODE) {
-				if (ctx->typecast == SMP_T_STR ||
-				    ctx->typecast == SMP_T_SINT ||
-				    ctx->typecast == SMP_T_BOOL) {
+				if (ctx->typecast == SMP_TYPES) {
+					/* raw data, ignore LOG opts and enforce binary
+					 * to dump smp as-is
+					 */
+					ctx->options = LOG_OPT_NONE;
+					type = SMP_T_BIN;
+				}
+				else if (ctx->typecast == SMP_T_STR ||
+				         ctx->typecast == SMP_T_SINT ||
+				         ctx->typecast == SMP_T_BOOL) {
 					/* enforce type */
 					type = ctx->typecast;
 				}
diff -Nru haproxy-3.0.9/src/mux_fcgi.c haproxy-3.0.10/src/mux_fcgi.c
--- haproxy-3.0.9/src/mux_fcgi.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/mux_fcgi.c	2025-04-22 14:53:02.000000000 +0300
@@ -438,41 +438,57 @@
 /* Indicates whether or not the we may call the fcgi_recv() function to attempt
  * to receive data into the buffer and/or demux pending data. The condition is
  * a bit complex due to some API limits for now. The rules are the following :
- *   - if an error or a shutdown was detected on the connection and the buffer
- *     is empty, we must not attempt to receive
+ *   - if an error or a shutdown was detected on the connection,
+       we must not attempt to receive
+ *   - if we're subscribed for receving, no need to try again
  *   - if the demux buf failed to be allocated, we must not try to receive and
- *     we know there is nothing pending
- *   - if no flag indicates a blocking condition, we may attempt to receive,
- *     regardless of whether the demux buffer is full or not, so that only
- *     de demux part decides whether or not to block. This is needed because
- *     the connection API indeed prevents us from re-enabling receipt that is
- *     already enabled in a polled state, so we must always immediately stop
- *     as soon as the demux can't proceed so as never to hit an end of read
- *     with data pending in the buffers.
- *   - otherwise must may not attempt
+ *     we know there is nothing pending (we'll be woken up once allocated)
+ *   - if the demux buf is full, we will not be able to receive.
+ *   - otherwise we may attempt to receive
  */
 static inline int fcgi_recv_allowed(const struct fcgi_conn *fconn)
 {
-	if (fconn->flags & (FCGI_CF_EOS|FCGI_CF_ERROR))
+	if (fconn->flags & (FCGI_CF_EOS|FCGI_CF_ERROR) || fconn->state == FCGI_CS_CLOSED)
 		return 0;
 
-	if (b_data(&fconn->dbuf) == 0 && fconn->state == FCGI_CS_CLOSED)
+	if ((fconn->wait_event.events & SUB_RETRY_RECV))
 		return 0;
 
-	if (!(fconn->flags & FCGI_CF_DEM_DALLOC) &&
-	    !(fconn->flags & FCGI_CF_DEM_BLOCK_ANY))
-		return 1;
+	if (!(fconn->flags & (FCGI_CF_DEM_DALLOC | FCGI_CF_DEM_DFULL)))
+	    return 1;
 
 	return 0;
 }
 
-/* Restarts reading on the connection if it was not enabled */
+/* Indicates whether it's worth waking up the I/O handler to restart demuxing.
+ * Its conditions are the following:
+ *   - if the buffer is empty and the connection is closed, there's nothing
+ *     to demux
+ *   - if a short read was reported, no need to try demuxing again
+ *   - if some blocking conditions remain, no need to try again
+ *   - otherwise it's safe to try demuxing again
+ */
+static inline int fcgi_may_demux(const struct fcgi_conn *fconn)
+{
+	if (fconn->state == FCGI_CS_CLOSED && !b_data(&fconn->dbuf))
+		return 0;
+
+	if (fconn->flags & FCGI_CF_DEM_SHORT_READ)
+		return 0;
+
+	if (fconn->flags & FCGI_CF_DEM_BLOCK_ANY)
+		return 0;
+
+	return 1;
+}
+
+
+/* restarts reading/processing on the connection if we can receive or demux
+ * (both are called from the same tasklet).
+ */
 static inline void fcgi_conn_restart_reading(const struct fcgi_conn *fconn, int consider_buffer)
 {
-	if (!fcgi_recv_allowed(fconn))
-		return;
-	if ((!consider_buffer || !b_data(&fconn->dbuf)) &&
-	    (fconn->wait_event.events & SUB_RETRY_RECV))
+	if (!fcgi_recv_allowed(fconn) && !fcgi_may_demux(fconn))
 		return;
 	tasklet_wakeup(fconn->wait_event.tasklet);
 }
@@ -783,15 +799,15 @@
 	}
 }
 
-/* Detect a pending read0 for a FCGI connection. It happens if a read0 is
- * pending on the connection AND if there is no more data in the demux
- * buffer. The function returns 1 to report a read0 or 0 otherwise.
+/* Detect a pending read0 for a FCGI connection. It happens if a read0 was
+ * already reported on a previous xprt->rcvbuf() AND a record parser failed
+ * to parse pending data, confirming no more progress is possible because
+ * we're facing a truncated frame. The function returns 1 to report a read0
+ * or 0 otherwise.
  */
 static int fcgi_conn_read0_pending(struct fcgi_conn *fconn)
 {
-	if ((fconn->flags & FCGI_CF_EOS) && !b_data(&fconn->dbuf))
-		return 1;
-	return 0;
+	return !!(fconn->flags & FCGI_CF_END_REACHED);
 }
 
 
@@ -1532,6 +1548,7 @@
 
 	/* process full record only */
 	if (b_data(dbuf) < (fconn->drl + fconn->drp)) {
+		fconn->flags |= FCGI_CF_DEM_SHORT_READ;
 		TRACE_DEVEL("leaving on missing data", FCGI_EV_RX_RECORD|FCGI_EV_RX_GETVAL, fconn->conn);
 		return 0;
 	}
@@ -2257,10 +2274,10 @@
 	if (fconn->state == FCGI_CS_RECORD_P)
 		goto end_transfer;
 
-	if (b_data(dbuf) < (fconn->drl + fconn->drp) &&
-	    b_size(dbuf) > (fconn->drl + fconn->drp) &&
-	    buf_room_for_htx_data(dbuf))
+	if (b_data(dbuf) < (fconn->drl + fconn->drp) && !b_full(dbuf)) {
+		fconn->flags |= FCGI_CF_DEM_SHORT_READ;
 		goto fail; // incomplete record
+	}
 
 	if (!fcgi_get_buf(fconn, &fstrm->rxbuf)) {
 		fconn->flags |= FCGI_CF_DEM_SALLOC;
@@ -2320,23 +2337,31 @@
 
 	TRACE_ENTER(FCGI_EV_RX_RECORD|FCGI_EV_RX_STDOUT, fconn->conn, fstrm);
 
+	if (b_data(&fconn->dbuf) < (fconn->drl + fconn->drp) && !b_full(&fconn->dbuf)) {
+		fconn->flags |= FCGI_CF_DEM_SHORT_READ;
+		goto fail; // incomplete record
+	}
+
 	fconn->state = FCGI_CS_RECORD_P;
 	TRACE_STATE("switching to RECORD_P", FCGI_EV_RX_RECORD|FCGI_EV_RX_STDOUT, fconn->conn, fstrm);
+
 	fconn->drl += fconn->drp;
 	fconn->drp = 0;
 	ret = MIN(b_data(&fconn->dbuf), fconn->drl);
 	b_del(&fconn->dbuf, ret);
 	fconn->drl -= ret;
-	if (fconn->drl) {
-		TRACE_DEVEL("leaving on missing data or error", FCGI_EV_RX_RECORD|FCGI_EV_RX_STDOUT, fconn->conn, fstrm);
-		return 0;
-	}
+	if (fconn->drl)
+		goto fail;
 	fconn->state = FCGI_CS_RECORD_H;
 	fstrm->flags |= FCGI_SF_ES_RCVD;
 	TRACE_PROTO("FCGI STDOUT record rcvd", FCGI_EV_RX_RECORD|FCGI_EV_RX_STDOUT, fconn->conn, fstrm, 0, (size_t[]){0});
 	TRACE_STATE("stdout data fully send, switching to RECORD_H", FCGI_EV_RX_RECORD|FCGI_EV_RX_FHDR|FCGI_EV_RX_EOI, fconn->conn, fstrm);
 	TRACE_LEAVE(FCGI_EV_RX_RECORD|FCGI_EV_RX_STDOUT, fconn->conn, fstrm);
 	return 1;
+
+  fail:
+	TRACE_DEVEL("leaving on missing data or error", FCGI_EV_RX_RECORD|FCGI_EV_RX_STDOUT, fconn->conn, fstrm);
+	return 0;
 }
 
 /* Processes a STDERR record. Returns > 0 on success, 0 if it couldn't do
@@ -2355,13 +2380,13 @@
 	if (fconn->state == FCGI_CS_RECORD_P || !fconn->drl)
 		goto end_transfer;
 
-	if (b_data(dbuf) < (fconn->drl + fconn->drp) &&
-	    b_size(dbuf) > (fconn->drl + fconn->drp) &&
-	    buf_room_for_htx_data(dbuf))
+	if (b_data(dbuf) < (fconn->drl + fconn->drp) && !b_full(dbuf)) {
+		fconn->flags |= FCGI_CF_DEM_SHORT_READ;
 		goto fail; // incomplete record
+	}
 
 	chunk_reset(&trash);
-	ret = b_force_xfer(&trash, dbuf, MIN(b_room(&trash), fconn->drl));
+	ret = b_force_xfer(&trash, dbuf, MIN(b_room(&trash) - 2, fconn->drl));
 	if (!ret)
 		goto fail;
 	fconn->drl -= ret;
@@ -2416,6 +2441,7 @@
 
 	/* process full record only */
 	if (b_data(dbuf) < (fconn->drl + fconn->drp)) {
+		fconn->flags |= FCGI_CF_DEM_SHORT_READ;
 		TRACE_DEVEL("leaving on missing data", FCGI_EV_RX_RECORD|FCGI_EV_RX_ENDREQ, fconn->conn);
 		return 0;
 	}
@@ -2477,6 +2503,7 @@
 			TRACE_STATE("receiving FCGI record header", FCGI_EV_RX_RECORD|FCGI_EV_RX_FHDR, fconn->conn);
 			ret = fcgi_decode_record_hdr(&fconn->dbuf, 0, &hdr);
 			if (!ret) {
+				fconn->flags |= FCGI_CF_DEM_SHORT_READ;
 				TRACE_ERROR("header record decoding failure", FCGI_EV_RX_RECORD|FCGI_EV_RX_ENDREQ|FCGI_EV_FSTRM_ERR, fconn->conn, fstrm);
 				goto fail;
 			}
@@ -2494,8 +2521,13 @@
 
 	/* process as many incoming records as possible below */
 	while (1) {
+		/* Make sure to clear DFULL if contents were deleted */
+		if (!b_full(&fconn->dbuf))
+			fconn->flags &= ~FCGI_CF_DEM_DFULL;
+
 		if (!b_data(&fconn->dbuf)) {
 			TRACE_DEVEL("no more Rx data", FCGI_EV_RX_RECORD, fconn->conn);
+			fconn->flags |= FCGI_CF_DEM_SHORT_READ;
 			break;
 		}
 
@@ -2507,8 +2539,10 @@
 		if (fconn->state == FCGI_CS_RECORD_H) {
 			TRACE_PROTO("receiving FCGI record header", FCGI_EV_RX_RECORD|FCGI_EV_RX_FHDR, fconn->conn);
 			ret = fcgi_decode_record_hdr(&fconn->dbuf, 0, &hdr);
-			if (!ret)
+			if (!ret) {
+				fconn->flags |= FCGI_CF_DEM_SHORT_READ;
 				break;
+			}
 			b_del(&fconn->dbuf, ret);
 
 		  new_record:
@@ -2520,6 +2554,10 @@
 			TRACE_STATE("FCGI record header rcvd, switching to RECORD_D", FCGI_EV_RX_RECORD|FCGI_EV_RX_FHDR, fconn->conn);
 		}
 
+		/* Make sure to clear DFULL if contents were deleted */
+		if (!b_full(&fconn->dbuf))
+			fconn->flags &= ~FCGI_CF_DEM_DFULL;
+
 		/* Only FCGI_CS_RECORD_D or FCGI_CS_RECORD_P */
 		tmp_fstrm = fcgi_conn_st_by_id(fconn, fconn->dsi);
 
@@ -2579,6 +2617,12 @@
 				 * larger than the buffer so we drain all of
 				 * their contents until we reach the end.
 				 */
+				if (b_data(&fconn->dbuf) < (fconn->drl + fconn->drp) && !b_full(&fconn->dbuf)) {
+					fconn->flags |= FCGI_CF_DEM_SHORT_READ;
+					ret = 0;
+					break;
+				}
+
 				fconn->state = FCGI_CS_RECORD_P;
 				fconn->drl += fconn->drp;
 				fconn->drp = 0;
@@ -2603,6 +2647,15 @@
 	}
 
  fail:
+	if (fconn->state == FCGI_CS_CLOSED || (fconn->flags & FCGI_CF_DEM_SHORT_READ)) {
+		if (fconn->flags & FCGI_CF_EOS)
+			fconn->flags |= FCGI_CF_END_REACHED;
+	}
+
+	/* Make sure to clear DFULL if contents were deleted */
+	if (!b_full(&fconn->dbuf))
+		fconn->flags &= ~FCGI_CF_DEM_DFULL;
+
 	/* we can go here on missing data, blocked response or error */
 	if (fstrm && fcgi_strm_sc(fstrm) &&
 	    (b_data(&fstrm->rxbuf) ||
@@ -2746,14 +2799,17 @@
 		TRACE_DATA("failed to receive data, subscribing", FCGI_EV_FCONN_RECV, conn);
 		conn->xprt->subscribe(conn, conn->xprt_ctx, SUB_RETRY_RECV, &fconn->wait_event);
 	}
-	else
+	else if (ret) {
+		fconn->flags &= ~FCGI_CF_DEM_SHORT_READ;
 		TRACE_DATA("recv data", FCGI_EV_FCONN_RECV, conn, 0, 0, (size_t[]){ret});
+	}
 
 	if (conn_xprt_read0_pending(conn)) {
 		TRACE_DATA("received read0", FCGI_EV_FCONN_RECV, conn);
 		fconn->flags |= FCGI_CF_EOS;
 	}
-	if (conn->flags & CO_FL_ERROR) {
+	if (conn->flags & CO_FL_ERROR &&
+	    (!b_data(&fconn->dbuf) || (fconn->flags & FCGI_CF_DEM_SHORT_READ))) {
 		TRACE_DATA("connection error", FCGI_EV_FCONN_RECV, conn);
 		fconn->flags |= FCGI_CF_ERROR;
 	}
@@ -3000,14 +3056,30 @@
 
 	TRACE_POINT(FCGI_EV_FCONN_WAKE, conn);
 
-	if (b_data(&fconn->dbuf) && !(fconn->flags & FCGI_CF_DEM_BLOCK_ANY)) {
-		fcgi_process_demux(fconn);
+	if (!(fconn->flags & FCGI_CF_DEM_BLOCK_ANY) &&
+	    (b_data(&fconn->dbuf) || (fconn->flags & FCGI_CF_EOS))) {
+		do {
+			fcgi_process_demux(fconn);
+
+			/* hint: if we ended up aligned on a record, we've very
+			 * likely reached the end, no point trying again.
+			 */
+			if (fconn->state == FCGI_CS_RECORD_H)
+				break;
+
+			if (!fcgi_recv_allowed(fconn))
+				break;
+
+			/* OK, it's worth trying to grab a few more records */
+			fcgi_recv(fconn);
+
+		} while ((b_data(&fconn->dbuf) && fcgi_may_demux(fconn)) || (fconn->flags & FCGI_CF_EOS));
+
+		/* now's time to wake the task up */
+		fcgi_conn_restart_reading(fconn, 0);
 
 		if (fconn->state == FCGI_CS_CLOSED || (fconn->flags & FCGI_CF_ERROR))
 			b_reset(&fconn->dbuf);
-
-		if (buf_room_for_htx_data(&fconn->dbuf))
-			fconn->flags &= ~FCGI_CF_DEM_DFULL;
 	}
 	fcgi_send(fconn);
 
diff -Nru haproxy-3.0.9/src/mux_h2.c haproxy-3.0.10/src/mux_h2.c
--- haproxy-3.0.9/src/mux_h2.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/mux_h2.c	2025-04-22 14:53:02.000000000 +0300
@@ -732,6 +732,16 @@
 	} else {
 		h2c->task->expire = TICK_ETERNITY;
 	}
+
+	/* Timer may already be expired, in this case it must not be requeued.
+	 * Currently it may happen with idle conn calculation based on shut or
+	 * http-req/ka timeouts.
+	 */
+	if (unlikely(tick_is_expired(h2c->task->expire, now_ms))) {
+		task_wakeup(h2c->task, TASK_WOKEN_TIMER);
+		goto leave;
+	}
+
 	task_queue(h2c->task);
  leave:
 	TRACE_LEAVE(H2_EV_H2C_WAKE);
@@ -3513,6 +3523,8 @@
 		struct proxy *prx = l->bind_conf->frontend;
 
 		h2c->flags &= ~H2_CF_IS_BACK;
+		/* Must manually init max_id so that GOAWAY can be emitted. */
+		h2c->max_id = 0;
 
 		h2c->shut_timeout = h2c->timeout = prx->timeout.client;
 		if (tick_isset(prx->timeout.clientfin))
diff -Nru haproxy-3.0.9/src/mux_quic.c haproxy-3.0.10/src/mux_quic.c
--- haproxy-3.0.9/src/mux_quic.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/mux_quic.c	2025-04-22 14:53:02.000000000 +0300
@@ -1148,6 +1148,25 @@
 	}
 }
 
+/* Register <qcs> stream for emission of STREAM, STOP_SENDING or RESET_STREAM.
+ * Set <urg> to true if stream should be emitted in priority. This is useful
+ * when sending STOP_SENDING or RESET_STREAM, or for emission on an application
+ * control stream.
+ */
+static void _qcc_send_stream(struct qcs *qcs, int urg)
+{
+	struct qcc *qcc = qcs->qcc;
+
+	if (urg) {
+		LIST_DEL_INIT(&qcs->el_send);
+		LIST_INSERT(&qcc->send_list, &qcs->el_send);
+	}
+	else {
+		if (!LIST_INLIST(&qcs->el_send))
+			LIST_APPEND(&qcs->qcc->send_list, &qcs->el_send);
+	}
+}
+
 /* Prepare for the emission of RESET_STREAM on <qcs> with error code <err>. */
 void qcc_reset_stream(struct qcs *qcs, int err)
 {
@@ -1186,14 +1205,13 @@
 		qcs_alert(qcs);
 	}
 
-	qcc_send_stream(qcs, 1, 0);
+	_qcc_send_stream(qcs, 1);
 	tasklet_wakeup(qcc->wait_event.tasklet);
 }
 
-/* Register <qcs> stream for emission of STREAM, STOP_SENDING or RESET_STREAM.
- * Set <urg> to 1 if stream content should be treated in priority compared to
- * other streams. For STREAM emission, <count> must contains the size of the
- * frame payload. This is used for flow control accounting.
+/* Register <qcs> stream for STREAM emission. Set <urg> to 1 if stream content
+ * should be treated in priority compared to other streams. <count> must
+ * contains the size of the frame payload, used for flow control accounting.
  */
 void qcc_send_stream(struct qcs *qcs, int urg, int count)
 {
@@ -1201,17 +1219,10 @@
 
 	TRACE_ENTER(QMUX_EV_QCS_SEND, qcc->conn, qcs);
 
-	/* Cannot send if already closed. */
+	/* Cannot send STREAM if already closed. */
 	BUG_ON(qcs_is_close_local(qcs));
 
-	if (urg) {
-		LIST_DEL_INIT(&qcs->el_send);
-		LIST_INSERT(&qcc->send_list, &qcs->el_send);
-	}
-	else {
-		if (!LIST_INLIST(&qcs->el_send))
-			LIST_APPEND(&qcs->qcc->send_list, &qcs->el_send);
-	}
+	_qcc_send_stream(qcs, urg);
 
 	if (count) {
 		qfctl_sinc(&qcc->tx.fc, count);
@@ -1234,7 +1245,7 @@
 	TRACE_STATE("abort stream read", QMUX_EV_QCS_END, qcc->conn, qcs);
 	qcs->flags |= (QC_SF_TO_STOP_SENDING|QC_SF_READ_ABORTED);
 
-	qcc_send_stream(qcs, 1, 0);
+	_qcc_send_stream(qcs, 1);
 	tasklet_wakeup(qcc->wait_event.tasklet);
 
  end:
diff -Nru haproxy-3.0.9/src/peers.c haproxy-3.0.10/src/peers.c
--- haproxy-3.0.9/src/peers.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/peers.c	2025-04-22 14:53:02.000000000 +0300
@@ -1740,6 +1740,11 @@
 		memcpy(&expire, *msg_cur, expire_sz);
 		*msg_cur += expire_sz;
 		expire = ntohl(expire);
+		/* Protocol contains expire in MS, check if value is less than table config */
+		if (expire > table->expire)
+			expire = table->expire;
+		/* the rest of the code considers expire as ticks and not MS */
+		expire = MS_TO_TICKS(expire);
 	}
 
 	newts = stksess_new(table, NULL);
diff -Nru haproxy-3.0.9/src/proto_rhttp.c haproxy-3.0.10/src/proto_rhttp.c
--- haproxy-3.0.9/src/proto_rhttp.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/proto_rhttp.c	2025-04-22 14:53:02.000000000 +0300
@@ -287,7 +287,7 @@
 		l->rx.rhttp.pend_conn = conn;
 
 		/* On success task will be woken up by H2 mux after reversal. */
-		l->rx.rhttp.task->expire = conn ?
+		l->rx.rhttp.task->expire = conn && tick_isset(srv->proxy->timeout.connect) ?
 		  tick_add_ifset(now_ms, srv->proxy->timeout.connect) :
 		  MS_TO_TICKS(now_ms + 1000);
 	}
diff -Nru haproxy-3.0.9/src/sample.c haproxy-3.0.10/src/sample.c
--- haproxy-3.0.9/src/sample.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/sample.c	2025-04-22 14:53:02.000000000 +0300
@@ -3163,7 +3163,7 @@
 		output->data = exp_replace(output->area, output->size, start, arg_p[1].data.str.area, pmatch);
 
 		/* replace the matching part */
-		max = output->size - output->data;
+		max = trash->size - trash->data;
 		if (max) {
 			if (max > output->data)
 				max = output->data;
diff -Nru haproxy-3.0.9/src/sink.c haproxy-3.0.10/src/sink.c
--- haproxy-3.0.9/src/sink.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/sink.c	2025-04-22 14:53:02.000000000 +0300
@@ -573,7 +573,7 @@
 	if (appctx_init(appctx) == -1)
 		goto out_free_appctx;
 
-	sft->last_conn = TICK_ETERNITY;
+	sft->last_conn = now_ms;
 	return appctx;
 
 	/* Error unrolling */
diff -Nru haproxy-3.0.9/src/stick_table.c haproxy-3.0.10/src/stick_table.c
--- haproxy-3.0.9/src/stick_table.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/stick_table.c	2025-04-22 14:53:02.000000000 +0300
@@ -503,6 +503,9 @@
 /*
  * Looks in table <t> for a sticky session with same key as <ts>.
  * Returns pointer on requested sticky session or NULL if none was found.
+ *
+ * <ts> must originate from a table with same key type and length than <t>,
+ * else it is undefined behavior.
  */
 struct stksess *__stktable_lookup(struct stktable *t, struct stksess *ts, uint shard)
 {
@@ -524,6 +527,9 @@
  * Returns pointer on requested sticky session or NULL if none was found.
  * The refcount of the found entry is increased and this function
  * is protected using the table lock
+ *
+ * <ts> must originate from a table with same key type and length than <t>,
+ * else it is undefined behavior.
  */
 struct stksess *stktable_lookup(struct stktable *t, struct stksess *ts)
 {
@@ -3646,7 +3652,12 @@
 		return NULL;
 
 	if (unlikely(args[arg].type == ARGT_TAB)) {
-		/* an alternate table was specified, let's look up the same key there */
+		/* an alternate table was specified, let's look up the same key there
+		 * unless the table key type or length differs from the tracked one
+		 */
+		if (args[arg].data.t->type != stkptr->table->type ||
+		    args[arg].data.t->key_size != stkptr->table->key_size)
+			return NULL;
 		stkctr->table = args[arg].data.t;
 		stkctr_set_entry(stkctr, stktable_lookup(stkctr->table, stksess));
 		return stkctr;
diff -Nru haproxy-3.0.9/src/stream.c haproxy-3.0.10/src/stream.c
--- haproxy-3.0.9/src/stream.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/stream.c	2025-04-22 14:53:02.000000000 +0300
@@ -1839,6 +1839,7 @@
 			sc_shutdown(scf);
 			//sc_report_error(scf); TODO: Be sure it is useless
 			if (!(req->analysers) && !(res->analysers)) {
+				COUNT_IF(1, "Report a client abort (no analysers)");
 				_HA_ATOMIC_INC(&s->be->be_counters.cli_aborts);
 				_HA_ATOMIC_INC(&sess->fe->fe_counters.cli_aborts);
 				if (sess->listener && sess->listener->counters)
@@ -1862,6 +1863,7 @@
 			if (srv)
 				_HA_ATOMIC_INC(&srv->counters.failed_resp);
 			if (!(req->analysers) && !(res->analysers)) {
+				COUNT_IF(1, "Report a client abort (no analysers)");
 				_HA_ATOMIC_INC(&s->be->be_counters.srv_aborts);
 				_HA_ATOMIC_INC(&sess->fe->fe_counters.srv_aborts);
 				if (sess->listener && sess->listener->counters)
@@ -2167,6 +2169,7 @@
 				if (srv)
 					_HA_ATOMIC_INC(&srv->counters.cli_aborts);
 				s->flags |= SF_ERR_CLICL;
+				COUNT_IF(1, "Report unhandled client error");
 			}
 			else if (req->flags & CF_READ_TIMEOUT) {
 				_HA_ATOMIC_INC(&s->be->be_counters.cli_aborts);
@@ -2176,6 +2179,7 @@
 				if (srv)
 					_HA_ATOMIC_INC(&srv->counters.cli_aborts);
 				s->flags |= SF_ERR_CLITO;
+				COUNT_IF(1, "Report unhandled client timeout (RD)");
 			}
 			else {
 				_HA_ATOMIC_INC(&s->be->be_counters.srv_aborts);
@@ -2185,6 +2189,7 @@
 				if (srv)
 					_HA_ATOMIC_INC(&srv->counters.srv_aborts);
 				s->flags |= SF_ERR_SRVTO;
+				COUNT_IF(1, "Report unhandled server timeout (WR)");
 			}
 			sess_set_term_flags(s);
 
@@ -2213,6 +2218,7 @@
 				if (srv)
 					_HA_ATOMIC_INC(&srv->counters.srv_aborts);
 				s->flags |= SF_ERR_SRVCL;
+				COUNT_IF(1, "Report unhandled server error");
 			}
 			else if (res->flags & CF_READ_TIMEOUT) {
 				_HA_ATOMIC_INC(&s->be->be_counters.srv_aborts);
@@ -2222,6 +2228,7 @@
 				if (srv)
 					_HA_ATOMIC_INC(&srv->counters.srv_aborts);
 				s->flags |= SF_ERR_SRVTO;
+				COUNT_IF(1, "Report unhandled server timeout (RD)");
 			}
 			else {
 				_HA_ATOMIC_INC(&s->be->be_counters.cli_aborts);
@@ -2231,6 +2238,7 @@
 				if (srv)
 					_HA_ATOMIC_INC(&srv->counters.cli_aborts);
 				s->flags |= SF_ERR_CLITO;
+				COUNT_IF(1, "Report unhandled client timeout (WR)");
 			}
 			sess_set_term_flags(s);
 		}
@@ -2788,11 +2796,28 @@
 	if (stream->scb->flags & (SC_FL_SHUT_DONE|SC_FL_SHUT_WANTED))
 		return;
 
-	sc_schedule_shutdown(stream->scb);
-	sc_schedule_abort(stream->scb);
 	stream->task->nice = 1024;
 	if (!(stream->flags & SF_ERR_MASK))
 		stream->flags |= why;
+
+	if (objt_server(stream->target)) {
+		if (stream->flags & SF_CURR_SESS) {
+			stream->flags &= ~SF_CURR_SESS;
+			_HA_ATOMIC_DEC(&__objt_server(stream->target)->cur_sess);
+		}
+
+		sess_change_server(stream, NULL);
+		if (may_dequeue_tasks(objt_server(stream->target), stream->be))
+			process_srv_queue(objt_server(stream->target));
+	}
+
+	/* shutw is enough to stop a connecting socket */
+	stream->scb->flags |= SC_FL_ERROR | SC_FL_NOLINGER;
+	sc_shutdown(stream->scb);
+
+	stream->scb->state = SC_ST_CLO;
+	if (stream->srv_error)
+		stream->srv_error(stream, stream->scb);
 }
 
 /* dumps an error message for type <type> at ptr <ptr> related to stream <s>,
diff -Nru haproxy-3.0.9/src/tools.c haproxy-3.0.10/src/tools.c
--- haproxy-3.0.9/src/tools.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/src/tools.c	2025-04-22 14:53:02.000000000 +0300
@@ -5503,11 +5503,12 @@
 		goto use_array;
 
 	i = dladdr_and_size(addr, &dli, &size);
-	if (!isolated)
-		HA_SPIN_UNLOCK(OTHER_LOCK, &dladdr_lock);
 
-	if (!i)
+	if (!i) {
+		if (!isolated)
+			HA_SPIN_UNLOCK(OTHER_LOCK, &dladdr_lock);
 		goto use_array;
+	}
 
 	/* 1. prefix the library name if it's not the same object as the one
 	 * that contains the main function. The name is picked between last '/'
@@ -5529,6 +5530,9 @@
 		ha_thread_relax();
 	}
 
+	if (!isolated)
+		HA_SPIN_UNLOCK(OTHER_LOCK, &dladdr_lock);
+
 	if (dli_main.dli_fbase != dli.dli_fbase) {
 		fname = dli.dli_fname;
 		p = strrchr(fname, '/');
diff -Nru haproxy-3.0.9/SUBVERS haproxy-3.0.10/SUBVERS
--- haproxy-3.0.9/SUBVERS	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/SUBVERS	2025-04-22 14:53:02.000000000 +0300
@@ -1,2 +1,2 @@
--7f0031e
+-346eb4f
 
diff -Nru haproxy-3.0.9/tests/exp/filltab25.c haproxy-3.0.10/tests/exp/filltab25.c
--- haproxy-3.0.9/tests/exp/filltab25.c	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/tests/exp/filltab25.c	2025-04-22 14:53:02.000000000 +0300
@@ -182,7 +182,7 @@
 	p = sw;
 }
 
-main(int argc, char **argv) {
+int main(int argc, char **argv) {
 	int conns;
 	int i;
 
diff -Nru haproxy-3.0.9/VERDATE haproxy-3.0.10/VERDATE
--- haproxy-3.0.9/VERDATE	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/VERDATE	2025-04-22 14:53:02.000000000 +0300
@@ -1,2 +1,2 @@
-2025-03-20 14:27:37 +0100
-2025/03/20
+2025-04-22 13:53:02 +0200
+2025/04/22
diff -Nru haproxy-3.0.9/VERSION haproxy-3.0.10/VERSION
--- haproxy-3.0.9/VERSION	2025-03-20 15:27:37.000000000 +0200
+++ haproxy-3.0.10/VERSION	2025-04-22 14:53:02.000000000 +0300
@@ -1 +1 @@
-3.0.9
+3.0.10

Reply via email to