Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package memcached for openSUSE:Factory 
checked in at 2026-04-11 22:22:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/memcached (Old)
 and      /work/SRC/openSUSE:Factory/.memcached.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "memcached"

Sat Apr 11 22:22:39 2026 rev:72 rq:1345699 version:1.6.41

Changes:
--------
--- /work/SRC/openSUSE:Factory/memcached/memcached.changes      2026-02-06 
19:06:44.975017467 +0100
+++ /work/SRC/openSUSE:Factory/.memcached.new.21863/memcached.changes   
2026-04-11 22:22:40.411290714 +0200
@@ -1,0 +2,19 @@
+Thu Apr  9 09:12:47 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.6.41:
+  * tests: make slabs-reassign2 test more resilient
+  * proxy: reduce flakiness in t/proxyunits.t
+  * proxy: fix off by one in temp string with 250b key
+  * slabs: fix hang and crash.
+  * Fix failing proxy*.t tests on some systems like OL8
+  * Account for absent 'ssl_proto_errors' in stats during SSL tests
+  * Fix test compatibility on IPv6-only systems.
+  * Use SSLv23 method when TLSv1.3 is unsupported (e.g., macOS)
+  * extstore: more compaction write patience
+  * parser: fix lru command regression
+  * Fix: avoid null print for slab busy reason
+  * extstore: testing around rescued compaction items
+  * extstore: fix compaction checks wrong refcount
+  * proto: armor against empty commands
+
+-------------------------------------------------------------------

Old:
----
  memcached-1.6.40.tar.gz

New:
----
  memcached-1.6.41.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ memcached.spec ++++++
--- /var/tmp/diff_new_pack.tXBDol/_old  2026-04-11 22:22:41.171321825 +0200
+++ /var/tmp/diff_new_pack.tXBDol/_new  2026-04-11 22:22:41.187322481 +0200
@@ -42,7 +42,7 @@
 %endif
 %endif
 Name:           memcached
-Version:        1.6.40
+Version:        1.6.41
 Release:        0
 Summary:        A high-performance, distributed memory object caching system
 License:        BSD-3-Clause

++++++ memcached-1.6.40.tar.gz -> memcached-1.6.41.tar.gz ++++++
++++ 2704 lines of diff (skipped)
++++    retrying with extended exclude list
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/authfile.c new/memcached-1.6.41/authfile.c
--- old/memcached-1.6.40/authfile.c     2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/authfile.c     2026-03-06 21:46:28.000000000 +0100
@@ -33,6 +33,10 @@
     char *auth_data = NULL;
     auth_t auth_entries[MAX_ENTRIES];
 
+    if (!file || strlen(file) == 0) {
+        return AUTHFILE_OPENFAIL;
+    }
+
     FILE *pwfile = fopen(file, "r");
     if (pwfile == NULL) {
         return AUTHFILE_OPENFAIL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/doc/Makefile new/memcached-1.6.41/doc/Makefile
--- old/memcached-1.6.40/doc/Makefile   2025-12-17 01:17:57.000000000 +0100
+++ new/memcached-1.6.41/doc/Makefile   2026-03-06 22:03:59.000000000 +0100
@@ -187,17 +187,17 @@
 LIBS = -levent 
 LTLIBOBJS = 
 MAKEINFO = ${SHELL} '/home/dormando/p/code/memcached/missing' makeinfo
-MKDIR_P = /usr/bin/mkdir -p
+MKDIR_P = mkdir -p
 OBJEXT = o
 OPENSSL_CFLAGS = 
 OPENSSL_LIBS = 
 PACKAGE = memcached
 PACKAGE_BUGREPORT = [email protected]
 PACKAGE_NAME = memcached
-PACKAGE_STRING = memcached 1.6.40
+PACKAGE_STRING = memcached 1.6.41
 PACKAGE_TARNAME = memcached
 PACKAGE_URL = 
-PACKAGE_VERSION = 1.6.40
+PACKAGE_VERSION = 1.6.41
 PATH_SEPARATOR = :
 PKG_CONFIG = /usr/bin/pkg-config
 PKG_CONFIG_LIBDIR = 
@@ -208,7 +208,7 @@
 SET_MAKE = 
 SHELL = /bin/bash
 STRIP = 
-VERSION = 1.6.40
+VERSION = 1.6.41
 XML2RFC = no
 XSLTPROC = no
 abs_builddir = /home/dormando/p/code/memcached/doc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/logger.c new/memcached-1.6.41/logger.c
--- old/memcached-1.6.40/logger.c       2025-10-22 06:59:10.000000000 +0200
+++ new/memcached-1.6.41/logger.c       2026-03-06 21:46:28.000000000 +0100
@@ -530,7 +530,7 @@
         "type=compact_read_start id=%lu offset=%llu"
     },
     [LOGGER_COMPACT_READ_END] = {512, LOG_SYSEVENTS, _logger_log_text, 
_logger_parse_text,
-        "type=compact_read_end id=%lu offset=%llu rescues=%lu lost=%lu 
skipped=%lu"
+        "type=compact_read_end id=%lu offset=%llu rescues=%lu 
rescues_realloc=%lu lost=%lu lost_oom=%lu skipped=%lu"
     },
     [LOGGER_COMPACT_END] = {512, LOG_SYSEVENTS, _logger_log_text, 
_logger_parse_text,
         "type=compact_end id=%lu"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/memcached.c new/memcached-1.6.41/memcached.c
--- old/memcached-1.6.40/memcached.c    2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/memcached.c    2026-03-06 21:46:28.000000000 +0100
@@ -1847,13 +1847,18 @@
     APPEND_STAT("hash_bytes", "%llu", (unsigned long 
long)stats_state.hash_bytes);
     APPEND_STAT("hash_is_expanding", "%u", stats_state.hash_is_expanding);
     if (settings.slab_reassign) {
+        const char *busy_status = stats.slab_reassign_last_busy_status;
+        if (!busy_status) {
+            // Ensure we can't be NULL, for portability reasons.
+            busy_status = "none";
+        }
         APPEND_STAT("slab_reassign_rescues", "%llu", 
stats.slab_reassign_rescues);
         APPEND_STAT("slab_reassign_chunk_rescues", "%llu", 
stats.slab_reassign_chunk_rescues);
         APPEND_STAT("slab_reassign_inline_reclaim", "%llu", 
stats.slab_reassign_inline_reclaim);
         APPEND_STAT("slab_reassign_busy_items", "%llu", 
stats.slab_reassign_busy_items);
         APPEND_STAT("slab_reassign_busy_deletes", "%llu", 
stats.slab_reassign_busy_deletes);
         APPEND_STAT("slab_reassign_busy_nomem", "%llu", 
stats.slab_reassign_busy_nomem);
-        APPEND_STAT("slab_reassign_last_busy_status", "%s", 
stats.slab_reassign_last_busy_status);
+        APPEND_STAT("slab_reassign_last_busy_status", "%s", busy_status);
         APPEND_STAT("slab_reassign_running", "%u", 
stats_state.slab_reassign_running);
         APPEND_STAT("slabs_moved", "%llu", stats.slabs_moved);
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/memcached.h new/memcached-1.6.41/memcached.h
--- old/memcached-1.6.40/memcached.h    2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/memcached.h    2026-03-06 21:46:28.000000000 +0100
@@ -911,6 +911,9 @@
     ssize_t (*read)(conn  *c, void *buf, size_t count);
     ssize_t (*sendmsg)(conn *c, struct msghdr *msg, int flags);
     ssize_t (*write)(conn *c, void *buf, size_t count);
+#ifdef MEMCACHED_DEBUG
+    void *debug_item_reffed; // used for refcount leak testing
+#endif
 };
 
 /* array of conn structures, indexed by file descriptor */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/memcached.spec new/memcached-1.6.41/memcached.spec
--- old/memcached-1.6.40/memcached.spec 2025-12-17 01:17:54.000000000 +0100
+++ new/memcached-1.6.41/memcached.spec 2026-03-06 22:03:56.000000000 +0100
@@ -27,7 +27,7 @@
 %endif
 
 Name:           memcached
-Version:        1.6.40
+Version:        1.6.41
 Release:        1%{?dist}
 Summary:        High Performance, Distributed Memory Object Cache
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/proto_text.c new/memcached-1.6.41/proto_text.c
--- old/memcached-1.6.40/proto_text.c   2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/proto_text.c   2026-03-06 21:46:28.000000000 +0100
@@ -851,16 +851,25 @@
             out_string(c, "MISS");
             return;
         }
