diff --git a/.gitignore b/.gitignore index f3d6757ca4..1056ceb65a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,5 @@ memcached-*.tar.gz doc/protocol-binary-range.txt doc/protocol-binary.txt /sizes -/internal_tests /version.m4 +/testapp diff --git a/Makefile.am b/Makefile.am index af41fe4c48..1a2b5c6501 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,9 +1,11 @@ bin_PROGRAMS = memcached pkginclude_HEADERS = protocol_binary.h -noinst_PROGRAMS = memcached-debug sizes internal_tests +noinst_PROGRAMS = memcached-debug sizes testapp BUILT_SOURCES= +testapp_SOURCES = testapp.c util.c util.h + memcached_SOURCES = memcached.c memcached.h \ hash.c hash.h \ slabs.c slabs.h \ @@ -12,7 +14,12 @@ memcached_SOURCES = memcached.c memcached.h \ thread.c daemon.c \ stats.c stats.h \ util.c util.h \ - trace.h + trace.h cache.h + +if BUILD_CACHE +memcached_SOURCES += cache.c +testapp_SOURCES += cache.c +endif if BUILD_SOLARIS_PRIVS memcached_SOURCES += solaris_priv.c @@ -28,8 +35,6 @@ memcached_DEPENDENCIES = memcached_debug_DEPENDENCIES = CLEANFILES= -internal_tests_SOURCES = internal_tests.c util.c - if BUILD_DTRACE BUILT_SOURCES += memcached_dtrace.h CLEANFILES += memcached_dtrace.h @@ -62,9 +67,9 @@ EXTRA_DIST = doc scripts TODO t memcached.spec memcached_dtrace.d version.m4 MOSTLYCLEANFILES = *.gcov *.gcno *.gcda *.tcov -test: memcached-debug internal_tests sizes +test: memcached-debug sizes testapp $(srcdir)/sizes - $(srcdir)/internal_tests + $(srcdir)/testapp prove $(srcdir)/t @if test `basename $(PROFILER)` = "gcov"; then \ for file in memcached_debug-*.gc??; do \ diff --git a/cache.c b/cache.c new file mode 100644 index 0000000000..636cf45658 --- /dev/null +++ b/cache.c @@ -0,0 +1,146 @@ +/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +#include +#include +#include + +#ifndef NDEBUG +#include +#endif + +#include "cache.h" + +#ifndef NDEBUG +const uint64_t redzone_pattern = 0xdeadbeefcafebabe; +int cache_error = 0; +#endif + +const int initial_pool_size = 64; + +cache_t* cache_create(const char *name, size_t bufsize, size_t align, + cache_constructor_t* constructor, + cache_destructor_t* destructor) { + cache_t* ret = calloc(1, sizeof(cache_t)); + char* nm = strdup(name); + void** ptr = calloc(initial_pool_size, bufsize); + if (ret == NULL || nm == NULL || ptr == NULL || + pthread_mutex_init(&ret->mutex, NULL) == -1) { + free(ret); + free(nm); + free(ptr); + return NULL; + } + + ret->name = nm; + ret->ptr = ptr; + ret->freetotal = initial_pool_size; + ret->constructor = constructor; + ret->destructor = destructor; + +#ifndef NDEBUG + ret->bufsize = bufsize + 2 * sizeof(redzone_pattern); +#else + ret->bufsize = bufsize; +#endif + + return ret; +} + +static inline void* get_object(void *ptr) { +#ifndef NDEBUG + uint64_t *pre = ptr; + return pre + 1; +#endif + return ptr; +} + +void cache_destroy(cache_t *cache) { + while (cache->freecurr > 0) { + void *ptr = cache->ptr[--cache->freecurr]; + if (cache->destructor) { + cache->destructor(get_object(ptr), NULL); + } + free(ptr); + } + free(cache->name); + free(cache->ptr); + pthread_mutex_destroy(&cache->mutex); +} + +void* cache_alloc(cache_t *cache) { + void *ret; + void *object; + pthread_mutex_lock(&cache->mutex); + if (cache->freecurr > 0) { + ret = cache->ptr[--cache->freecurr]; + object = get_object(ret); + } else { + object = ret = malloc(cache->bufsize); + if (ret != NULL) { + object = get_object(ret); + + if (cache->constructor != NULL && + cache->constructor(object, NULL, 0) != 0) { + free(ret); + object = NULL; + } + } + } + pthread_mutex_unlock(&cache->mutex); + +#ifndef NDEBUG + if (object != NULL) { + /* add a simple form of buffer-check */ + uint64_t *pre = ret; + *pre = redzone_pattern; + ret = pre+1; + memcpy(((char*)ret) + cache->bufsize - (2 * sizeof(redzone_pattern)), + &redzone_pattern, sizeof(redzone_pattern)); + } +#endif + + return object; +} + +void cache_free(cache_t *cache, void *ptr) { + pthread_mutex_lock(&cache->mutex); + +#ifndef NDEBUG + /* validate redzone... */ + if (memcmp(((char*)ptr) + cache->bufsize - (2 * sizeof(redzone_pattern)), + &redzone_pattern, sizeof(redzone_pattern)) != 0) { + raise(SIGABRT); + cache_error = 1; + pthread_mutex_unlock(&cache->mutex); + return; + } + uint64_t *pre = ptr; + --pre; + if (*pre != redzone_pattern) { + raise(SIGABRT); + cache_error = -1; + pthread_mutex_unlock(&cache->mutex); + return; + } + ptr = pre; +#endif + if (cache->freecurr < cache->freetotal) { + cache->ptr[cache->freecurr++] = ptr; + } else { + /* try to enlarge free connections array */ + size_t newtotal = cache->freetotal * 2; + void **new_free = realloc(cache->ptr, sizeof(char *) * newtotal); + if (new_free) { + cache->freetotal = newtotal; + cache->ptr = new_free; + cache->ptr[cache->freecurr++] = ptr; + } else { + if (cache->destructor) { + cache->destructor(ptr, NULL); + } + free(ptr); + + } + } + pthread_mutex_unlock(&cache->mutex); +} + diff --git a/cache.h b/cache.h new file mode 100644 index 0000000000..f720ee07e3 --- /dev/null +++ b/cache.h @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +#ifndef CACHE_H +#define CACHE_H +#include + +#ifdef HAVE_UMEM_H +#include +#define cache_t umem_cache_t +#define cache_alloc(a) umem_cache_alloc(a, UMEM_DEFAULT) +#define cache_free(a, b) umem_cache_free(a, b) +#define cache_create(a,b,c,d,e) umem_cache_create((char*)a, b, c, d, e, NULL, NULL, NULL, 0) +#define cache_destroy(a) umem_cache_destroy(a); + +#else + +#ifndef NDEBUG +/* may be used for debug purposes */ +extern int cache_error; +#endif + +typedef int cache_constructor_t(void *, void *, int); +typedef void cache_destructor_t(void *, void *); + +typedef struct { + pthread_mutex_t mutex; + char *name; + void **ptr; + size_t bufsize; + int freetotal; + int freecurr; + cache_constructor_t* constructor; + cache_destructor_t* destructor; +} cache_t; + + +cache_t* cache_create(const char *name, size_t bufsize, size_t align, + cache_constructor_t* constructor, + cache_destructor_t* destructor); +void cache_destroy(cache_t*); +void* cache_alloc(cache_t*); +void cache_free(cache_t*, void*); +#endif + +#endif diff --git a/configure.ac b/configure.ac index e96d7b8a77..65b0252055 100644 --- a/configure.ac +++ b/configure.ac @@ -230,7 +230,6 @@ dnl ---------------------------------------------------------------------------- AC_SEARCH_LIBS(socket, socket) AC_SEARCH_LIBS(gethostbyname, nsl) AC_SEARCH_LIBS(umem_cache_create, umem) - AC_HEADER_STDBOOL AC_C_CONST @@ -336,6 +335,14 @@ AC_CHECK_FUNCS(setppriv, [ AM_CONDITIONAL([BUILD_SOLARIS_PRIVS],[test "$build_solaris_privs" = "yes"]) +AC_CHECK_HEADER(umem.h, [ + AC_DEFINE([HAVE_UMEM_H], 1, + [Define this if you have umem.h]) + build_cache=no +], [build_cache=yes]) + +AM_CONDITIONAL([BUILD_CACHE], [test "x$build_cache" = "xyes"]) + AC_ARG_ENABLE(docs, [AS_HELP_STRING([--disable-docs],[Disable documentation generation])]) diff --git a/internal_tests.c b/internal_tests.c deleted file mode 100644 index 3429c26ac3..0000000000 --- a/internal_tests.c +++ /dev/null @@ -1,137 +0,0 @@ -/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -#undef NDEBUG -#include -#include -#include -#include - -#include "memcached.h" - -static void test_safe_strtoull(void); -static void test_safe_strtoul(void); -static void test_safe_strtoll(void); -static void test_safe_strtol(void); - -static void test_safe_strtoul() { - uint32_t val; - assert(safe_strtoul("123", &val)); - assert(val == 123); - assert(safe_strtoul("+123", &val)); - assert(val == 123); - assert(!safe_strtoul("", &val)); // empty - assert(!safe_strtoul("123BOGUS", &val)); // non-numeric - /* Not sure what it does, but this works with ICC :/ - assert(!safe_strtoul("92837498237498237498029383", &val)); // out of range - */ - - // extremes: - assert(safe_strtoul("4294967295", &val)); // 2**32 - 1 - assert(val == 4294967295L); - /* This actually works on 64-bit ubuntu - assert(!safe_strtoul("4294967296", &val)); // 2**32 - */ - assert(!safe_strtoul("-1", &val)); // negative -} - - -static void test_safe_strtoull() { - uint64_t val; - assert(safe_strtoull("123", &val)); - assert(val == 123); - assert(safe_strtoull("+123", &val)); - assert(val == 123); - assert(!safe_strtoull("", &val)); // empty - assert(!safe_strtoull("123BOGUS", &val)); // non-numeric - assert(!safe_strtoull("92837498237498237498029383", &val)); // out of range - - // extremes: - assert(safe_strtoull("18446744073709551615", &val)); // 2**64 - 1 - assert(val == 18446744073709551615ULL); - assert(!safe_strtoull("18446744073709551616", &val)); // 2**64 - assert(!safe_strtoull("-1", &val)); // negative -} - -static void test_safe_strtoll() { - int64_t val; - assert(safe_strtoll("123", &val)); - assert(val == 123); - assert(safe_strtoll("+123", &val)); - assert(val == 123); - assert(safe_strtoll("-123", &val)); - assert(val == -123); - assert(!safe_strtoll("", &val)); // empty - assert(!safe_strtoll("123BOGUS", &val)); // non-numeric - assert(!safe_strtoll("92837498237498237498029383", &val)); // out of range - - // extremes: - assert(!safe_strtoll("18446744073709551615", &val)); // 2**64 - 1 - assert(safe_strtoll("9223372036854775807", &val)); // 2**63 - 1 - assert(val == 9223372036854775807LL); - /* - assert(safe_strtoll("-9223372036854775808", &val)); // -2**63 - assert(val == -9223372036854775808LL); - */ - assert(!safe_strtoll("-9223372036854775809", &val)); // -2**63 - 1 - - // We'll allow space to terminate the string. And leading space. - assert(safe_strtoll(" 123 foo", &val)); - assert(val == 123); -} - -static void test_safe_strtol() { - int32_t val; - assert(safe_strtol("123", &val)); - assert(val == 123); - assert(safe_strtol("+123", &val)); - assert(val == 123); - assert(safe_strtol("-123", &val)); - assert(val == -123); - assert(!safe_strtol("", &val)); // empty - assert(!safe_strtol("123BOGUS", &val)); // non-numeric - assert(!safe_strtol("92837498237498237498029383", &val)); // out of range - - // extremes: - /* This actually works on 64-bit ubuntu - assert(!safe_strtol("2147483648", &val)); // (expt 2.0 31.0) - */ - assert(safe_strtol("2147483647", &val)); // (- (expt 2.0 31) 1) - assert(val == 2147483647L); - /* This actually works on 64-bit ubuntu - assert(!safe_strtol("-2147483649", &val)); // (- (expt -2.0 31) 1) - */ - - // We'll allow space to terminate the string. And leading space. - assert(safe_strtol(" 123 foo", &val)); - assert(val == 123); -} - -static void test_issue_44(void) { - char pidfile[80]; - char buffer[256]; - sprintf(pidfile, "/tmp/memcached.%d", getpid()); - sprintf(buffer, "./memcached-debug -p 0 -P %s -d", pidfile); - assert(system(buffer) == 0); - sleep(1); - FILE *fp = fopen(pidfile, "r"); - assert(fp); - assert(fgets(buffer, sizeof(buffer), fp)); - fclose(fp); - pid_t pid = atol(buffer); - assert(kill(pid, 0) == 0); - assert(kill(pid, SIGHUP) == 0); - sleep(1); - assert(kill(pid, 0) == 0); - assert(kill(pid, SIGTERM) == 0); - assert(remove(pidfile) == 0); -} - - -int main(int argc, char **argv) { - test_safe_strtoull(); - test_safe_strtoll(); - test_safe_strtoul(); - test_safe_strtol(); - test_issue_44(); - printf("OK.\n"); - return 0; -} diff --git a/memcached.c b/memcached.c index a2d4c59861..4d2bd98ba5 100644 --- a/memcached.c +++ b/memcached.c @@ -451,9 +451,7 @@ static void conn_cleanup(conn *c) { if (c->suffixleft != 0) { for (; c->suffixleft > 0; c->suffixleft--, c->suffixcurr++) { - if(suffix_add_to_freelist(*(c->suffixcurr))) { - free(*(c->suffixcurr)); - } + cache_free(c->thread->suffix_cache, *(c->suffixcurr)); } } @@ -612,72 +610,6 @@ static void conn_set_state(conn *c, enum conn_states state) { } } -/* - * Free list management for suffix buffers. - */ - -static char **freesuffix; -static int freesuffixtotal; -static int freesuffixcurr; -/* Lock for alternative item suffix freelist */ -static pthread_mutex_t suffix_lock = PTHREAD_MUTEX_INITIALIZER; - -static void suffix_init(void) { - freesuffixtotal = 500; - freesuffixcurr = 0; - - freesuffix = calloc(freesuffixtotal, sizeof(char *)); - if (freesuffix == NULL) { - fprintf(stderr, "Failed to allocate suffix pool\n"); - } - return; -} - -/* - * Returns a suffix buffer from the freelist, if any. - */ -char *suffix_from_freelist() { - char *s; - - pthread_mutex_lock(&suffix_lock); - if (freesuffixcurr > 0) { - s = freesuffix[--freesuffixcurr]; - } else { - /* If malloc fails, let the logic fall through without spamming - * STDERR on the server. */ - s = malloc( SUFFIX_SIZE ); - } - pthread_mutex_unlock(&suffix_lock); - - return s; -} - -/* - * Adds a connection to the freelist. 0 = success. Should call this using - * suffix_add_to_freelist() for thread safety. - */ -bool suffix_add_to_freelist(char *s) { - bool ret = true; - - pthread_mutex_lock(&suffix_lock); - if (freesuffixcurr < freesuffixtotal) { - freesuffix[freesuffixcurr++] = s; - ret = false; - } else { - /* try to enlarge free connections array */ - char **new_freesuffix = realloc(freesuffix, - sizeof(char *) * freesuffixtotal * 2); - if (new_freesuffix) { - freesuffixtotal *= 2; - freesuffix = new_freesuffix; - freesuffix[freesuffixcurr++] = s; - ret = false; - } - } - pthread_mutex_unlock(&suffix_lock); - return ret; -} - /* * Ensures that there is room for another struct iovec in a connection's * iov list. @@ -2354,7 +2286,7 @@ static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, } } - suffix = suffix_from_freelist(); + suffix = cache_alloc(c->thread->suffix_cache); if (suffix == NULL) { pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.get_cmds += stats_get_cmds; @@ -3386,10 +3318,7 @@ static void drive_machine(conn *c) { } while (c->suffixleft > 0) { char *suffix = *(c->suffixcurr); - if(suffix_add_to_freelist(suffix)) { - /* Failed to add to freelist, don't leak */ - free(suffix); - } + cache_free(c->thread->suffix_cache, suffix); c->suffixcurr++; c->suffixleft--; } @@ -4200,8 +4129,6 @@ int main (int argc, char **argv) { stats_init(); assoc_init(); conn_init(); - /* Hacky suffix buffers. */ - suffix_init(); slabs_init(settings.maxbytes, settings.factor, preallocate); /* diff --git a/memcached.h b/memcached.h index 50110448dc..9effb59e09 100644 --- a/memcached.h +++ b/memcached.h @@ -15,6 +15,7 @@ #include #include "protocol_binary.h" +#include "cache.h" /* Maximum length of a key. */ #define KEY_MAX_LENGTH 250 @@ -263,6 +264,7 @@ typedef struct { int notify_send_fd; /* sending end of notify pipe */ struct thread_stats stats; /* Stats generated by this thread */ struct conn_queue *new_conn_queue; /* queue of new connections to handle */ + cache_t *suffix_cache; /* suffix cache */ } LIBEVENT_THREAD; typedef struct conn conn; @@ -392,8 +394,6 @@ char *add_delta(conn *c, item *item, const int incr, const int64_t delta, void accept_new_conns(const bool do_accept); conn *conn_from_freelist(void); bool conn_add_to_freelist(conn *c); -char *suffix_from_freelist(void); -bool suffix_add_to_freelist(char *s); int is_listen_thread(void); item *item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes); char *item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes); diff --git a/testapp.c b/testapp.c new file mode 100644 index 0000000000..c0013ac949 --- /dev/null +++ b/testapp.c @@ -0,0 +1,308 @@ +/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "cache.h" +#include "util.h" + +enum test_return { TEST_SKIP, TEST_PASS, TEST_FAIL }; + +static enum test_return cache_create_test(void) +{ + cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*), + NULL, NULL); + assert(cache != NULL); + cache_destroy(cache); + return TEST_PASS; +} + +const uint64_t constructor_pattern = 0xdeadcafebabebeef; + +static int cache_constructor(void *buffer, void *notused1, int notused2) { + uint64_t *ptr = buffer; + *ptr = constructor_pattern; + return 0; +} + +static enum test_return cache_constructor_test(void) +{ + cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t), + cache_constructor, NULL); + assert(cache != NULL); + uint64_t *ptr = cache_alloc(cache); + uint64_t pattern = *ptr; + cache_free(cache, ptr); + cache_destroy(cache); + return (pattern == constructor_pattern) ? TEST_PASS : TEST_FAIL; +} + +static int cache_fail_constructor(void *buffer, void *notused1, int notused2) { + return 1; +} + +static enum test_return cache_fail_constructor_test(void) +{ + enum test_return ret = TEST_PASS; + + cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t), + cache_fail_constructor, NULL); + assert(cache != NULL); + uint64_t *ptr = cache_alloc(cache); + if (ptr != NULL) { + ret = TEST_FAIL; + } + cache_destroy(cache); + return ret; +} + +static void *destruct_data = 0; + +static void cache_destructor(void *buffer, void *notused) { + destruct_data = buffer; +} + +static enum test_return cache_destructor_test(void) +{ + cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*), + NULL, cache_destructor); + assert(cache != NULL); + char *ptr = cache_alloc(cache); + cache_free(cache, ptr); + cache_destroy(cache); + + return (ptr == destruct_data) ? TEST_PASS : TEST_FAIL; +} + +static enum test_return cache_reuse_test(void) +{ + int ii; + cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*), + NULL, NULL); + char *ptr = cache_alloc(cache); + cache_free(cache, ptr); + for (ii = 0; ii < 100; ++ii) { + char *p = cache_alloc(cache); + assert(p == ptr); + cache_free(cache, ptr); + } + cache_destroy(cache); + return TEST_PASS; +} + +static enum test_return cache_redzone_test(void) +{ +#ifndef HAVE_UMEM_H + cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*), + NULL, NULL); + + /* Ignore SIGABORT */ + struct sigaction old_action; + struct sigaction action = { .sa_handler = SIG_IGN, .sa_flags = 0}; + sigemptyset(&action.sa_mask); + sigaction(SIGABRT, &action, &old_action); + + /* check memory debug.. */ + char *p = cache_alloc(cache); + char old = *(p - 1); + *(p - 1) = 0; + cache_free(cache, p); + assert(cache_error == -1); + *(p - 1) = old; + + p[sizeof(uint32_t)] = 0; + cache_free(cache, p); + assert(cache_error == 1); + + /* restore signal handler */ + sigaction(SIGABRT, &old_action, NULL); + + cache_destroy(cache); + + return TEST_PASS; +#else + return TEST_SKIP; +#endif +} + +static enum test_return test_safe_strtoul(void) { + uint32_t val; + assert(safe_strtoul("123", &val)); + assert(val == 123); + assert(safe_strtoul("+123", &val)); + assert(val == 123); + assert(!safe_strtoul("", &val)); // empty + assert(!safe_strtoul("123BOGUS", &val)); // non-numeric + /* Not sure what it does, but this works with ICC :/ + assert(!safe_strtoul("92837498237498237498029383", &val)); // out of range + */ + + // extremes: + assert(safe_strtoul("4294967295", &val)); // 2**32 - 1 + assert(val == 4294967295L); + /* This actually works on 64-bit ubuntu + assert(!safe_strtoul("4294967296", &val)); // 2**32 + */ + assert(!safe_strtoul("-1", &val)); // negative + return TEST_PASS; +} + + +static enum test_return test_safe_strtoull(void) { + uint64_t val; + assert(safe_strtoull("123", &val)); + assert(val == 123); + assert(safe_strtoull("+123", &val)); + assert(val == 123); + assert(!safe_strtoull("", &val)); // empty + assert(!safe_strtoull("123BOGUS", &val)); // non-numeric + assert(!safe_strtoull("92837498237498237498029383", &val)); // out of range + + // extremes: + assert(safe_strtoull("18446744073709551615", &val)); // 2**64 - 1 + assert(val == 18446744073709551615ULL); + assert(!safe_strtoull("18446744073709551616", &val)); // 2**64 + assert(!safe_strtoull("-1", &val)); // negative + return TEST_PASS; +} + +static enum test_return test_safe_strtoll(void) { + int64_t val; + assert(safe_strtoll("123", &val)); + assert(val == 123); + assert(safe_strtoll("+123", &val)); + assert(val == 123); + assert(safe_strtoll("-123", &val)); + assert(val == -123); + assert(!safe_strtoll("", &val)); // empty + assert(!safe_strtoll("123BOGUS", &val)); // non-numeric + assert(!safe_strtoll("92837498237498237498029383", &val)); // out of range + + // extremes: + assert(!safe_strtoll("18446744073709551615", &val)); // 2**64 - 1 + assert(safe_strtoll("9223372036854775807", &val)); // 2**63 - 1 + assert(val == 9223372036854775807LL); + /* + assert(safe_strtoll("-9223372036854775808", &val)); // -2**63 + assert(val == -9223372036854775808LL); + */ + assert(!safe_strtoll("-9223372036854775809", &val)); // -2**63 - 1 + + // We'll allow space to terminate the string. And leading space. + assert(safe_strtoll(" 123 foo", &val)); + assert(val == 123); + return TEST_PASS; +} + +static enum test_return test_safe_strtol(void) { + int32_t val; + assert(safe_strtol("123", &val)); + assert(val == 123); + assert(safe_strtol("+123", &val)); + assert(val == 123); + assert(safe_strtol("-123", &val)); + assert(val == -123); + assert(!safe_strtol("", &val)); // empty + assert(!safe_strtol("123BOGUS", &val)); // non-numeric + assert(!safe_strtol("92837498237498237498029383", &val)); // out of range + + // extremes: + /* This actually works on 64-bit ubuntu + assert(!safe_strtol("2147483648", &val)); // (expt 2.0 31.0) + */ + assert(safe_strtol("2147483647", &val)); // (- (expt 2.0 31) 1) + assert(val == 2147483647L); + /* This actually works on 64-bit ubuntu + assert(!safe_strtol("-2147483649", &val)); // (- (expt -2.0 31) 1) + */ + + // We'll allow space to terminate the string. And leading space. + assert(safe_strtol(" 123 foo", &val)); + assert(val == 123); + return TEST_PASS; +} + +static enum test_return test_issue_44(void) { + char pidfile[80]; + char buffer[256]; + sprintf(pidfile, "/tmp/memcached.%d", getpid()); + sprintf(buffer, "./memcached-debug -p 0 -P %s -d", pidfile); + assert(system(buffer) == 0); + sleep(1); + FILE *fp = fopen(pidfile, "r"); + assert(fp); + assert(fgets(buffer, sizeof(buffer), fp)); + fclose(fp); + pid_t pid = atol(buffer); + assert(kill(pid, 0) == 0); + assert(kill(pid, SIGHUP) == 0); + sleep(1); + assert(kill(pid, 0) == 0); + assert(kill(pid, SIGTERM) == 0); + assert(remove(pidfile) == 0); + + return TEST_PASS; +} + + + +typedef enum test_return (*TEST_FUNC)(void); +struct testcase { + const char *description; + TEST_FUNC function; +}; + +struct testcase testcases[] = { + { "cache_create", cache_create_test }, + { "cache_constructor", cache_constructor_test }, + { "cache_constructor_fail", cache_fail_constructor_test }, + { "cache_destructor", cache_destructor_test }, + { "cache_reuse", cache_reuse_test }, + { "cache_redzone", cache_redzone_test }, + { "strtol", test_safe_strtol }, + { "strtoll", test_safe_strtoll }, + { "strtoul", test_safe_strtoul }, + { "strtoull", test_safe_strtoull }, + { "issue_44", test_issue_44 }, + { NULL, NULL } +}; + +int main(int argc, char **argv) +{ + int exitcode = 0; + int ii; + const int paddingsz = 60; + char padding[paddingsz + 1]; + memset(padding, ' ', paddingsz); + + for (ii = 0; testcases[ii].description != NULL; ++ii) { + int len = strlen(testcases[ii].description); + if (len > paddingsz) { + len = paddingsz; + } + padding[paddingsz - len] = '\0'; + fprintf(stdout, "%s:%s", testcases[ii].description, padding); + padding[paddingsz - len] = ' '; + fflush(stdout); + enum test_return ret = testcases[ii].function(); + if (ret == TEST_SKIP) { + fprintf(stdout, "[skipped]\n"); + } else if (ret == TEST_PASS) { + fprintf(stdout, "[ok]\n"); + } else { + fprintf(stdout, "[failed]\n"); + exitcode = 1; + } + fflush(stdout); + } + + return exitcode; +} diff --git a/thread.c b/thread.c index b90370984b..1cc01c9430 100644 --- a/thread.c +++ b/thread.c @@ -217,6 +217,13 @@ static void setup_thread(LIBEVENT_THREAD *me) { perror("Failed to initialize mutex"); exit(EXIT_FAILURE); } + + me->suffix_cache = cache_create("suffix", SUFFIX_SIZE, sizeof(char*), + NULL, NULL); + if (me->suffix_cache == NULL) { + fprintf(stderr, "Failed to create suffix cache\n"); + exit(EXIT_FAILURE); + } }