Hello community,

here is the log from the commit of package memcached for openSUSE:Factory 
checked in at 2018-08-22 14:19:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/memcached (Old)
 and      /work/SRC/openSUSE:Factory/.memcached.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "memcached"

Wed Aug 22 14:19:43 2018 rev:40 rq:630705 version:1.5.10

Changes:
--------
--- /work/SRC/openSUSE:Factory/memcached/memcached.changes      2018-07-09 
13:31:36.462458371 +0200
+++ /work/SRC/openSUSE:Factory/.memcached.new/memcached.changes 2018-08-22 
14:20:14.134334989 +0200
@@ -1,0 +2,17 @@
+Fri Aug 17 15:15:38 UTC 2018 - [email protected]
+
+- Fix linter errors regarding COPYING
+
+-------------------------------------------------------------------
+Mon Aug 13 09:43:51 UTC 2018 - [email protected]
+
+- update to 1.5.10:
+  * disruptive change in extstore: -o ext_page_count= is deprecated
+    and no longer works. To specify size: -o ext_path=/d/m/e:500G
+    extstore figures out the page count based on your desired page
+    size. M|G|T|P supported.
+  * extstore: Add basic JBOD support: ext_path can be specified
+    multiple times for striping onto simimar devices
+  * fix alignment issues on some ARM platforms for chunked items
+
+-------------------------------------------------------------------

Old:
----
  memcached-1.5.9.tar.gz

New:
----
  memcached-1.5.10.tar.gz

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

Other differences:
------------------
++++++ memcached.spec ++++++
--- /var/tmp/diff_new_pack.rZiXmt/_old  2018-08-22 14:20:15.918339215 +0200
+++ /var/tmp/diff_new_pack.rZiXmt/_new  2018-08-22 14:20:15.922339223 +0200
@@ -18,16 +18,15 @@
 
 #Compat macro for new _fillupdir macro introduced in Nov 2017
 %if ! %{defined _fillupdir}
-  %define _fillupdir /var/adm/fillup-templates
+  %define _fillupdir %{_localstatedir}/adm/fillup-templates
 %endif
-
 Name:           memcached
-Version:        1.5.9
+Version:        1.5.10
 Release:        0
 Summary:        A high-performance, distributed memory object caching system
 License:        BSD-3-Clause
 Group:          Productivity/Networking/Other
-Url:            http://memcached.org/
+URL:            http://memcached.org/
 Source:         http://www.memcached.org/files/%{name}-%{version}.tar.gz
 Source1:        %{name}.init
 Source2:        %{name}.sysconfig
@@ -41,18 +40,17 @@
 BuildRequires:  cyrus-sasl-devel
 BuildRequires:  libevent-devel
 BuildRequires:  libtool
-BuildRequires:  pkg-config
+BuildRequires:  pkgconfig
+Requires(pre):  %fillup_prereq
 Requires(pre):  %{_sbindir}/groupadd
 Requires(pre):  %{_sbindir}/useradd
 Conflicts:      memcached-unstable
-BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 %if 0%{?suse_version} > 1210
 BuildRequires:  systemd-rpm-macros
 %{?systemd_requires}
 %else
 Requires(pre):  %insserv_prereq
 %endif
-Requires(pre):  %fillup_prereq
 
 %description
 Memcached is a high-performance, distributed memory object caching
@@ -80,7 +78,7 @@
 This package contains development files
 
 %prep
-%setup -q -n %{name}-%{version}
+%setup -q
 %patch0
 %patch1
 %patch2
@@ -99,7 +97,7 @@
 make %{?_smp_mflags}
 
 %install
-make DESTDIR=%{buildroot} install %{?_smp_mflags}
+%make_install
 install -D  -m 0755 scripts/memcached-tool 
%{buildroot}%{_sbindir}/memcached-tool
 install -Dd -m 0751 %{buildroot}%{_localstatedir}/lib/%{name}
 install -D  -m 0644 %{SOURCE2} %{buildroot}%{_fillupdir}/sysconfig.%{name}
@@ -112,7 +110,7 @@
 %endif
 
 %check
-make test
+make %{?_smp_mflags} test
 
 %pre
 %{_sbindir}/groupadd -r %{name} >/dev/null 2>&1 || :
@@ -145,8 +143,8 @@
 %endif
 
 %files
-%defattr(-,root,root)
-%doc AUTHORS ChangeLog COPYING NEWS
+%doc AUTHORS ChangeLog NEWS
+%license COPYING
 %{_sbindir}/%{name}
 %{_sbindir}/memcached-tool
 %{_sbindir}/rc%{name}
@@ -160,8 +158,8 @@
 %dir %attr(751,root,root) %{_localstatedir}/lib/%{name}
 
 %files devel
-%defattr(-,root,root)
-%doc AUTHORS ChangeLog COPYING NEWS doc/*.txt
+%doc AUTHORS ChangeLog NEWS doc/*.txt
+%license COPYING
 %{_includedir}/%{name}
 
 %changelog

++++++ memcached-1.5.9.tar.gz -> memcached-1.5.10.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/configure 
new/memcached-1.5.10/configure
--- old/memcached-1.5.9/configure       2018-07-08 05:24:04.000000000 +0200
+++ new/memcached-1.5.10/configure      2018-08-10 22:40:22.000000000 +0200
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for memcached 1.5.9.
+# Generated by GNU Autoconf 2.69 for memcached 1.5.10.
 #
 # Report bugs to <[email protected]>.
 #
@@ -580,8 +580,8 @@
 # Identity of this package.
 PACKAGE_NAME='memcached'
 PACKAGE_TARNAME='memcached'
-PACKAGE_VERSION='1.5.9'
-PACKAGE_STRING='memcached 1.5.9'
+PACKAGE_VERSION='1.5.10'
+PACKAGE_STRING='memcached 1.5.10'
 PACKAGE_BUGREPORT='[email protected]'
 PACKAGE_URL=''
 
@@ -1323,7 +1323,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures memcached 1.5.9 to adapt to many kinds of systems.
+\`configure' configures memcached 1.5.10 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1394,7 +1394,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of memcached 1.5.9:";;
+     short | recursive ) echo "Configuration of memcached 1.5.10:";;
    esac
   cat <<\_ACEOF
 
@@ -1499,7 +1499,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-memcached configure 1.5.9
+memcached configure 1.5.10
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1968,7 +1968,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by memcached $as_me 1.5.9, which was
+It was created by memcached $as_me 1.5.10, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2903,7 +2903,7 @@
 
 # Define the identity of the package.
  PACKAGE='memcached'
- VERSION='1.5.9'
+ VERSION='1.5.10'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -7225,7 +7225,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by memcached $as_me 1.5.9, which was
+This file was extended by memcached $as_me 1.5.10, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -7291,7 +7291,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; 
s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-memcached config.status 1.5.9
+memcached config.status 1.5.10
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/doc/Makefile 
new/memcached-1.5.10/doc/Makefile
--- old/memcached-1.5.9/doc/Makefile    2018-07-08 05:24:06.000000000 +0200
+++ new/memcached-1.5.10/doc/Makefile   2018-08-10 22:40:25.000000000 +0200
@@ -191,10 +191,10 @@
 PACKAGE = memcached
 PACKAGE_BUGREPORT = [email protected]
 PACKAGE_NAME = memcached
-PACKAGE_STRING = memcached 1.5.9
+PACKAGE_STRING = memcached 1.5.10
 PACKAGE_TARNAME = memcached
 PACKAGE_URL = 
-PACKAGE_VERSION = 1.5.9
+PACKAGE_VERSION = 1.5.10
 PATH_SEPARATOR = :
 PROFILER = /usr/bin/gcov
 PROFILER_FLAGS = -fprofile-arcs -ftest-coverage
@@ -202,7 +202,7 @@
 SET_MAKE = 
 SHELL = /bin/bash
 STRIP = 
-VERSION = 1.5.9
+VERSION = 1.5.10
 XML2RFC = /usr/bin/xml2rfc
 XSLTPROC = no
 abs_builddir = /home/dormando/d/p/danga/git/memcached/doc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/extstore.c 
new/memcached-1.5.10/extstore.c
--- old/memcached-1.5.9/extstore.c      2018-06-27 22:12:55.000000000 +0200
+++ new/memcached-1.5.10/extstore.c     2018-08-10 22:36:38.000000000 +0200
@@ -62,6 +62,7 @@
     unsigned int allocated;
     unsigned int written; /* item offsets can be past written if wbuf not 
flushed */
     unsigned int bucket; /* which bucket the page is linked into */