+        c->debug_item_reffed = it;
     } else if (strncmp(subcmd, "unref", len) == 0) {
-        // double unlink. debugger must have already ref'ed it or this
-        // underflows.
-        item *it = item_get(key, klen, c->thread, DONT_UPDATE);
-        if (it == NULL) {
-            out_string(c, "MISS");
-            return;
+        if (klen == 6 && strncmp(key, "reffed", klen) == 0) {
+            // Item memory held in slot so we can test replacing reffed items
+            // without leaking.
+            item *reffed = c->debug_item_reffed;
+            do_item_remove(reffed);
+            c->debug_item_reffed = NULL;
+        } else {
+            // double unlink. debugger must have already ref'ed it or this
+            // underflows.
+            item *it = item_get(key, klen, c->thread, DONT_UPDATE);
+            if (it == NULL) {
+                out_string(c, "MISS");
+                return;
+            }
+            do_item_remove(it);
+            do_item_remove(it);
         }
-        do_item_remove(it);
-        do_item_remove(it);
     } else {
         out_string(c, "ERROR");
         return;
@@ -1605,13 +1614,15 @@
 
         return;
     } else if (ret == PROCESS_REQUEST_CMD_NOT_FOUND) {
-        int len = 0;
-        const char *cm = mcmc_token_get(pr.request, &pr.tok, 0, &len);
-        for (int x = 0; text_cmd_entries[x].s; x++) {
-            const struct text_cmd_entry *e = &text_cmd_entries[x];
-            if (strncmp(e->s, cm, len) == 0) {
-                e->func(c, &pr);
-                return;
+        if (pr.tok.ntokens > 0) {
+            int len = 0;
+            const char *cm = mcmc_token_get(pr.request, &pr.tok, 0, &len);
+            for (int x = 0; text_cmd_entries[x].s; x++) {
+                const struct text_cmd_entry *e = &text_cmd_entries[x];
+                if (strlen(e->s) == len && strncmp(e->s, cm, len) == 0) {
+                    e->func(c, &pr);
+                    return;
+                }
             }
         }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/proxy_lua.c new/memcached-1.6.41/proxy_lua.c
--- old/memcached-1.6.40/proxy_lua.c    2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/proxy_lua.c    2026-03-06 21:46:28.000000000 +0100
@@ -722,7 +722,7 @@
     }
 
     memcpy(temp, key, klen);
-    temp[klen+1] = '\0';
+    temp[klen] = '\0';
 
     // TODO (v2): memmem would avoid the temp key and memcpy here, but it's
     // not technically portable. An easy improvement would be to detect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/proxy_network.c new/memcached-1.6.41/proxy_network.c
--- old/memcached-1.6.40/proxy_network.c        2025-12-17 01:16:18.000000000 
+0100
+++ new/memcached-1.6.41/proxy_network.c        2026-03-06 21:46:28.000000000 
+0100
@@ -1368,7 +1368,7 @@
         }
 
 #ifdef PROXY_DEBUG
-        if (!STAILQ_EMPTY(&be->iop_head)) {
+        if (!STAILQ_EMPTY(&be->iop_read)) {
             P_DEBUG("backend has leftover IOs: %d\n", be->depth);
         }
 #endif
@@ -1430,7 +1430,7 @@
         }
 
 #ifdef PROXY_DEBUG
-        if (!STAILQ_EMPTY(&be->iop_head)) {
+        if (!STAILQ_EMPTY(&be->iop_read)) {
             P_DEBUG("backend has leftover IOs: %d\n", be->depth);
         }
 #endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/slab_automove.c new/memcached-1.6.41/slab_automove.c
--- old/memcached-1.6.40/slab_automove.c        2024-10-31 21:47:40.000000000 
+0100
+++ new/memcached-1.6.41/slab_automove.c        2026-03-06 21:46:28.000000000 
+0100
@@ -82,6 +82,8 @@
     bool youngest_evicting = false;
     *src = -1;
     *dst = -1;
+    bool mem_limit_reached = false;
+    global_page_pool_size(&mem_limit_reached);
 
     // fill after structs
     fill_item_stats_automove(a->iam_after);
@@ -128,7 +130,8 @@
 
         // if > N free chunks and not dirty, make decision.
         if (a->sam_after[n].free_chunks > a->sam_after[n].chunks_per_page * 
MIN_PAGES_FOR_RECLAIM) {
-            if (w_sum.dirty == 0) {
+            // Only handle "too much free" scenario if all memory was malloc'ed
+            if (mem_limit_reached && w_sum.dirty == 0) {
                 *src = n;
                 *dst = 0;
                 youngest = oldest = -1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/slabs_mover.c new/memcached-1.6.41/slabs_mover.c
--- old/memcached-1.6.40/slabs_mover.c  2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/slabs_mover.c  2026-03-06 21:46:28.000000000 +0100
@@ -108,6 +108,10 @@
     }
 
     void *page = slabs_peek_page(t->rebal.s_clsid, &size, &perslab);
+    // We _almost_ never get called in a condition where this can fail.
+    if (page == NULL) {
+        return -1;
+    }
 
     // Bit-vector to keep track of completed chunks
     t->rebal.completed = (uint8_t*)calloc(perslab,sizeof(uint8_t));
@@ -136,13 +140,17 @@
     return 0;
 }
 
-static void *slab_rebalance_alloc(struct slab_rebal_thread *t, unsigned int 
id) {
+#define MC_ALLOW_NEWPAGE 1
+#define MC_NO_NEWPAGE 0
+static void *slab_rebalance_alloc(struct slab_rebal_thread *t, unsigned int 
id, int flags) {
     item *new_it = NULL;
+    // translating our flags to downstream flags...
+    int sa_flags = flags == MC_ALLOW_NEWPAGE ? 0 : SLABS_ALLOC_NO_NEWPAGE;
 
     // We will either wipe the whole page if unused, or run out of memory in
     // the page and return NULL.
     while (1) {
-        new_it = slabs_alloc(id, SLABS_ALLOC_NO_NEWPAGE);
+        new_it = slabs_alloc(id, sa_flags);
         if (new_it == NULL) {
             break;
         }
@@ -174,23 +182,48 @@
         return;
     }
 
-    t->new_it = slab_rebalance_alloc(t, s_clsid);
+    t->new_it = slab_rebalance_alloc(t, s_clsid, MC_NO_NEWPAGE);
     // we could free the entire page in the above alloc call, but not get any
     // other memory to work with.
     // We try to busy-loop the page mover at least a few times in this case,
     // so it will pick up on all of the memory being freed already.
-    if (t->new_it == NULL && t->allow_evictions) {
-        // global is empty and memory limit is reached. we have to evict
-        // memory to move forward.
-        for (int x = 0; x < 10; x++) {
-            if (lru_pull_tail(s_clsid, COLD_LRU, 0, LRU_PULL_EVICT, 0, NULL) 
<= 0) {
-                if (settings.lru_segmented) {
-                    lru_pull_tail(s_clsid, HOT_LRU, 0, 0, 0, NULL);
+    if (t->new_it == NULL) {
+        // If we've been stuck for a long time and don't have memory to
+        // allocate with, flip allow_evictions. It starts on if we
+        // start rebalance with completely full memory. However it's
+        // possible to start rebalance before memory is full then it fills
+        // while we rebalance and get stuck forever.
+        if (!t->allow_evictions && t->rebal.busy_loops > SLAB_MOVE_MAX_LOOPS) {
+            bool mem_limit_reached = false;
+            global_page_pool_size(&mem_limit_reached);
+
+            // This is a stupid corner case that should self-resolve as memory
+            // fills. If we haven't malloc'ed all possible memory but we're
+            // trying to move memory for some reason, causing an eviction is
+            // nonsense. So we allocate a page to push a page out.
+            if (mem_limit_reached) {
+                t->allow_evictions = true;
+            } else {
+                t->new_it = slab_rebalance_alloc(t, s_clsid, MC_ALLOW_NEWPAGE);
+                if (t->new_it) {
+                    return;
                 }
             }
-            t->new_it = slab_rebalance_alloc(t, s_clsid);
-            if (t->new_it != NULL) {
-                break;
+        }
+
+        if (t->allow_evictions) {
+            // global is empty and memory limit is reached. we have to evict
+            // memory to move forward.
+            for (int x = 0; x < 10; x++) {
+                if (lru_pull_tail(s_clsid, COLD_LRU, 0, LRU_PULL_EVICT, 0, 
NULL) <= 0) {
+                    if (settings.lru_segmented) {
+                        lru_pull_tail(s_clsid, HOT_LRU, 0, 0, 0, NULL);
+                    }
+                }
+                t->new_it = slab_rebalance_alloc(t, s_clsid, MC_NO_NEWPAGE);
+                if (t->new_it != NULL) {
+                    break;
+                }
             }
         }
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/storage.c new/memcached-1.6.41/storage.c
--- old/memcached-1.6.40/storage.c      2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/storage.c      2026-03-06 21:46:28.000000000 +0100
@@ -934,10 +934,17 @@
         uint32_t page_id, uint64_t page_version, uint32_t page_offset, 
uint64_t read_size) {
     uint64_t offset = 0;
     unsigned int rescues = 0;
+    // rescues which required a memory realloc (informational)
+    unsigned int rescues_realloc = 0;
     unsigned int lost = 0;
+    // failed rescues because of OOM vs disk space
+    unsigned int lost_oom = 0;
     unsigned int skipped = 0;
     unsigned int rescue_cold = 0;
     unsigned int rescue_old = 0;
+    // Sleep this many times during the readback before becoming impatient.
+    // Each sleep is 1ms.
+    int max_waits = 5000;
 
     while (offset < read_size) {
         item *hdr_it = NULL;
@@ -983,24 +990,30 @@
 
             if (do_write) {
                 bool do_update = false;
-                int tries;
+                int tries = max_waits;
                 obj_io io;
                 io.len = ntotal;
                 io.mode = OBJ_IO_WRITE;
-                for (tries = 10; tries > 0; tries--) {
+                // Wait for a write buffer or page to become available.
+                if (tries < 10) {
+                    // Wait a minimum of 10ms
+                    tries = 10;
+                }
+                for (; tries > 0; tries--) {
                     if (extstore_write_request(storage, bucket, bucket, &io) 
== 0) {
                         memcpy(io.buf, it, io.len);
                         extstore_write(storage, &io);
                         do_update = true;
                         break;
                     } else {
+                        max_waits--;
                         usleep(1000);
                     }
                 }
 
                 if (do_update) {
                     bool rescued = false;
-                    if (it->refcount == 2) {
+                    if (hdr_it->refcount == 2) {
                         hdr->page_version = io.page_version;
                         hdr->page_id = io.page_id;
                         hdr->offset = io.offset;
@@ -1028,8 +1041,10 @@
                             item_replace(hdr_it, new_it, hv, 
ITEM_get_cas(hdr_it));
                             do_item_remove(new_it); // release our reference.
                             rescued = true;
+                            rescues_realloc++;
                         } else {
                             lost++;
+                            lost_oom++;
                         }
                     }
 
@@ -1063,7 +1078,7 @@
     stats.extstore_compact_resc_old += rescue_old;
     STATS_UNLOCK();
     LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_COMPACT_READ_END,
-            NULL, page_id, offset, rescues, lost, skipped);
+            NULL, page_id, offset, rescues, rescues_realloc, lost, lost_oom, 
skipped);
 }
 
 // wrap lock is held while waiting for this callback, preventing caller thread
Binary files old/memcached-1.6.40/t/.slabs-reassign2.t.swp and 
new/memcached-1.6.41/t/.slabs-reassign2.t.swp differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/t/extstore-start.t new/memcached-1.6.41/t/extstore-start.t
--- old/memcached-1.6.40/t/extstore-start.t     1970-01-01 01:00:00.000000000 
+0100
+++ new/memcached-1.6.41/t/extstore-start.t     2026-03-06 21:46:28.000000000 
+0100
@@ -0,0 +1,45 @@
+#!/usr/bin/env perl
+
+# Starting the daemon multiple times within one test file makes attaching GDB
+# to a test difficult. We isolate those tests into this file.
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+use Data::Dumper qw/Dumper/;
+
+my $ext_path;
+
+if (!supports_extstore()) {
+    plan skip_all => 'extstore not enabled';
+    exit 0;
+}
+
+$ext_path = "/tmp/extstore.$$";
+
+eval {
+    my $server = new_memcached("-o ext_path=$ext_path:0m");
+};
+ok($@, "failed to start server with zero pages assigned");
+
+eval {
+    my $server = new_memcached("-o ext_path=$ext_path:1GB");
+};
+ok($@, "failed to start server with invalid path size");
+
+my $server = new_memcached("-m 64 -U 0 -o 
ext_page_size=8,ext_wbuf_size=2,ext_threads=1,ext_io_depth=2,ext_item_size=512,ext_item_age=2,ext_recache_rate=10000,ext_max_frag=0.9,ext_path=$ext_path:64m,slab_automove=0,ext_compact_under=1,ext_max_sleep=100000");
+my $sock = $server->sock;
+
+eval {
+    my $server = new_memcached("-o ext_path=$ext_path:64m");
+};
+ok($@, "failed to start a second server with the same file path");
+
+done_testing();
+
+END {
+    unlink $ext_path if $ext_path;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/t/extstore.t new/memcached-1.6.41/t/extstore.t
--- old/memcached-1.6.40/t/extstore.t   2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/t/extstore.t   2026-03-06 21:46:28.000000000 +0100
@@ -17,24 +17,9 @@
 
 $ext_path = "/tmp/extstore.$$";
 
-eval {
-    my $server = new_memcached("-o ext_path=$ext_path:0m");
-};
-ok($@, "failed to start server with zero pages assigned");
-
-eval {
-    my $server = new_memcached("-o ext_path=$ext_path:1GB");
-};
-ok($@, "failed to start server with invalid path size");
-
 my $server = new_memcached("-m 64 -U 0 -o 
ext_page_size=8,ext_wbuf_size=2,ext_threads=1,ext_io_depth=2,ext_item_size=512,ext_item_age=2,ext_recache_rate=10000,ext_max_frag=0.9,ext_path=$ext_path:64m,slab_automove=0,ext_compact_under=1,ext_max_sleep=100000");
 my $sock = $server->sock;
 
-eval {
-    my $server = new_memcached("-o ext_path=$ext_path:64m");
-};
-ok($@, "failed to start a second server with the same file path");
-
 # Wait until all items have flushed
 sub wait_for_ext {
     my $target = shift || 0;
@@ -61,6 +46,7 @@
     is($res, "OK\r\n", "watcher enabled");
 
     my $fragcount = 20;
+    my $read_end = '';
     while (my $log = <$watcher>) {
         chomp $log;
         if ($log =~ m/type=compact_fraginfo/) {
@@ -80,61 +66,64 @@
     }
 }
 
-# fill a small object
-print $sock "set foo 0 0 2\r\nhi\r\n";
-is(scalar <$sock>, "STORED\r\n", "stored small value");
-# fetch
-mem_get_is($sock, "foo", "hi");
-# check extstore counters
-{
-    my $stats = mem_stats($sock);
-    is($stats->{extstore_objects_written}, 0);
-}
-# fill some larger objects
-{
-    # set one canary value for later
-    print $sock "set canary 0 0 20000 noreply\r\n$value\r\n";
-    my $keycount = 1000;
-    for (1 .. $keycount) {
-        print $sock "set nfoo$_ 0 0 20000 noreply\r\n$value\r\n";
-    }
-    # wait for a flush
-    wait_for_ext();
+subtest 'basics' => sub {
+    # fill a small object
+    print $sock "set foo 0 0 2\r\nhi\r\n";
+    is(scalar <$sock>, "STORED\r\n", "stored small value");
     # fetch
-    # TODO: Fetch back all values
-    mem_get_is($sock, "nfoo1", $value);
-    # check extstore counters
-    my $stats = mem_stats($sock);
-    cmp_ok($stats->{extstore_page_allocs}, '>', 0, 'at least one page 
allocated');
-    cmp_ok($stats->{extstore_objects_written}, '>', $keycount / 2, 'some 
objects written');
-    cmp_ok($stats->{extstore_bytes_written}, '>', length($value) * 2, 'some 
bytes written');
-    cmp_ok($stats->{get_extstore}, '>', 0, 'one object was fetched');
-    cmp_ok($stats->{extstore_objects_read}, '>', 0, 'one object read');
-    cmp_ok($stats->{extstore_bytes_read}, '>', length($value), 'some bytes 
read');
+    mem_get_is($sock, "foo", "hi");
 
-    # Remove half of the keys for the next test.
-    for (1 .. $keycount) {
-        next unless $_ % 2 == 0;
-        print $sock "delete nfoo$_ noreply\r\n";
+    # check extstore counters
+    {
+        my $stats = mem_stats($sock);
+        is($stats->{extstore_objects_written}, 0);
     }
 
-    my $stats2 = mem_stats($sock);
-    cmp_ok($stats->{extstore_bytes_used}, '>', $stats2->{extstore_bytes_used},
-        'bytes used dropped after deletions');
-    cmp_ok($stats->{extstore_objects_used}, '>', 
$stats2->{extstore_objects_used},
-        'objects used dropped after deletions');
-    is($stats2->{badcrc_from_extstore}, 0, 'CRC checks successful');
-    is($stats2->{miss_from_extstore}, 0, 'no misses');
+    # fill some larger objects
+    {
+        # set one canary value for later
+        print $sock "set canary 0 0 20000 noreply\r\n$value\r\n";
+        my $keycount = 1000;
+        for (1 .. $keycount) {
+            print $sock "set nfoo$_ 0 0 20000 noreply\r\n$value\r\n";
+        }
+        # wait for a flush
+        wait_for_ext();
+        # fetch
+        # TODO: Fetch back all values
+        mem_get_is($sock, "nfoo1", $value);
+        # check extstore counters
+        my $stats = mem_stats($sock);
+        cmp_ok($stats->{extstore_page_allocs}, '>', 0, 'at least one page 
allocated');
+        cmp_ok($stats->{extstore_objects_written}, '>', $keycount / 2, 'some 
objects written');
+        cmp_ok($stats->{extstore_bytes_written}, '>', length($value) * 2, 
'some bytes written');
+        cmp_ok($stats->{get_extstore}, '>', 0, 'one object was fetched');
+        cmp_ok($stats->{extstore_objects_read}, '>', 0, 'one object read');
+        cmp_ok($stats->{extstore_bytes_read}, '>', length($value), 'some bytes 
read');
+
+        # Remove half of the keys for the next test.
+        for (1 .. $keycount) {
+            next unless $_ % 2 == 0;
+            print $sock "delete nfoo$_ noreply\r\n";
+        }
 
-    # delete the rest
-    for (1 .. $keycount) {
-        next unless $_ % 2 == 1;
-        print $sock "delete nfoo$_ noreply\r\n";
+        my $stats2 = mem_stats($sock);
+        cmp_ok($stats->{extstore_bytes_used}, '>', 
$stats2->{extstore_bytes_used},
+            'bytes used dropped after deletions');
+        cmp_ok($stats->{extstore_objects_used}, '>', 
$stats2->{extstore_objects_used},
+            'objects used dropped after deletions');
+        is($stats2->{badcrc_from_extstore}, 0, 'CRC checks successful');
+        is($stats2->{miss_from_extstore}, 0, 'no misses');
+
+        # delete the rest
+        for (1 .. $keycount) {
+            next unless $_ % 2 == 1;
+            print $sock "delete nfoo$_ noreply\r\n";
+        }
     }
-}
+};
 
-# check item flag survival after write to disk
-{
+subtest 'check item flag survival after write to disk' => sub {
     print $sock "ms itflagtest 20000\r\n$value\r\n";
     is(scalar <$sock>, "HD\r\n", "prepped flag test value");
     print $sock "mg itflagtest\r\n";
@@ -146,11 +135,102 @@
 
     print $sock "mg itflagtest h\r\n";
     is(scalar <$sock>, "HD h1 X W\r\n", "flags came back as expected");
-}
+};
 
-# fill to eviction
-{
+subtest 'compaction and rescues' => sub {
+    my $keycount = 2500;
+
+    print $sock "set refcanary 5 0 20000 noreply\r\n$value\r\n";
+
+    # Ensure our canary goes to disk.
+    wait_for_ext();
+    print $sock "touch refcanary 0 noreply\r\n";
+    print $sock "touch refcanary 0 noreply\r\n";
+    # Set some extra flags
+    print $sock "md refcanary I\r\n";
+    is(scalar <$sock>, "HD\r\n", "invalidated item to set STALE");
+
+    # reflock one key to test realloc rescues
+    # important: reflock _after_ it goes to disk so we lock the header item.
+    print $sock "debugitem ref refcanary\r\n";
+    my $res = <$sock>;
+
+    # Need to ensure we catch all compaction log events.
+    my $watcher = $server->new_sock;
+    print $watcher "watch sysevents\n";
+    $res = <$watcher>;
+    is($res, "OK\r\n", "watcher enabled");
+
+    for (1 .. $keycount) {
+        print $sock "set cfoo$_ 0 0 20000 noreply\r\n$value\r\n";
+        # wait to avoid evictions
+        wait_for_ext(500) if ($_ % 2000 == 0);
+    }
+    # because item_age is set to 2s
+    wait_for_ext();
+    my $stats = mem_stats($sock);
+    is($stats->{evictions}, 0, 'no evictions');
+
+    # check counters
+    cmp_ok($stats->{extstore_page_evictions}, '==', 0, 'no pages evicted');
+    cmp_ok($stats->{extstore_objects_evicted}, '==', 0, 'no objects evicted');
+    cmp_ok($stats->{extstore_bytes_evicted}, '==', 0, 'no bytes evicted');
+    cmp_ok($stats->{extstore_pages_free}, '<', 2, 'few pages are free');
+
+    for (1 .. $keycount) {
+        next unless $_ % 2 == 0;
+        print $sock "delete cfoo$_ noreply\r\n";
+    }
+
+    my %h = ();
+    my $tries = 250;
+    while (my $log = <$watcher>) {
+        #diag "WATCHER LOG: $log";
+        chomp $log;
+        if ($log =~ m/type=compact_read_end/) {
+            # FIXME: I forget the better method for this.
+            my @p = split(/\s+/, $log);
+            for (@p) {
+                my @hp = split(/=/, $_);
+                $h{$hp[0]} = $hp[1];
+            }
+            if ($h{rescues_realloc} != 0) {
+                ok("saw a rescue realloc");
+                last;
+            }
+        }
+        if ($tries-- == 0) {
+            fail("never saw a rescue realloc");
+            last;
+        }
+    }
+
+    $stats = mem_stats($sock);
+    cmp_ok($stats->{extstore_pages_free}, '>', 0, 'some pages now free');
+    cmp_ok($stats->{extstore_compact_rescues}, '>', 0, 'some compaction 
rescues happened');
+    cmp_ok($stats->{extstore_compact_skipped}, '==', 0, 'no compaction skips 
happened');
+    print $sock "extstore drop_unread 0\r\n";
+    $res = <$sock>;
+
+    # release our leaked item
+    print $sock "debugitem unref reffed\r\n";
+    $res = <$sock>;
+    # Ensure various flag bits and header memory survived.
+    print $sock "mg refcanary f h\r\n";
+    is(scalar <$sock>, "HD f5 h1 X W\r\n", "reffed item flags came back as 
expected");
+
+    mem_get_is({ sock => $sock, flags => 5 }, "refcanary", $value);
+
+    # remove all data and wait for extstore to settle.
+    print $sock "flush_all\r\n";
+    $res = <$sock>;
+
+    watch_compact();
+};
+
+subtest 'eviction and compaction' => sub {
     my $keycount = 4000;
+
     for (1 .. $keycount) {
         print $sock "set mfoo$_ 0 0 20000 noreply\r\n$value\r\n";
         # wait to avoid evictions
@@ -201,10 +281,10 @@
     cmp_ok($stats->{extstore_compact_skipped}, '>', 0, 'some compaction skips 
happened');
     print $sock "extstore drop_unread 0\r\n";
     $res = <$sock>;
-}
+};
 
 # attempt to incr/decr/append/prepend or chunk objects that were sent to disk.
-{
+subtest 'invalid operations' => sub {
     my $keycount = 100;
     for (1 .. $keycount) {
         print $sock "set bfoo$_ 0 0 20000 noreply\r\n$value\r\n";
@@ -220,7 +300,7 @@
     is(scalar <$sock>, "NOT_STORED\r\n", 'append fails');
     print $sock "prepend bfoo1 0 0 2\r\nhi\r\n";
     is(scalar <$sock>, "NOT_STORED\r\n", 'prepend fails');
-}
+};
 
 done_testing();
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/t/issue_67.t new/memcached-1.6.41/t/issue_67.t
--- old/memcached-1.6.40/t/issue_67.t   2024-08-01 20:54:00.000000000 +0200
+++ new/memcached-1.6.41/t/issue_67.t   2026-03-06 21:46:28.000000000 +0100
@@ -85,8 +85,8 @@
     my $server = run_server($params);
     my %ports = read_ports();
 
-    validate_port($name, $ports{'TCP INET'}, $expected_tcp);
-    validate_port($name, $ports{'UDP INET'}, $expected_udp);
+    validate_port($name, $ports{'TCP INET'} || $ports{'TCP INET6'}, 
$expected_tcp);
+    validate_port($name, $ports{'UDP INET'} || $ports{'UDP INET6'}, 
$expected_udp);
 }
 
 skip_if_default_addr_in_use { when('no arguments', '', 11211, -1) };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/t/proxyunits.lua new/memcached-1.6.41/t/proxyunits.lua
--- old/memcached-1.6.40/t/proxyunits.lua       2025-10-22 06:59:10.000000000 
+0200
+++ new/memcached-1.6.41/t/proxyunits.lua       2026-03-06 21:46:28.000000000 
+0100
@@ -14,7 +14,8 @@
     local b2z = {b2}
     local b3z = {b3}
 
-    local dead = srv('dead', '127.9.9.9', 11011);
+    local dead = srv({ label = "dead", host = "127.9.9.9", port = 11011,
+                                   down = true })
 
     local no_label = srv('', '127.0.0.1', 11414)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/t/slabs-reassign2.t 
new/memcached-1.6.41/t/slabs-reassign2.t
--- old/memcached-1.6.40/t/slabs-reassign2.t    2025-10-22 06:59:10.000000000 
+0200
+++ new/memcached-1.6.41/t/slabs-reassign2.t    2026-03-06 21:46:28.000000000 
+0100
@@ -2,7 +2,7 @@
 
 use strict;
 use warnings;
-use Test::More tests => 11;
+use Test::More tests => 12;
 use FindBin qw($Bin);
 use lib "$Bin/lib";
 use MemcachedTest;
@@ -12,25 +12,23 @@
 my $sock = $server->sock;
 
 my $value = "B"x11000;
-my $keycount = 5000;
+my $keycount = 6000;
 
 my $res;
 for (1 .. $keycount) {
     print $sock "set nfoo$_ 0 0 11000 noreply\r\n$value\r\n";
 }
 
-my $todelete = 0;
 {
     my $stats = mem_stats($sock);
     cmp_ok($stats->{curr_items}, '>', 4000, "stored at least 4000 11k items");
-    $todelete = $stats->{curr_items};
-#    for ('evictions', 'reclaimed', 'curr_items', 'cmd_set', 'bytes') {
-#        print STDERR "$_: ", $stats->{$_}, "\n";
-#    }
+    #for ('evictions', 'reclaimed', 'curr_items', 'cmd_set', 'bytes') {
+    #    diag "$_: ", $stats->{$_}, "\n";
+    #}
 }
 
 # Make room in old class so rescues can happen when we switch slab classes.
-for (1 .. $todelete) {
+for (1 .. $keycount) {
     next unless $_ % 2 == 0;
     print $sock "delete nfoo$_ noreply\r\n";
 }
@@ -47,9 +45,13 @@
     cmp_ok($tries, '>', 0, 'some pages moved back to global pool');
 }
 
-$value = "B"x7000;
+my $stats_before = mem_stats($sock);
+
+# Ensure we can set data into a different slab class.
+
+$value = "B"x6000;
 for (1 .. $keycount) {
-    print $sock "set ifoo$_ 0 0 7000 noreply\r\n$value\r\n";
+    print $sock "set ifoo$_ 0 0 6000 noreply\r\n$value\r\n";
 }
 
 my $missing = 0;
@@ -57,7 +59,7 @@
 for (1 .. $keycount) {
     print $sock "get ifoo$_\r\n";
     my $body = scalar(<$sock>);
-    my $expected = "VALUE ifoo$_ 0 7000\r\n$value\r\nEND\r\n";
+    my $expected = "VALUE ifoo$_ 0 6000\r\n$value\r\nEND\r\n";
     if ($body =~ /^END/) {
         $missing++;
     } else {
@@ -73,7 +75,8 @@
 
 {
     my $stats = mem_stats($sock);
-    cmp_ok($stats->{evictions}, '<', 2000, 'evictions were less than 2000');
+    cmp_ok($stats->{evictions} - $stats_before->{evictions}, '>', 0, 
'evictions were greater than 0');
+    cmp_ok($stats->{evictions} - $stats_before->{evictions}, '<', 2200, 
'evictions were less than 2200');
 #    for ('evictions', 'reclaimed', 'curr_items', 'cmd_set', 'bytes') {
 #        print STDERR "$_: ", $stats->{$_}, "\n";
 #    }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/t/ssl_ports.t new/memcached-1.6.41/t/ssl_ports.t
--- old/memcached-1.6.40/t/ssl_ports.t  2025-10-22 06:59:10.000000000 +0200
+++ new/memcached-1.6.41/t/ssl_ports.t  2026-03-06 21:46:28.000000000 +0100
@@ -33,8 +33,15 @@
 # not trying very hard but: if the above works and this doesn't, it's going to
 # be the peer cert. If someone wants to tryhard you can try inspecting
 # $SSL_ERROR and/or checking `watch connevents` stream
-my $mtls_sock = $server->new_nocert_tls_sock($mtls_port, 'TLSv1_3');
+my ($mtls_sock, $mtls_version);
+for my $tls ('TLSv1_3', 'SSLv23') {
+    $mtls_sock = $server->new_nocert_tls_sock($mtls_port, $tls);
+    if ($mtls_sock) {
+        $mtls_version = $tls;
+        last;
+    }
+}
 print $mtls_sock "version\r\n";
-is(scalar <$mtls_sock>, undef, "failed to connect without peer cert");
+is(scalar <$mtls_sock>, undef, "failed to connect without peer cert on 
$mtls_version");
 
 done_testing()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/t/ssl_proto_version.t 
new/memcached-1.6.41/t/ssl_proto_version.t
--- old/memcached-1.6.40/t/ssl_proto_version.t  2024-08-01 20:54:00.000000000 
+0200
+++ new/memcached-1.6.41/t/ssl_proto_version.t  2026-03-06 21:46:28.000000000 
+0100
@@ -37,9 +37,16 @@
 SKIP: {
     skip 'TLS v1.3 not available', 1 if !$is_tls_13_available;
     # Above minimum supported protocol version
-    $sock = $server->new_sock(undef, 'TLSv1_3');
-    print $sock "version\r\n";
-    like(scalar <$sock>, qr/VERSION/, "handshake above minimum proto version");
+    my ($mtls_sock, $mtls_version);
+    for my $tls ('TLSv1_3', 'SSLv23') {
+        $mtls_sock = $server->new_sock(undef, $tls);
+        if ($mtls_sock) {
+            $mtls_version = $tls;
+            last;
+        }
+    }
+    print $mtls_sock "version\r\n";
+    like(scalar <$mtls_sock>, qr/VERSION/, "handshake above minimum proto 
version on $mtls_version");
 }
 
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/t/stats.t new/memcached-1.6.41/t/stats.t
--- old/memcached-1.6.40/t/stats.t      2025-12-17 01:16:18.000000000 +0100
+++ new/memcached-1.6.41/t/stats.t      2026-03-06 21:46:28.000000000 +0100
@@ -28,7 +28,8 @@
     # when TLS is enabled, stats contains additional keys:
     #   - ssl_handshake_errors
     #   - time_since_server_cert_refresh
-    is(scalar(keys(%$stats)), 86, "expected count of stats values");
+    #   - ssl_proto_errors
+    is(scalar(keys(%$stats)), 87, "expected count of stats values");
 } else {
     is(scalar(keys(%$stats)), 84, "expected count of stats values");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/vendor/mcmc/mcmc.c new/memcached-1.6.41/vendor/mcmc/mcmc.c
--- old/memcached-1.6.40/vendor/mcmc/mcmc.c     2025-10-22 06:59:10.000000000 
+0200
+++ new/memcached-1.6.41/vendor/mcmc/mcmc.c     2026-03-06 21:46:28.000000000 
+0100
@@ -45,7 +45,7 @@
 
 // INTERNAL FUNCTIONS
 
-#define TOKENIZER_MAXLEN USHRT_MAX-1
+#define TOKENIZER_MAXLEN (USHRT_MAX-1)
 
 // Find the starting offsets of each token; ignoring length.
 // This creates a fast small (<= cacheline) index into the request,
@@ -57,8 +57,9 @@
 // Function _assumes_ const char *line ends with \n or \r\n, but will not
 // break so long as the passed in 'len' is reasonable.
 MCMC_STATIC int _mcmc_tokenize_meta(mcmc_tokenizer_t *t, const char *line, 
size_t len, const int mstart, const int max) {
-    const char *s = line;
+    const char *s;
     const char *end;
+    const char *n = s = line;
     t->metaflags = 0;
 
     // since multigets can be huge, we can't purely judge reqlen against this
@@ -75,7 +76,47 @@
 
     int curtoken = 0;
 
-    while (s != end) {
+    // NOTE: To optimize this loop for long tokens:
+    // Add n++ to the *n == ' ' case next to s++
+    // Remove final n++, add else { n = memchr(etc) }
+    // This is slower with lots of single char tokens, faster with long
+    // tokens.
+    // To favor code simplicity I'm leaving it middle of the road: if we
+    // desire the optimization we need an if with two separate loops based on
+    // line len > X, where X is determined by benchmarking sets of string
+    // examples to be a good common tradeoff.
+    while (n != end) {
+        if (*n == ' ') {
+            if (s != n) {
+                t->tokens[curtoken] = s - line;
+                if (curtoken >= mstart) {
+                    if (*s > 64 && *s < 123) {
+                        t->metaflags |= (uint64_t)1 << (*s - 65);
+                    } else if (isdigit(*s) == 0) {
+                        return MCMC_NOK;
+                    }
+                }
+                if (++curtoken == max) {
+                    s++;
+                    s = memchr(s, ' ', end - s);
+                    if (!s) {
+                        s = end;
+                    }
+                    n = s; // avoid adding extra token
+                    break;
+                }
+                s = n;
+            }
+            s++;
+        }
+        n++;
+    }
+
+    // reached end of parsing with active token
+    if (s != n) {
+        // Deliberate code redundancy; too many args, inline return.
+        // Bad smell for macro, potential extra branch if inline func.
+        t->tokens[curtoken] = s - line;
         if (curtoken >= mstart) {
             if (*s > 64 && *s < 123) {
                 t->metaflags |= (uint64_t)1 << (*s - 65);
@@ -83,34 +124,13 @@
                 return MCMC_NOK;
             }
         }
-        t->tokens[curtoken] = s - line;
-        if (++curtoken == max) {
-            s++;
-            // hit max tokens before end of the line.
-            // keep advancing so we can place endcap token.
-            s = memchr(s, ' ', end - s);
-            if (!s) {
-                s = end;
-            }
-            break;
-        }
-        s++;
-        // avoid memchr if we were a single byte token.
-        if (*s == ' ') {
-            while (*s == ' ' && s != end) {
-                s++;
-            }
-        } else {
-            // advance over a token
-            s = memchr(s, ' ', end - s);
-            if (!s) {
-                s = end;
+        curtoken++;
+        // must advance to space or next token for end cap
+        while (s != end) {
+            if (*s == ' ') {
                 break;
-            } else {
-                while (*s == ' ' && s != end) {
-                    s++; // skip any spaces until the next token.
-                }
             }
+            s++;
         }
     }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude 
config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 
--exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh 
old/memcached-1.6.40/version.m4 new/memcached-1.6.41/version.m4
--- old/memcached-1.6.40/version.m4     2025-12-17 01:17:54.000000000 +0100
+++ new/memcached-1.6.41/version.m4     2026-03-06 22:03:56.000000000 +0100
@@ -1 +1 @@
-m4_define([VERSION_NUMBER], [1.6.40])
+m4_define([VERSION_NUMBER], [1.6.41])

Reply via email to