]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: dynbuf: refrain from offering a buffer if more critical ones are waiting
authorWilly Tarreau <w@1wt.eu>
Wed, 24 Apr 2024 16:14:06 +0000 (18:14 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 10 May 2024 15:18:13 +0000 (17:18 +0200)
Now b_alloc() will check the queues at the same and higher criticality
levels before allocating a buffer, and will refrain from allocating one
if these are not empty. The purpose is to put some priorities in the
allocation order so that most critical allocators are offered a chance
to complete.

However in order to permit a freshly dequeued task to allocate again while
siblings are still in the queue, there is a special DB_F_NOQUEUE flag to
pass to b_alloc() that will take care of this special situation.

include/haproxy/dynbuf-t.h
include/haproxy/dynbuf.h

index b93c6e0a4be55bfa10b00712690329608c1eeddd..f0ca1873f6349afb8a2f25464efe6f3c4ba7efbf 100644 (file)
@@ -50,7 +50,8 @@
  *     buffers). If these fail, we can't boot.
  *
  * Please DO NOT CHANGE THESE LEVELS without first getting a full understanding
- * of how all this works and touching the DB_CRIT_TO_QUEUE() macro below!
+ * of how all this works and touching the DB_F_CRIT_MASK and DB_CRIT_TO_QUEUE()
+ * macros below!
  */
 enum dynbuf_crit {
        DB_GROW_RING = 0, // used to grow an existing buffer ring
@@ -64,6 +65,14 @@ enum dynbuf_crit {
        DB_PERMANENT,     // buffers permanently allocated.
 };
 
+/* The values above are expected to be passed to b_alloc(). In addition, some
+ * Extra flags can be passed by oring the crit value above with one of these
+ * high-bit flags.
+ */
+#define DB_F_NOQUEUE   0x80000000U   // ignore presence of others in queue
+#define DB_F_CRIT_MASK 0x000000FFU   // mask to keep the criticality bits
+
+
 /* We'll deal with 4 queues, with indexes numbered from 0 to 3 based on the
  * criticality of the allocation. All criticality levels are mapped to a 2-bit
  * queue index. While some levels never use the queue (the first two), some of
index 21f347b4fbcfe9626984a0aa0cbee27a72d45e65..c4c28538d2b5551775377355c323eaeb483e1e05 100644 (file)
@@ -56,22 +56,38 @@ static inline int buffer_almost_full(const struct buffer *buf)
 /* Functions below are used for buffer allocation */
 /**************************************************/
 
+/* returns non-zero if one may try to allocate a buffer for criticality flags
+ * <crit> (made of a criticality and optional flags).
+ */
+static inline int b_may_alloc_for_crit(uint crit)
+{
+       int q = DB_CRIT_TO_QUEUE(crit & DB_F_CRIT_MASK);
+
+       /* if this queue or any more critical ones have entries, we must wait */
+       if (!(crit & DB_F_NOQUEUE) && th_ctx->bufq_map & ((2 << q) - 1))
+               return 0;
+
+       return 1;
+}
+
 /* Ensures that <buf> is allocated, or allocates it. If no memory is available,
  * ((char *)1) is assigned instead with a zero size. The allocated buffer is
  * returned, or NULL in case no memory is available. Since buffers only contain
  * user data, poisonning is always disabled as it brings no benefit and impacts
  * performance. Due to the difficult buffer_wait management, they are not
- * subject to forced allocation failures either.
+ * subject to forced allocation failures either. If other waiters are present
+ * at higher criticality levels, we refrain from allocating.
  */
-#define b_alloc(_buf, _crit)                   \
-({                                             \
-       char *_area;                            \
-       struct buffer *_retbuf = _buf;          \
-       enum dynbuf_crit _criticality __maybe_unused = _crit;   \
-                                               \
-       if (!_retbuf->size) {                   \
+#define b_alloc(_buf, _crit)                                           \
+({                                                                     \
+       char *_area = NULL;                                             \
+       struct buffer *_retbuf = _buf;                                  \
+       uint _criticality = _crit;                                      \
+                                                                       \
+       if (!_retbuf->size) {                                           \
                *_retbuf = BUF_WANTED;                                  \
-               _area = pool_alloc_flag(pool_head_buffer, POOL_F_NO_POISON | POOL_F_NO_FAIL); \
+               if (b_may_alloc_for_crit(_criticality))                 \
+                       _area = pool_alloc_flag(pool_head_buffer, POOL_F_NO_POISON | POOL_F_NO_FAIL); \
                if (unlikely(!_area)) {                                 \
                        activity[tid].buf_wait++;                       \
                        _retbuf = NULL;                                 \