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])