+    unsigned int free_bucket; /* which bucket this page returns to when freed 
*/
     int fd;
     unsigned short id;
     bool active; /* actively being written to */
@@ -95,6 +96,7 @@
     store_maint_thread *maint_thread;
     store_page *page_freelist;
     store_page **page_buckets; /* stack of pages currently allocated to each 
bucket */
+    store_page **free_page_buckets; /* stack of use-case isolated free pages */
     size_t page_size;
     unsigned int version; /* global version counter */
     unsigned int last_io_thread; /* round robin the IO threads */
@@ -102,6 +104,7 @@
     unsigned int page_count;
     unsigned int page_free; /* unallocated pages */
     unsigned int page_bucketcount; /* count of potential page buckets */
+    unsigned int free_page_bucketcount; /* count of free page buckets */
     unsigned int io_depth; /* FIXME: Might cache into thr struct */
     pthread_mutex_t stats_mutex;
     struct extstore_stats stats;
@@ -192,11 +195,11 @@
     return rv;
 }
 
-void *extstore_init(char *fn, struct extstore_conf *cf,
+// TODO: #define's for DEFAULT_BUCKET, FREE_VERSION, etc
+void *extstore_init(struct extstore_conf_file *fh, struct extstore_conf *cf,
         enum extstore_res *res) {
     int i;
-    int fd;
-    uint64_t offset = 0;
+    struct extstore_conf_file *f = NULL;
     pthread_t thread;
 
     if (cf->page_size % cf->wbuf_size != 0) {
@@ -227,43 +230,72 @@
     }
 
     e->page_size = cf->page_size;
-    fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, 0644);
-    if (fd < 0) {
-        *res = EXTSTORE_INIT_OPEN_FAIL;
+    for (f = fh; f != NULL; f = f->next) {
+        f->fd = open(f->file, O_RDWR | O_CREAT | O_TRUNC, 0644);
+        if (f->fd < 0) {
+            *res = EXTSTORE_INIT_OPEN_FAIL;
 #ifdef EXTSTORE_DEBUG
-        perror("open");
+            perror("open");
 #endif
-        free(e);
-        return NULL;
+            free(e);
+            return NULL;
+        }
+        e->page_count += f->page_count;
+        f->offset = 0;
     }
 
-    e->pages = calloc(cf->page_count, sizeof(store_page));
+    e->pages = calloc(e->page_count, sizeof(store_page));
     if (e->pages == NULL) {
         *res = EXTSTORE_INIT_OOM;
-        close(fd);
+        // FIXME: loop-close. make error label
         free(e);
         return NULL;
     }
 
-    for (i = 0; i < cf->page_count; i++) {
+    // interleave the pages between devices
+    f = NULL; // start at the first device.
+    for (i = 0; i < e->page_count; i++) {
+        // find next device with available pages
+        while (1) {
+            // restart the loop
+            if (f == NULL || f->next == NULL) {
+                f = fh;
+            } else {
+                f = f->next;
+            }
+            if (f->page_count) {
+                f->page_count--;
+                break;
+            }
+        }
         pthread_mutex_init(&e->pages[i].mutex, NULL);
         e->pages[i].id = i;
-        e->pages[i].fd = fd;
-        e->pages[i].offset = offset;
+        e->pages[i].fd = f->fd;
+        e->pages[i].free_bucket = f->free_bucket;
+        e->pages[i].offset = f->offset;
         e->pages[i].free = true;
-        offset += e->page_size;
+        f->offset += e->page_size;
     }
 
-    for (i = cf->page_count-1; i > 0; i--) {
-        e->pages[i].next = e->page_freelist;
-        e->page_freelist = &e->pages[i];
+    // free page buckets allows the app to organize devices by use case
+    e->free_page_buckets = calloc(cf->page_buckets, sizeof(store_page *));
+    e->page_bucketcount = cf->page_buckets;
+
+    for (i = e->page_count-1; i > 0; i--) {
         e->page_free++;
+        if (e->pages[i].free_bucket == 0) {
+            e->pages[i].next = e->page_freelist;
+            e->page_freelist = &e->pages[i];
+        } else {
+            int fb = e->pages[i].free_bucket;
+            e->pages[i].next = e->free_page_buckets[fb];
+            e->free_page_buckets[fb] = &e->pages[i];
+        }
     }
 
     // 0 is magic "page is freed" version
     e->version = 1;
 
-    e->page_count = cf->page_count;
     // scratch data for stats. TODO: malloc failure handle
     e->stats.page_data =
         calloc(e->page_count, sizeof(struct extstore_page_data));
@@ -309,6 +341,8 @@
     pthread_cond_init(&e->maint_thread->cond, NULL);
     pthread_create(&thread, NULL, extstore_maint_thread, e->maint_thread);
 
+    extstore_run_maint(e);
+
     return (void *)e;
 }
 
@@ -318,13 +352,25 @@
 }
 
 // call with *e locked
-static store_page *_allocate_page(store_engine *e, unsigned int bucket) {
+static store_page *_allocate_page(store_engine *e, unsigned int bucket,
+        unsigned int free_bucket) {
     assert(!e->page_buckets[bucket] || e->page_buckets[bucket]->allocated == 
e->page_size);
-    store_page *tmp = e->page_freelist;
-    E_DEBUG("EXTSTORE: allocating new page\n");
-    if (e->page_free > 0) {
-        assert(e->page_freelist != NULL);
+    store_page *tmp = NULL;
+    // if a specific free bucket was requested, check there first
+    if (free_bucket != 0 && e->free_page_buckets[free_bucket] != NULL) {
+        assert(e->page_free > 0);
+        tmp = e->free_page_buckets[free_bucket];
+        e->free_page_buckets[free_bucket] = tmp->next;
+    }
+    // failing that, try the global list.
+    if (tmp == NULL && e->page_freelist != NULL) {
+        tmp = e->page_freelist;
         e->page_freelist = tmp->next;
+    }
+    E_DEBUG("EXTSTORE: allocating new page\n");
+    // page_freelist can be empty if the only free pages are specialized and
+    // we didn't just request one.
+    if (e->page_free > 0 && tmp != NULL) {
         tmp->next = e->page_buckets[bucket];
         e->page_buckets[bucket] = tmp;
         tmp->active = true;
@@ -434,7 +480,8 @@
  * new page. best if used from a background thread that can harmlessly retry.
  */
 
-int extstore_write_request(void *ptr, unsigned int bucket, obj_io *io) {
+int extstore_write_request(void *ptr, unsigned int bucket,
+        unsigned int free_bucket, obj_io *io) {
     store_engine *e = (store_engine *)ptr;
     store_page *p;
     int ret = -1;
@@ -444,7 +491,7 @@
     pthread_mutex_lock(&e->mutex);
     p = e->page_buckets[bucket];
     if (!p) {
-        p = _allocate_page(e, bucket);
+        p = _allocate_page(e, bucket, free_bucket);
     }
     pthread_mutex_unlock(&e->mutex);
     if (!p)
@@ -458,7 +505,7 @@
             ((!p->wbuf || p->wbuf->full) && p->allocated >= e->page_size)) {
         pthread_mutex_unlock(&p->mutex);
         pthread_mutex_lock(&e->mutex);
-        _allocate_page(e, bucket);
+        _allocate_page(e, bucket, free_bucket);
         pthread_mutex_unlock(&e->mutex);
         return ret;
     }
@@ -764,8 +811,14 @@
     p->closed = false;
     p->free = true;
     // add to page stack
-    p->next = e->page_freelist;
-    e->page_freelist = p;
+    // TODO: free_page_buckets first class and remove redundancy?
+    if (p->free_bucket != 0) {
+        p->next = e->free_page_buckets[p->free_bucket];
+        e->free_page_buckets[p->free_bucket] = p;
+    } else {
+        p->next = e->page_freelist;
+        e->page_freelist = p;
+    }
     e->page_free++;
     pthread_mutex_unlock(&e->mutex);
 }
@@ -797,7 +850,9 @@
 
         pthread_cond_wait(&me->cond, &me->mutex);
         pthread_mutex_lock(&e->mutex);
-        if (e->page_free == 0) {
+        // default freelist requires at least one page free.
+        // specialized freelists fall back to default once full.
+        if (e->page_free == 0 || e->page_freelist == NULL) {
             do_evict = true;
         }
         pthread_mutex_unlock(&e->mutex);
@@ -806,6 +861,7 @@
         for (i = 0; i < e->page_count; i++) {
             store_page *p = &e->pages[i];
             pthread_mutex_lock(&p->mutex);
+            pd[p->id].free_bucket = p->free_bucket;
             if (p->active || p->free) {
                 pthread_mutex_unlock(&p->mutex);
                 continue;
@@ -814,7 +870,13 @@
                 pd[p->id].version = p->version;
                 pd[p->id].bytes_used = p->bytes_used;
                 pd[p->id].bucket = p->bucket;
-                if (p->version < low_version) {
+                // low_version/low_page are only used in the eviction
+                // scenario. when we evict, it's only to fill the default page
+                // bucket again.
+                // TODO: experiment with allowing evicting up to a single page
+                // for any specific free bucket. this is *probably* required
+                // since it could cause a load bias on default-only devices?
+                if (p->free_bucket == 0 && p->version < low_version) {
                     low_version = p->version;
                     low_page = i;
                 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/extstore.h 
new/memcached-1.5.10/extstore.h
--- old/memcached-1.5.9/extstore.h      2018-06-27 22:12:55.000000000 +0200
+++ new/memcached-1.5.10/extstore.h     2018-08-10 22:36:38.000000000 +0200
@@ -8,6 +8,7 @@
     uint64_t version;
     uint64_t bytes_used;
     unsigned int bucket;
+    unsigned int free_bucket;
 };
 
 /* Pages can have objects deleted from them at any time. This creates holes
@@ -43,12 +44,23 @@
     unsigned int page_size; // ideally 64-256M in size
     unsigned int page_count;
     unsigned int page_buckets; // number of different writeable pages
+    unsigned int free_page_buckets; // buckets of dedicated pages (see code)
     unsigned int wbuf_size; // must divide cleanly into page_size
     unsigned int wbuf_count; // this might get locked to "2 per active page"
     unsigned int io_threadcount;
     unsigned int io_depth; // with normal I/O, hits locks less. req'd for AIO
 };
 
+struct extstore_conf_file {
+    unsigned int page_count;
+    char *file;
+    int fd; // internal usage
+    uint64_t offset; // internal usage
+    unsigned int bucket; // free page bucket
+    unsigned int free_bucket; // specialized free bucket
+    struct extstore_conf_file *next;
+};
+
 enum obj_io_mode {
     OBJ_IO_READ = 0,
     OBJ_IO_WRITE,
@@ -87,8 +99,8 @@
 };
 
 const char *extstore_err(enum extstore_res res);
-void *extstore_init(char *fn, struct extstore_conf *cf, enum extstore_res 
*res);
-int extstore_write_request(void *ptr, unsigned int bucket, obj_io *io);
+void *extstore_init(struct extstore_conf_file *fh, struct extstore_conf *cf, 
enum extstore_res *res);
+int extstore_write_request(void *ptr, unsigned int bucket, unsigned int 
free_bucket, obj_io *io);
 void extstore_write(void *ptr, obj_io *io);
 int extstore_submit(void *ptr, obj_io *io);
 /* count are the number of objects being removed, bytes are the original
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/items.c new/memcached-1.5.10/items.c
--- old/memcached-1.5.9/items.c 2018-06-27 22:12:55.000000000 +0200
+++ new/memcached-1.5.10/items.c        2018-08-10 22:36:38.000000000 +0200
@@ -285,6 +285,13 @@
         if (settings.use_cas) {
             htotal += sizeof(uint64_t);
         }
+#ifdef NEED_ALIGN
+        // header chunk needs to be padded on some systems
+        int remain = htotal % 8;
+        if (remain != 0) {
+            htotal += 8 - remain;
+        }
+#endif
         hdr_id = slabs_clsid(htotal);
         it = do_item_alloc_pull(htotal, hdr_id);
         /* setting ITEM_CHUNKED is fine here because we aren't LINKED yet. */
@@ -336,7 +343,7 @@
 
     /* Initialize internal chunk. */
     if (it->it_flags & ITEM_CHUNKED) {
-        item_chunk *chunk = (item_chunk *) ITEM_data(it);
+        item_chunk *chunk = (item_chunk *) ITEM_schunk(it);
 
         chunk->next = 0;
         chunk->prev = 0;
@@ -1538,7 +1545,6 @@
     void *storage = arg;
     if (storage != NULL)
         sam = &slab_automove_extstore;
-    int x;
 #endif
     int i;
     useconds_t to_sleep = MIN_LRU_MAINTAINER_SLEEP;
@@ -1592,20 +1598,6 @@
             }
 
             int did_moves = lru_maintainer_juggle(i);
-#ifdef EXTSTORE
-            // Deeper loop to speed up pushing to storage.
-            if (storage) {
-                for (x = 0; x < 500; x++) {
-                    int found;
-                    found = lru_maintainer_store(storage, i);
-                    if (found) {
-                        did_moves += found;
-                    } else {
-                        break;
-                    }
-                }
-            }
-#endif
             if (did_moves == 0) {
                 if (backoff_juggles[i] != 0) {
                     backoff_juggles[i] += backoff_juggles[i] / 8;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/logger.c 
new/memcached-1.5.10/logger.c
--- old/memcached-1.5.9/logger.c        2018-06-27 22:12:55.000000000 +0200
+++ new/memcached-1.5.10/logger.c       2018-08-10 22:36:38.000000000 +0200
@@ -752,6 +752,7 @@
             rel_time_t sttl = va_arg(ap, rel_time_t);
             uint8_t sclsid = va_arg(ap, int);
             _logger_log_item_store(e, status, comm, skey, snkey, sttl, sclsid);
+            va_end(ap);
             break;
     }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/memcached.c 
new/memcached-1.5.10/memcached.c
--- old/memcached-1.5.9/memcached.c     2018-07-08 05:19:49.000000000 +0200
+++ new/memcached-1.5.10/memcached.c    2018-08-10 22:36:38.000000000 +0200
@@ -1032,7 +1032,7 @@
 
 static int add_chunked_item_iovs(conn *c, item *it, int len) {
     assert(it->it_flags & ITEM_CHUNKED);
-    item_chunk *ch = (item_chunk *) ITEM_data(it);
+    item_chunk *ch = (item_chunk *) ITEM_schunk(it);
     while (ch) {
         int todo = (len > ch->used) ? ch->used : len;
         if (add_iov(c, ch->data, todo) != 0) {
@@ -2485,7 +2485,15 @@
     }
 
     c->item = it;
+#ifdef NEED_ALIGN
+    if (it->it_flags & ITEM_CHUNKED) {
+        c->ritem = ITEM_schunk(it);
+    } else {
+        c->ritem = ITEM_data(it);
+    }
+#else
     c->ritem = ITEM_data(it);
+#endif
     c->rlbytes = vlen;
     conn_set_state(c, conn_nread);
     c->substate = bin_read_set_value;
@@ -2540,7 +2548,15 @@
     }
 
     c->item = it;
+#ifdef NEED_ALIGN
+    if (it->it_flags & ITEM_CHUNKED) {
+        c->ritem = ITEM_schunk(it);
+    } else {
+        c->ritem = ITEM_data(it);
+    }
+#else
     c->ritem = ITEM_data(it);
+#endif
     c->rlbytes = vlen;
     conn_set_state(c, conn_nread);
     c->substate = bin_read_set_value;
@@ -2701,7 +2717,7 @@
 /* Destination must always be chunked */
 /* This should be part of item.c */
 static int _store_item_copy_chunks(item *d_it, item *s_it, const int len) {
-    item_chunk *dch = (item_chunk *) ITEM_data(d_it);
+    item_chunk *dch = (item_chunk *) ITEM_schunk(d_it);
     /* Advance dch until we find free space */
     while (dch->size == dch->used) {
         if (dch->next) {
@@ -2713,7 +2729,7 @@
 
     if (s_it->it_flags & ITEM_CHUNKED) {
         int remain = len;
-        item_chunk *sch = (item_chunk *) ITEM_data(s_it);
+        item_chunk *sch = (item_chunk *) ITEM_schunk(s_it);
         int copied = 0;
         /* Fills dch's to capacity, not straight copy sch in case data is
          * being added or removed (ie append/prepend)
@@ -3401,6 +3417,8 @@
                 (unsigned long long) st.page_data[i].bytes_used);
         APPEND_NUM_STAT(i, "bucket", "%u",
                 st.page_data[i].bucket);
+        APPEND_NUM_STAT(i, "free_bucket", "%u",
+                st.page_data[i].free_bucket);
     }
 }
 #endif
@@ -3717,7 +3735,7 @@
     if (chunked) {
         unsigned int ciovcnt = 1;
         size_t remain = new_it->nbytes;
-        item_chunk *chunk = (item_chunk *) ITEM_data(new_it);
+        item_chunk *chunk = (item_chunk *) ITEM_schunk(new_it);
         io->io.iov = &c->iov[c->iovused];
         // fill the header so we can get the full data + crc back.
         add_iov(c, new_it, ITEM_ntotal(new_it) - new_it->nbytes);
@@ -4078,7 +4096,15 @@
     ITEM_set_cas(it, req_cas_id);
 
     c->item = it;
+#ifdef NEED_ALIGN
+    if (it->it_flags & ITEM_CHUNKED) {
+        c->ritem = ITEM_schunk(it);
+    } else {
+        c->ritem = ITEM_data(it);
+    }
+#else
     c->ritem = ITEM_data(it);
+#endif
     c->rlbytes = it->nbytes;
     c->cmd = comm;
     conn_set_state(c, conn_nread);
@@ -5291,7 +5317,6 @@
 
     while (c->rlbytes > 0) {
         item_chunk *ch = (item_chunk *)c->ritem;
-        assert(ch->used <= ch->size);
         if (ch->size == ch->used) {
             // FIXME: ch->next is currently always 0. remove this?
             if (ch->next) {
@@ -6262,8 +6287,8 @@
 #endif
 #ifdef EXTSTORE
            "   - ext_path:            file to write to for external storage.\n"
+           "                          ie: ext_path=/mnt/d1/extstore:1G\n"
            "   - ext_page_size:       size in megabytes of storage pages.\n"
-           "   - ext_page_count:      total number of storage pages.\n"
            "   - ext_wbuf_size:       size in megabytes of page write 
buffers.\n"
            "   - ext_threads:         number of IO threads to run.\n"
            "   - ext_item_size:       store items larger than this (bytes)\n"
@@ -6564,7 +6589,7 @@
     bool slab_chunk_size_changed = false;
 #ifdef EXTSTORE
     void *storage = NULL;
-    char *storage_file = NULL;
+    struct extstore_conf_file *storage_file = NULL;
     struct extstore_conf ext_cf;
 #endif
     char *subopts, *subopts_orig;
@@ -6611,7 +6636,6 @@
 #endif
 #ifdef EXTSTORE
         EXT_PAGE_SIZE,
-        EXT_PAGE_COUNT,
         EXT_WBUF_SIZE,
         EXT_THREADS,
         EXT_IO_DEPTH,
@@ -6669,7 +6693,6 @@
 #endif
 #ifdef EXTSTORE
         [EXT_PAGE_SIZE] = "ext_page_size",
-        [EXT_PAGE_COUNT] = "ext_page_count",
         [EXT_WBUF_SIZE] = "ext_wbuf_size",
         [EXT_THREADS] = "ext_threads",
         [EXT_IO_DEPTH] = "ext_io_depth",
@@ -6709,7 +6732,6 @@
     settings.ext_drop_under = 0;
     settings.slab_automove_freeratio = 0.01;
     ext_cf.page_size = 1024 * 1024 * 64;
-    ext_cf.page_count = 64;
     ext_cf.wbuf_size = settings.ext_wbuf_size;
     ext_cf.io_threadcount = 1;
     ext_cf.io_depth = 1;
@@ -7235,16 +7257,6 @@
                 }
                 ext_cf.page_size *= 1024 * 1024; /* megabytes */
                 break;
-            case EXT_PAGE_COUNT:
-                if (subopts_value == NULL) {
-                    fprintf(stderr, "Missing ext_page_count argument\n");
-                    return 1;
-                }
-                if (!safe_strtoul(subopts_value, &ext_cf.page_count)) {
-                    fprintf(stderr, "could not parse argument to 
ext_page_count\n");
-                    return 1;
-                }
-                break;
             case EXT_WBUF_SIZE:
                 if (subopts_value == NULL) {
                     fprintf(stderr, "Missing ext_wbuf_size argument\n");
@@ -7361,7 +7373,20 @@
                 settings.ext_drop_unread = true;
                 break;
             case EXT_PATH:
-                storage_file = strdup(subopts_value);
+                if (subopts_value) {
+                    struct extstore_conf_file *tmp = 
storage_conf_parse(subopts_value, ext_cf.page_size);
+                    if (tmp == NULL) {
+                        fprintf(stderr, "failed to parse ext_path argument\n");
+                        return 1;
+                    }
+                    if (storage_file != NULL) {
+                        tmp->next = storage_file;
+                    }
+                    storage_file = tmp;
+                } else {
+                    fprintf(stderr, "missing argument to ext_path, ie: 
ext_path=/d/file:5G\n");
+                    return 1;
+                }
                 break;
 #endif
             case MODERN:
@@ -7635,9 +7660,9 @@
     if (storage_file) {
         enum extstore_res eres;
         if (settings.ext_compact_under == 0) {
-            settings.ext_compact_under = ext_cf.page_count / 4;
+            settings.ext_compact_under = storage_file->page_count / 4;
             /* Only rescues non-COLD items if below this threshold */
-            settings.ext_drop_under = ext_cf.page_count / 4;
+            settings.ext_drop_under = storage_file->page_count / 4;
         }
         crc32c_init();
         /* Init free chunks to zero. */
@@ -7688,6 +7713,10 @@
         fprintf(stderr, "Failed to start storage compaction thread\n");
         exit(EXIT_FAILURE);
     }
+    if (storage && start_storage_write_thread(storage) != 0) {
+        fprintf(stderr, "Failed to start storage writer thread\n");
+        exit(EXIT_FAILURE);
+    }
 
     if (start_lru_maintainer && start_lru_maintainer_thread(storage) != 0) {
 #else
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/memcached.h 
new/memcached-1.5.10/memcached.h
--- old/memcached-1.5.9/memcached.h     2018-07-08 05:19:49.000000000 +0200
+++ new/memcached-1.5.10/memcached.h    2018-08-10 22:36:38.000000000 +0200
@@ -524,6 +524,23 @@
     uint8_t          slabs_clsid; /* Same as above. */
     char data[];
 } item_chunk;
+
+#ifdef NEED_ALIGN
+static inline char *ITEM_schunk(item *it) {
+    int offset = it->nkey + 1 + it->nsuffix
+        + ((it->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0);
+    int remain = offset % 8;
+    if (remain != 0) {
+        offset += 8 - remain;
+    }
+    return ((char *) &(it->data)) + offset;
+}
+#else
+#define ITEM_schunk(item) ((char*) &((item)->data) + (item)->nkey + 1 \
+         + (item)->nsuffix \
+         + (((item)->it_flags & ITEM_CAS) ? sizeof(uint64_t) : 0))
+#endif
+
 #ifdef EXTSTORE
 typedef struct {
     unsigned int page_version; /* from IO header */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/memcached.spec 
new/memcached-1.5.10/memcached.spec
--- old/memcached-1.5.9/memcached.spec  2018-07-08 05:24:03.000000000 +0200
+++ new/memcached-1.5.10/memcached.spec 2018-08-10 22:40:21.000000000 +0200
@@ -17,7 +17,7 @@
 %endif
 
 Name:           memcached
-Version:        1.5.9
+Version:        1.5.10
 Release:        1%{?dist}
 Summary:        High Performance, Distributed Memory Object Cache
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/slabs.c new/memcached-1.5.10/slabs.c
--- old/memcached-1.5.9/slabs.c 2018-07-08 05:19:49.000000000 +0200
+++ new/memcached-1.5.10/slabs.c        2018-08-10 22:36:38.000000000 +0200
@@ -380,7 +380,7 @@
 }
 
 static void do_slabs_free_chunked(item *it, const size_t size) {
-    item_chunk *chunk = (item_chunk *) ITEM_data(it);
+    item_chunk *chunk = (item_chunk *) ITEM_schunk(it);
     slabclass_t *p;
 
     it->it_flags = ITEM_SLABBED;
@@ -404,7 +404,15 @@
     p->slots = it;
     p->sl_curr++;
     // TODO: macro
+#ifdef NEED_ALIGN
+    int total = it->nkey + 1 + it->nsuffix + sizeof(item) + sizeof(item_chunk);
+    if (total % 8 != 0) {
+        total += 8 - (total % 8);
+    }
+    p->requested -= total;
+#else
     p->requested -= it->nkey + 1 + it->nsuffix + sizeof(item) + 
sizeof(item_chunk);
+#endif
     if (settings.use_cas) {
         p->requested -= sizeof(uint64_t);
     }
@@ -1013,7 +1021,7 @@
                         do_item_replace(it, new_it, hv);
                         /* Need to walk the chunks and repoint head  */
                         if (new_it->it_flags & ITEM_CHUNKED) {
-                            item_chunk *fch = (item_chunk *) ITEM_data(new_it);
+                            item_chunk *fch = (item_chunk *) 
ITEM_schunk(new_it);
                             fch->next->prev = fch;
                             while (fch) {
                                 fch->head = new_it;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/storage.c 
new/memcached-1.5.10/storage.c
--- old/memcached-1.5.9/storage.c       2018-07-08 05:19:49.000000000 +0200
+++ new/memcached-1.5.10/storage.c      2018-08-10 22:36:38.000000000 +0200
@@ -6,27 +6,18 @@
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
+#include <ctype.h>
 
 #define PAGE_BUCKET_DEFAULT 0
 #define PAGE_BUCKET_COMPACT 1
 #define PAGE_BUCKET_CHUNKED 2
 #define PAGE_BUCKET_LOWTTL  3
 
-int lru_maintainer_store(void *storage, const int clsid) {
-    //int i;
+/*** WRITE FLUSH THREAD ***/
+
+static int storage_write(void *storage, const int clsid, const int item_age) {
     int did_moves = 0;
-    int item_age = settings.ext_item_age;
-    bool mem_limit_reached = false;
-    unsigned int chunks_free;
     struct lru_pull_tail_return it_info;
-    // FIXME: need to directly ask the slabber how big a class is
-    if (slabs_clsid(settings.ext_item_size) > clsid)
-        return 0;
-    chunks_free = slabs_available_chunks(clsid, &mem_limit_reached,
-            NULL, NULL);
-    // if we are low on chunks and no spare, push out early.
-    if (chunks_free < settings.ext_free_memchunks[clsid] && mem_limit_reached)
-        item_age = 0;
 
     it_info.it = NULL;
     lru_pull_tail(clsid, COLD_LRU, 0, LRU_PULL_RETURN_ITEM, 0, &it_info);
@@ -60,7 +51,9 @@
             // NOTE: when the item is read back in, the slab mover
             // may see it. Important to have refcount>=2 or ~ITEM_LINKED
             assert(it->refcount >= 2);
-            if (extstore_write_request(storage, bucket, &io) == 0) {
+            // NOTE: write bucket vs free page bucket will disambiguate once
+            // lowttl feature is better understood.
+            if (extstore_write_request(storage, bucket, bucket, &io) == 0) {
                 // cuddle the hash value into the time field so we don't have
                 // to recalculate it.
                 item *buf_it = (item *) io.buf;
@@ -69,7 +62,7 @@
                 // TODO: should be in items.c
                 if (it->it_flags & ITEM_CHUNKED) {
                     // Need to loop through the item and copy
-                    item_chunk *sch = (item_chunk *) ITEM_data(it);
+                    item_chunk *sch = (item_chunk *) ITEM_schunk(it);
                     int remain = orig_ntotal;
                     int copied = 0;
                     // copy original header
@@ -118,6 +111,128 @@
     return did_moves;
 }
 
+static pthread_t storage_write_tid;
+static pthread_mutex_t storage_write_plock;
+#define WRITE_SLEEP_MAX 1000000
+#define WRITE_SLEEP_MIN 500
+
+static void *storage_write_thread(void *arg) {
+    void *storage = arg;
+    // NOTE: ignoring overflow since that would take years of uptime in a
+    // specific load pattern of never going to sleep.
+    unsigned int backoff[MAX_NUMBER_OF_SLAB_CLASSES] = {0};
+    unsigned int counter = 0;
+    useconds_t to_sleep = WRITE_SLEEP_MIN;
+    logger *l = logger_create();
+    if (l == NULL) {
+        fprintf(stderr, "Failed to allocate logger for storage compaction 
thread\n");
+        abort();
+    }
+
+    pthread_mutex_lock(&storage_write_plock);
+
+    while (1) {
+        // cache per-loop to avoid calls to the slabs_clsid() search loop
+        int min_class = slabs_clsid(settings.ext_item_size);
+        bool do_sleep = true;
+        counter++;
+        if (to_sleep > WRITE_SLEEP_MAX)
+            to_sleep = WRITE_SLEEP_MAX;
+
+        for (int x = 0; x < MAX_NUMBER_OF_SLAB_CLASSES; x++) {
+            bool did_move = false;
+            bool mem_limit_reached = false;
+            unsigned int chunks_free;
+            int item_age;
+            int target = settings.ext_free_memchunks[x];
+            if (min_class > x || (backoff[x] && (counter % backoff[x] != 0))) {
+                // Long sleeps means we should retry classes sooner.
+                if (to_sleep > WRITE_SLEEP_MIN * 10)
+                    backoff[x] /= 2;
+                continue;
+            }
+
+            // Avoid extra slab lock calls during heavy writing.
+            chunks_free = slabs_available_chunks(x, &mem_limit_reached,
+                    NULL, NULL);
+
+            // storage_write() will fail and cut loop after filling write 
buffer.
+            while (1) {
+                // if we are low on chunks and no spare, push out early.
+                if (chunks_free < target && mem_limit_reached) {
+                    item_age = 0;
+                } else {
+                    item_age = settings.ext_item_age;
+                }
+                if (storage_write(storage, x, item_age)) {
+                    chunks_free++; // Allow stopping if we've done enough this 
loop
+                    did_move = true;
+                    do_sleep = false;
+                    if (to_sleep > WRITE_SLEEP_MIN)
+                        to_sleep /= 2;
+                } else {
+                    break;
+                }
+            }
+
+            if (!did_move) {
+                backoff[x]++;
+            } else if (backoff[x]) {
+                backoff[x] /= 2;
+            }
+        }
+
+        // flip lock so we can be paused or stopped
+        pthread_mutex_unlock(&storage_write_plock);
+        if (do_sleep) {
+            usleep(to_sleep);
+            to_sleep *= 2;
+        }
+        pthread_mutex_lock(&storage_write_plock);
+    }
+    return NULL;
+}
+
+// TODO
+// logger needs logger_destroy() to exist/work before this is safe.
+/*int stop_storage_write_thread(void) {
+    int ret;
+    pthread_mutex_lock(&lru_maintainer_lock);
+    do_run_lru_maintainer_thread = 0;
+    pthread_mutex_unlock(&lru_maintainer_lock);
+    // WAKEUP SIGNAL
+    if ((ret = pthread_join(lru_maintainer_tid, NULL)) != 0) {
+        fprintf(stderr, "Failed to stop LRU maintainer thread: %s\n", 
strerror(ret));
+        return -1;
+    }
+    settings.lru_maintainer_thread = false;
+    return 0;
+}*/
+
+void storage_write_pause(void) {
+    pthread_mutex_lock(&storage_write_plock);
+}
+
+void storage_write_resume(void) {
+    pthread_mutex_unlock(&storage_write_plock);
+}
+
+int start_storage_write_thread(void *arg) {
+    int ret;
+
+    pthread_mutex_init(&storage_write_plock, NULL);
+    if ((ret = pthread_create(&storage_write_tid, NULL,
+        storage_write_thread, arg)) != 0) {
+        fprintf(stderr, "Can't create storage_write thread: %s\n",
+            strerror(ret));
+        return -1;
+    }
+
+    return 0;
+}
+
+/*** COMPACTOR ***/
+
 /* Fetch stats from the external storage system and decide to compact.
  * If we're more than half full, start skewing how aggressively to run
  * compaction, up to a desired target when all pages are full.
@@ -253,7 +368,7 @@
                 io.len = ntotal;
                 io.mode = OBJ_IO_WRITE;
                 for (tries = 10; tries > 0; tries--) {
-                    if (extstore_write_request(storage, PAGE_BUCKET_COMPACT, 
&io) == 0) {
+                    if (extstore_write_request(storage, PAGE_BUCKET_COMPACT, 
PAGE_BUCKET_COMPACT, &io) == 0) {
                         memcpy(io.buf, it, io.len);
                         extstore_write(storage, &io);
                         do_update = true;
@@ -449,4 +564,88 @@
     return 0;
 }
 
+/*** UTILITY ***/
+// /path/to/file:100G:bucket1
+// FIXME: Modifies argument. copy instead?
+struct extstore_conf_file *storage_conf_parse(char *arg, unsigned int 
page_size) {
+    struct extstore_conf_file *cf = NULL;
+    char *b = NULL;
+    char *p = strtok_r(arg, ":", &b);
+    char unit = 0;
+    uint64_t multiplier = 0;
+    int base_size = 0;
+    if (p == NULL)
+        goto error;
+    // First arg is the filepath.
+    cf = calloc(1, sizeof(struct extstore_conf_file));
+    cf->file = strdup(p);
+
+    p = strtok_r(NULL, ":", &b);
+    if (p == NULL) {
+        fprintf(stderr, "must supply size to ext_path, ie: ext_path=/f/e:64m 
(M|G|T|P supported)\n");
+        goto error;
+    }
+    unit = tolower(p[strlen(p)-1]);
+    p[strlen(p)-1] = '\0';
+    // sigh.
+    switch (unit) {
+        case 'm':
+            multiplier = 1024 * 1024;
+            break;
+        case 'g':
+            multiplier = 1024 * 1024 * 1024;
+            break;
+        case 't':
+            multiplier = 1024 * 1024;
+            multiplier *= 1024 * 1024;
+            break;
+        case 'p':
+            multiplier = 1024 * 1024;
+            multiplier *= 1024 * 1024 * 1024;
+            break;
+    }
+    base_size = atoi(p);
+    multiplier *= base_size;
+    // page_count is nearest-but-not-larger-than pages * psize
+    cf->page_count = multiplier / page_size;
+    assert(page_size * cf->page_count <= multiplier);
+
+    // final token would be a default free bucket
+    p = strtok_r(NULL, ",", &b);
+    // TODO: We reuse the original DEFINES for now,
+    // but if lowttl gets split up this needs to be its own set.
+    if (p != NULL) {
+        if (strcmp(p, "compact") == 0) {
+            cf->free_bucket = PAGE_BUCKET_COMPACT;
+        } else if (strcmp(p, "lowttl") == 0) {
+            cf->free_bucket = PAGE_BUCKET_LOWTTL;
+        } else if (strcmp(p, "chunked") == 0) {
+            cf->free_bucket = PAGE_BUCKET_CHUNKED;
+        } else if (strcmp(p, "default") == 0) {
+            cf->free_bucket = PAGE_BUCKET_DEFAULT;
+        } else {
+            fprintf(stderr, "Unknown extstore bucket: %s\n", p);
+            goto error;
+        }
+    } else {
+        // TODO: is this necessary?
+        cf->free_bucket = PAGE_BUCKET_DEFAULT;
+    }
+
+    // TODO: disabling until compact algorithm is improved.
+    if (cf->free_bucket != PAGE_BUCKET_DEFAULT) {
+        fprintf(stderr, "ext_path only presently supports the default 
bucket\n");
+        goto error;
+    }
+
+    return cf;
+error:
+    if (cf) {
+        if (cf->file)
+            free(cf->file);
+        free(cf);
+    }
+    return NULL;
+}
+
 #endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/storage.h 
new/memcached-1.5.10/storage.h
--- old/memcached-1.5.9/storage.h       2018-07-06 06:21:26.000000000 +0200
+++ new/memcached-1.5.10/storage.h      2018-08-10 22:36:38.000000000 +0200
@@ -1,10 +1,13 @@
 #ifndef STORAGE_H
 #define STORAGE_H
 
-int lru_maintainer_store(void *storage, const int clsid);
+int start_storage_write_thread(void *arg);
+void storage_write_pause(void);
+void storage_write_resume(void);
 int start_storage_compact_thread(void *arg);
 void storage_compact_pause(void);
 void storage_compact_resume(void);
+struct extstore_conf_file *storage_conf_parse(char *arg, unsigned int 
page_size);
 
 // Ignore pointers and header bits from the CRC
 #define STORE_OFFSET offsetof(item, nbytes)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/t/binary-extstore.t 
new/memcached-1.5.10/t/binary-extstore.t
--- old/memcached-1.5.9/t/binary-extstore.t     2018-07-06 06:21:26.000000000 
+0200
+++ new/memcached-1.5.10/t/binary-extstore.t    2018-08-10 22:36:38.000000000 
+0200
@@ -17,7 +17,7 @@
 
 $ext_path = "/tmp/extstore.$$";
 
-my $server = new_memcached("-m 64 -U 0 -o 
ext_page_size=8,ext_page_count=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,no_lru_crawler,slab_automove=0");
+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,no_lru_crawler,slab_automove=0");
 ok($server, "started the server");
 
 # Based almost 100% off testClient.py which is:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/t/chunked-extstore.t 
new/memcached-1.5.10/t/chunked-extstore.t
--- old/memcached-1.5.9/t/chunked-extstore.t    2018-07-06 06:21:26.000000000 
+0200
+++ new/memcached-1.5.10/t/chunked-extstore.t   2018-08-10 22:36:38.000000000 
+0200
@@ -18,7 +18,7 @@
 
 $ext_path = "/tmp/extstore.$$";
 
-my $server = new_memcached("-m 64 -U 0 -o 
ext_page_size=8,ext_page_count=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,slab_chunk_max=16384,slab_automove=0,ext_compact_under=1");
+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_chunk_max=16384,slab_automove=0,ext_compact_under=1");
 my $sock = $server->sock;
 
 # Wait until all items have flushed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/t/error-extstore.t 
new/memcached-1.5.10/t/error-extstore.t
--- old/memcached-1.5.9/t/error-extstore.t      2018-07-08 05:19:49.000000000 
+0200
+++ new/memcached-1.5.10/t/error-extstore.t     2018-08-10 22:36:38.000000000 
+0200
@@ -20,7 +20,7 @@
 
 $ext_path = "/tmp/extstore.$$";
 
-my $server = new_memcached("-m 64 -I 4m -U 0 -o 
ext_page_size=8,ext_page_count=8,ext_wbuf_size=8,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,slab_automove=0,ext_compact_under=1");
+my $server = new_memcached("-m 64 -I 4m -U 0 -o 
ext_page_size=8,ext_wbuf_size=8,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");
 my $sock = $server->sock;
 
 # Wait until all items have flushed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/t/extstore-buckets.t 
new/memcached-1.5.10/t/extstore-buckets.t
--- old/memcached-1.5.9/t/extstore-buckets.t    2018-06-27 22:12:55.000000000 
+0200
+++ new/memcached-1.5.10/t/extstore-buckets.t   2018-08-10 22:36:38.000000000 
+0200
@@ -17,7 +17,7 @@
 
 $ext_path = "/tmp/extstore.$$";
 
-my $server = new_memcached("-m 256 -U 0 -o 
ext_page_size=8,ext_page_count=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,ext_path=$ext_path,ext_low_ttl=60,slab_automove=1");
+my $server = new_memcached("-m 256 -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,ext_path=$ext_path:64m,ext_low_ttl=60,slab_automove=1");
 my $sock = $server->sock;
 
 my $value;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/t/extstore-jbod.t 
new/memcached-1.5.10/t/extstore-jbod.t
--- old/memcached-1.5.9/t/extstore-jbod.t       1970-01-01 01:00:00.000000000 
+0100
+++ new/memcached-1.5.10/t/extstore-jbod.t      2018-08-10 22:36:38.000000000 
+0200
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+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;
+my $ext_path2;
+
+if (!supports_extstore()) {
+    plan skip_all => 'extstore not enabled';
+    exit 0;
+}
+
+$ext_path = "/tmp/extstore1.$$";
+$ext_path2 = "/tmp/extstore2.$$";
+
+my $server = new_memcached("-m 256 -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,ext_path=$ext_path2:96m,slab_automove=1");
+my $sock = $server->sock;
+
+my $value;
+{
+    my @chars = ("C".."Z");
+    for (1 .. 20000) {
+        $value .= $chars[rand @chars];
+    }
+}
+
+# fill some larger objects
+{
+    # interleave sets with 0 ttl vs long ttl's.
+    my $keycount = 3700;
+    for (1 .. $keycount) {
+        print $sock "set nfoo$_ 0 0 20000 noreply\r\n$value\r\n";
+        print $sock "set lfoo$_ 0 0 20000 noreply\r\n$value\r\n";
+    }
+    # wait for a flush
+    wait_ext_flush($sock);
+    # delete half
+    mem_get_is($sock, "nfoo1", $value);
+    for (1 .. $keycount) {
+        print $sock "delete lfoo$_ noreply\r\n";
+    }
+    print $sock "lru_crawler crawl all\r\n";
+    <$sock>;
+    sleep 10;
+    # fetch
+    # check extstore counters
+    my $stats = mem_stats($sock);
+    is($stats->{evictions}, 0, 'no RAM evictions');
+    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');
+    cmp_ok($stats->{extstore_page_reclaims}, '>', 1, 'at least two pages 
reclaimed');
+}
+
+done_testing();
+
+END {
+    unlink $ext_path if $ext_path;
+    unlink $ext_path2 if $ext_path2;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/t/extstore.t 
new/memcached-1.5.10/t/extstore.t
--- old/memcached-1.5.9/t/extstore.t    2018-07-06 06:21:26.000000000 +0200
+++ new/memcached-1.5.10/t/extstore.t   2018-08-10 22:36:38.000000000 +0200
@@ -17,13 +17,14 @@
 
 $ext_path = "/tmp/extstore.$$";
 
-my $server = new_memcached("-m 64 -U 0 -o 
ext_page_size=8,ext_page_count=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,slab_automove=0,ext_compact_under=1");
+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");
 my $sock = $server->sock;
 
 # Wait until all items have flushed
 sub wait_for_ext {
-    my $sum = 1;
-    while ($sum != 0) {
+    my $target = shift || 0;
+    my $sum = $target + 1;
+    while ($sum > $target) {
         my $s = mem_stats($sock, "items");
         $sum = 0;
         for my $key (keys %$s) {
@@ -33,7 +34,7 @@
                 $sum += $s->{$key};
             }
         }
-        sleep 1 if $sum != 0;
+        sleep 1 if $sum > $target;
     }
 }
 
@@ -103,12 +104,17 @@
     my $keycount = 4000;
     for (1 .. $keycount) {
         print $sock "set mfoo$_ 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');
     is($stats->{miss_from_extstore}, 0, 'no misses');
-    mem_get_is($sock, "canary", undef);
+    # FIXME: test is flaky; something can rescue the canary because of a race
+    # condition. might need to roundtrip twice or disable compaction?
+    #mem_get_is($sock, "canary", undef);
 
     # check counters
     $stats = mem_stats($sock);
@@ -116,7 +122,7 @@
     cmp_ok($stats->{extstore_objects_evicted}, '>', 0, 'at least one object 
evicted');
     cmp_ok($stats->{extstore_bytes_evicted}, '>', 0, 'some bytes evicted');
     cmp_ok($stats->{extstore_pages_free}, '<', 2, 'few pages are free');
-    is($stats->{miss_from_extstore}, 1, 'exactly one miss');
+    #is($stats->{miss_from_extstore}, 1, 'exactly one miss');
 
     # refresh some keys so rescues happen while drop_unread == 1.
     for (1 .. $keycount / 2) {
@@ -153,7 +159,7 @@
     for (1 .. $keycount) {
         print $sock "set bfoo$_ 0 0 20000 noreply\r\n$value\r\n";
     }
-    sleep 4;
+    wait_for_ext();
 
     # incr should be blocked.
     print $sock "incr bfoo1 1\r\n";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/t/flush-all.t 
new/memcached-1.5.10/t/flush-all.t
--- old/memcached-1.5.9/t/flush-all.t   2018-05-09 02:12:30.000000000 +0200
+++ new/memcached-1.5.10/t/flush-all.t  2018-08-10 22:36:38.000000000 +0200
@@ -40,7 +40,7 @@
 print $sock "set foo 0 0 4\r\n1234\r\n";
 is(scalar <$sock>, "STORED\r\n", "stored foo = '1234'");
 mem_get_is($sock, "foo", '1234');
-sleep(3);
+sleep(5);
 mem_get_is($sock, "foo", undef);
 
 print $sock "set foo 0 0 5\r\n12345\r\n";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/t/lib/MemcachedTest.pm 
new/memcached-1.5.10/t/lib/MemcachedTest.pm
--- old/memcached-1.5.9/t/lib/MemcachedTest.pm  2018-06-27 22:12:55.000000000 
+0200
+++ new/memcached-1.5.10/t/lib/MemcachedTest.pm 2018-08-10 22:36:38.000000000 
+0200
@@ -14,13 +14,33 @@
 my @unixsockets = ();
 
 @EXPORT = qw(new_memcached sleep mem_get_is mem_gets mem_gets_is mem_stats
-             supports_sasl free_port supports_drop_priv supports_extstore);
+             supports_sasl free_port supports_drop_priv supports_extstore
+             wait_ext_flush);
 
 sub sleep {
     my $n = shift;
     select undef, undef, undef, $n;
 }
 
+# Wait until all items have flushed
+sub wait_ext_flush {
+    my $sock = shift;
+    my $target = shift || 0;
+    my $sum = $target + 1;
+    while ($sum > $target) {
+        my $s = mem_stats($sock, "items");
+        $sum = 0;
+        for my $key (keys %$s) {
+            if ($key =~ m/items:(\d+):number/) {
+                # Ignore classes which can contain extstore items
+                next if $1 < 3;
+                $sum += $s->{$key};
+            }
+        }
+        sleep 1 if $sum > $target;
+    }
+}
+
 sub mem_stats {
     my ($sock, $type) = @_;
     $type = $type ? " $type" : "";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/thread.c 
new/memcached-1.5.10/thread.c
--- old/memcached-1.5.9/thread.c        2018-07-06 02:59:50.000000000 +0200
+++ new/memcached-1.5.10/thread.c       2018-08-10 22:36:38.000000000 +0200
@@ -144,6 +144,7 @@
             lru_crawler_pause();
 #ifdef EXTSTORE
             storage_compact_pause();
+            storage_write_pause();
 #endif
         case PAUSE_WORKER_THREADS:
             buf[0] = 'p';
@@ -155,6 +156,7 @@
             lru_crawler_resume();
 #ifdef EXTSTORE
             storage_compact_resume();
+            storage_write_resume();
 #endif
         case RESUME_WORKER_THREADS:
             pthread_mutex_unlock(&worker_hang_lock);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/timedrun.c 
new/memcached-1.5.10/timedrun.c
--- old/memcached-1.5.9/timedrun.c      2018-06-27 22:12:55.000000000 +0200
+++ new/memcached-1.5.10/timedrun.c     2018-08-10 22:36:38.000000000 +0200
@@ -7,11 +7,11 @@
 
 #include <assert.h>
 
-static int caught = 0;
+volatile sig_atomic_t caught_sig = 0;
 
-static void caught_signal(int which)
+static void signal_handler(int which)
 {
-    caught = which;
+    caught_sig = which;
 }
 
 static int wait_for_process(pid_t pid)
@@ -21,7 +21,7 @@
     int i = 0;
     struct sigaction sig_handler;
 
-    sig_handler.sa_handler = caught_signal;
+    sig_handler.sa_handler = signal_handler;
     sig_handler.sa_flags = 0;
 
     sigaction(SIGALRM, &sig_handler, NULL);
@@ -44,8 +44,8 @@
             switch (i) {
             case 0:
                 /* On the first iteration, pass the signal through */
-                sig = caught > 0 ? caught : SIGTERM;
-                if (caught == SIGALRM) {
+                sig = caught_sig > 0 ? caught_sig : SIGTERM;
+                if (caught_sig == SIGALRM) {
                    fprintf(stderr, "Timeout.. killing the process\n");
                 }
                 break;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/memcached-1.5.9/version.m4 
new/memcached-1.5.10/version.m4
--- old/memcached-1.5.9/version.m4      2018-07-08 05:24:03.000000000 +0200
+++ new/memcached-1.5.10/version.m4     2018-08-10 22:40:21.000000000 +0200
@@ -1 +1 @@
-m4_define([VERSION_NUMBER], [1.5.9])
+m4_define([VERSION_NUMBER], [1.5.10])


Reply via email to