From f05cd5b77ca7b17440bfc7ed9f48f7c11d269767 Mon Sep 17 00:00:00 2001 From: Trond Norbye Date: Mon, 11 May 2009 13:51:40 +0200 Subject: [PATCH] Added libmemcachedutil containing utility functions --- .hgignore | 1 + Makefile.am | 2 +- config/util.m4 | 20 ++++ configure.ac | 3 +- docs/Makefile.am | 27 +++++ docs/libmemcachedutil.pod | 41 +++++++ docs/memcached_pool.pod | 75 ++++++++++++ libmemcached/Makefile.am | 4 + libmemcached/memcached_pool.h | 30 +++++ libmemcached/memcached_util.h | 23 ++++ libmemcachedutil/Makefile.am | 9 ++ libmemcachedutil/memcached_pool.c | 186 ++++++++++++++++++++++++++++++ tests/Makefile.am | 5 + tests/function.c | 64 ++++++++++ 14 files changed, 488 insertions(+), 2 deletions(-) create mode 100644 config/util.m4 create mode 100755 docs/libmemcachedutil.pod create mode 100755 docs/memcached_pool.pod create mode 100644 libmemcached/memcached_pool.h create mode 100644 libmemcached/memcached_util.h create mode 100644 libmemcachedutil/Makefile.am create mode 100644 libmemcachedutil/memcached_pool.c diff --git a/.hgignore b/.hgignore index 7988d72c..ad0a989f 100644 --- a/.hgignore +++ b/.hgignore @@ -15,6 +15,7 @@ # Build artifacts ^libmemcached/libmemcached.la$ +^libmemcachedutil/libmemcachedutil.la$ ^clients/mem(cat|cp|flush|rm|slap|stat|error)$ ^tests/testapp$ .(deps|libs)/*$ diff --git a/Makefile.am b/Makefile.am index f4a98c4b..c2de2f6d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ INCLUDES = -SUBDIRS = docs libmemcached tests support clients +SUBDIRS = docs libmemcached libmemcachedutil tests support clients EXTRA_dist = README.FIRST test: all diff --git a/config/util.m4 b/config/util.m4 new file mode 100644 index 00000000..e1fc17b4 --- /dev/null +++ b/config/util.m4 @@ -0,0 +1,20 @@ +BUILD_UTILLIB=yes + +AC_ARG_ENABLE(utils, + [ --enable-utils Build libmemcachedutils [[default=yes]]], + [ + if test "x$enableval" = "xno"; then + BUILD_UTILLIB="no" + fi + ] + ) + +if test "x$BUILD_UTILLIB" = "xyes"; then + AC_SEARCH_LIBS(pthread_create, pthread) + if test "x$ac_cv_search_pthread_create" = "xno"; then + AC_MSG_ERROR([Sorry you need POSIX thread library to build libmemcachedutil.]) + fi + AC_DEFINE([HAVE_LIBMEMCACHEDUTIL], [1], [Enables libmemcachedutil Support]) +fi + +AM_CONDITIONAL([BUILD_LIBMEMCACHEDUTIL],[test "x$BUILD_UTILLIB" = "xyes"]) diff --git a/configure.ac b/configure.ac index a84ae071..ba496a49 100644 --- a/configure.ac +++ b/configure.ac @@ -65,6 +65,7 @@ sinclude(config/protocol_binary.m4) sinclude(config/memcached.m4) sinclude(config/setsockopt.m4) sinclude(config/hsieh.m4) +sinclude(config/util.m4) # We only support GCC and Sun's forte at the moment if test "$GCC" = "yes" @@ -88,4 +89,4 @@ fi LDFLAGS="-lm" -AC_OUTPUT(Makefile clients/Makefile tests/Makefile docs/Makefile libmemcached/Makefile support/Makefile support/libmemcached.pc support/libmemcached.spec support/libmemcached-fc.spec) +AC_OUTPUT(Makefile clients/Makefile tests/Makefile docs/Makefile libmemcached/Makefile libmemcachedutil/Makefile support/Makefile support/libmemcached.pc support/libmemcached.spec support/libmemcached-fc.spec) diff --git a/docs/Makefile.am b/docs/Makefile.am index 1d48b1c8..becd24e5 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -1,6 +1,7 @@ INCLUDES = include EXTRA_DIST = libmemcached.pod\ + libmemcachedutil.pod\ memcached_flush.pod\ memcached_stats.pod\ memrm.pod\ @@ -18,6 +19,7 @@ EXTRA_DIST = libmemcached.pod\ memcached_server_st.pod\ memcat.pod\ memcached_create.pod\ + memcached_pool.pod\ memcached_servers.pod\ memcp.pod\ memcached_delete.pod\ @@ -90,9 +92,21 @@ man_MANS = libmemcached.3\ memcached_flush_buffers.3\ memcached_generate_hash_value.3 +if BUILD_LIBMEMCACHEDUTIL +man_MANS+= libmemcachedutil.3 \ + memcached_pool_create.3 \ + memcached_pool_destroy.3 \ + memcached_pool_push.3 \ + memcached_pool_pop.3 +endif + + libmemcached.3: libmemcached.pod @POD2MAN@ -c "libmemcached" -r "" -s 3 libmemcached.pod > libmemcached.3 +libmemcachedutil.3: libmemcachedutil.pod + @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 libmemcachedutil.pod > libmemcachedutil.3 + libmemcached_examples.3: libmemcached_examples.pod @POD2MAN@ -c "libmemcached" -r "" -s 3 libmemcached_examples.pod > libmemcached_examples.3 @@ -285,6 +299,18 @@ memcached_analyze.3: memcached_analyze.pod memcached_generate_hash_value.3: memcached_generate_hash_value.pod @POD2MAN@ -c "libmemcached" -r "" -s 3 memcached_generate_hash_value.pod > memcached_generate_hash_value.3 +memcached_pool_create.3: memcached_pool.pod + @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 memcached_pool.pod > memcached_pool_create.3 + +memcached_pool_destroy.3: memcached_pool.pod + @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 memcached_pool.pod > memcached_pool_destroy.3 + +memcached_pool_pop.3: memcached_pool.pod + @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 memcached_pool.pod > memcached_pool_pop.3 + +memcached_pool_push.3: memcached_pool.pod + @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 memcached_pool.pod > memcached_pool_push.3 + memcp.1: memcp.pod @POD2MAN@ -c "libmemcached" -r "" -s 1 memcp.pod > memcp.1 @@ -332,6 +358,7 @@ test: podchecker memcached_version.pod podchecker memflush.pod podchecker memcached_flush_buffers.pod + podchecker memcached_pool.pod html: pod2htmltree "/libmemcached" . diff --git a/docs/libmemcachedutil.pod b/docs/libmemcachedutil.pod new file mode 100755 index 00000000..d5c6b485 --- /dev/null +++ b/docs/libmemcachedutil.pod @@ -0,0 +1,41 @@ +=head1 NAME + +libmemcachedutil - Utility library for libmemcached + +=head1 LIBRARY + +C Client Library containing utility functions for libmemcached (libmemcachedutil, -lmemcachedutil) + +=head1 SYNOPSIS + + cc [ flag ... ] file ... -lmemcachedutil + + #include + +=head1 DESCRIPTION + +B is a small and thread-safe client library that provides +extra functionality built on top of B. + +=head1 THREADS AND PROCESSES + +When using threads or forked processes it is important to keep an instance +of C per process or thread. Without creating your own locking +structures you can not share a single C. You can though call +memcached_quit(3) on a C and then use the resulting cloned +structure. + +=head1 HOME + +To find out more information please check: +L + +=head1 AUTHOR + +Trond Norbye, Etrond.norbye@sun.comE + +=head1 SEE ALSO + +libmemcached(3) memcached_pool_create(3) memcached_pool_destroy(3) memcached_pool_pop(3) memcached_pool_push(3) + +=cut diff --git a/docs/memcached_pool.pod b/docs/memcached_pool.pod new file mode 100755 index 00000000..c0a818e8 --- /dev/null +++ b/docs/memcached_pool.pod @@ -0,0 +1,75 @@ +=head1 NAME + +memcached_pool_create, memcached_pool_destroy, memcached_pool_push, memcached_pool_pop + +=head1 LIBRARY + +C Client Library for memcached (libmemcachedutil, -lmemcachedutil) + +=head1 SYNOPSIS + + #include + + memcached_pool_st *memcached_pool_create(memcached_st* mmc, int initial, int max); + memcached_st* memcached_pool_destroy(memcached_pool_st* pool); + + memcached_st* memcached_pool_pop(memcached_pool_st* pool, bool block); + void memcached_pool_push(memcached_pool_st* pool, memcached_st *mmc); + + memcached_st *memcached_create (memcached_st *ptr); + + +=head1 DESCRIPTION + +memcached_pool_create() is used to create a connection pool of objects you +may use to remove the overhead of using memcached_clone for short +lived C objects. The mmc argument should be an +initialised C structure, and a successfull invocation of +memcached_pool_create takes full ownership of the variable (until it +is released by memcached_pool_destroy). The C argument +specifies the initial size of the connection pool, and the C +argument specifies the maximum size the connection pool should grow +to. Please note that the library will allocate a fixed size buffer +scaled to the max size of the connection pool, so you should not pass +MAXINT or some other large number here. + +memcached_pool_destroy() is used to destroy the connection pool +created with memcached_pool_create() and release all allocated +resources. It will return the pointer to the C structure +passed as an argument to memcached_pool_create(), and returns the +ownership of the pointer to the caller. + +memcached_pool_pop() is used to grab a connection structure from the +connection pool. The block argument specifies if the function should +block and wait for a connection structure to be available if we try to +exceed the maximum size. + +memcached_pool_push() is used to return a connection structure back to the pool. + +=head1 RETURN + +memcached_pool_create() returns a pointer to the newly created +memcached_pool_st structure. On an allocation failure, it returns +NULL. + +memcached_pool_destroy() returns the pointer (and ownership) to the +memcached_st structure used to create the pool. If connections are in +use it returns NULL. + +memcached_pool_pop() returns a pointer to a memcached_st structure +from the pool (or NULL if an allocation cannot be satisfied). + +=head1 HOME + +To find out more information please check: +L + +=head1 AUTHOR + +Trond Norbye, Etrond.norbye@sun.comE + +=head1 SEE ALSO + +memcached(1) libmemcached(3) memcached_create(3) memcached_free(3) libmemcachedutil(3) + +=cut diff --git a/libmemcached/Makefile.am b/libmemcached/Makefile.am index 19a60152..772564cd 100644 --- a/libmemcached/Makefile.am +++ b/libmemcached/Makefile.am @@ -26,6 +26,10 @@ pkginclude_HEADERS= memcached.h \ memcached_types.h \ memcached_watchpoint.h +if BUILD_LIBMEMCACHEDUTIL +pkginclude_HEADERS+= memcached_util.h memcached_pool.h +endif + lib_LTLIBRARIES = libmemcached.la libmemcached_la_SOURCES = crc.c \ diff --git a/libmemcached/memcached_pool.h b/libmemcached/memcached_pool.h new file mode 100644 index 00000000..e364feb5 --- /dev/null +++ b/libmemcached/memcached_pool.h @@ -0,0 +1,30 @@ +/* + * Summary: Connection pool implementation for libmemcached. + * + * Copy: See Copyright for the status of this software. + * + * Author: Trond Norbye + */ + +#ifndef MEMCACHED_POOL_H +#define MEMCACHED_POOL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct memcached_pool_st; +typedef struct memcached_pool_st memcached_pool_st; +memcached_pool_st *memcached_pool_create(memcached_st* mmc, int initial, + int max); +memcached_st* memcached_pool_destroy(memcached_pool_st* pool); +memcached_st* memcached_pool_pop(memcached_pool_st* pool, bool block); +void memcached_pool_push(memcached_pool_st* pool, memcached_st *mmc); + +#ifdef __cplusplus +} +#endif + +#endif /* MEMCACHED_POOL_H */ diff --git a/libmemcached/memcached_util.h b/libmemcached/memcached_util.h new file mode 100644 index 00000000..ec8afa80 --- /dev/null +++ b/libmemcached/memcached_util.h @@ -0,0 +1,23 @@ +/* + * Summary: interface for libmemcached utility library + * Description: main include file for libmemcachedutil + * + * Copy: See Copyright for the status of this software. + * + * Author: Trond Norbye + */ + +#ifndef MEMCACHED_UTIL_H +#define MEMCACHED_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifdef __cplusplus +} +#endif + +#endif /* MEMCACHED_UTIL_H */ diff --git a/libmemcachedutil/Makefile.am b/libmemcachedutil/Makefile.am new file mode 100644 index 00000000..73e8f6de --- /dev/null +++ b/libmemcachedutil/Makefile.am @@ -0,0 +1,9 @@ +INCLUDES = -I$(top_builddir) +LIBS = @LIBS@ + +lib_LTLIBRARIES= +if BUILD_LIBMEMCACHEDUTIL +lib_LTLIBRARIES+= libmemcachedutil.la +endif + +libmemcachedutil_la_SOURCES= memcached_pool.c diff --git a/libmemcachedutil/memcached_pool.c b/libmemcachedutil/memcached_pool.c new file mode 100644 index 00000000..78ba40e4 --- /dev/null +++ b/libmemcachedutil/memcached_pool.c @@ -0,0 +1,186 @@ +#include "common.h" +#include "libmemcached/memcached_pool.h" +#include + +struct memcached_pool_st +{ + pthread_mutex_t mutex; + pthread_cond_t cond; + memcached_st *master; + memcached_st **mmc; + int firstfree; + int size; + int current_size; +}; + +/** + * Lock a the pthread mutex and handle error conditions. If we fail to + * lock the mutex there must be something really wrong and we cannot continue. + */ +static void mutex_enter(pthread_mutex_t *mutex) +{ + int ret; + do + ret= pthread_mutex_lock(mutex); + while (ret == -1 && errno == EINTR); + + if (ret == -1) + { + /* + ** This means something is seriously wrong (deadlock or an internal + ** error in the posix library. Print out an error message and abort + ** the program. + */ + fprintf(stderr, "pthread_mutex_lock failed: %s\n", strerror(errno)); + fflush(stderr); + abort(); + } +} + +/** + * Unlock a the pthread mutex and handle error conditions. + * All errors except EINTR is fatal errors and will terminate the program + * with a coredump. + */ +static void mutex_exit(pthread_mutex_t *mutex) { + int ret; + do + ret = pthread_mutex_unlock(mutex); + while (ret == -1 && errno == EINTR); + + if (ret == -1) + { + /* + ** This means something is seriously wrong (deadlock or an internal + ** error in the posix library. Print out an error message and abort + ** the program. + */ + fprintf(stderr, "pthread_mutex_unlock %s\n", strerror(errno)); + fflush(stderr); + abort(); + } +} + +/** + * Grow the connection pool by creating a connection structure and clone the + * original memcached handle. + */ +static int grow_pool(memcached_pool_st* pool) { + memcached_st *obj = calloc(1, sizeof(*obj)); + if (obj == NULL) + return -1; + + if (memcached_clone(obj, pool->master) == NULL) + { + free(obj); + return -1; + } + + pool->mmc[++pool->firstfree] = obj; + pool->current_size++; + + return 0; +} + +memcached_pool_st *memcached_pool_create(memcached_st* mmc, int initial, int max) +{ + memcached_pool_st* ret = NULL; + memcached_pool_st object = { .mutex = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .master = mmc, + .mmc = calloc(max, sizeof(memcached_st*)), + .firstfree = -1, + .size = max, + .current_size = 0 }; + + if (object.mmc != NULL) + { + ret = calloc(1, sizeof(*ret)); + if (ret == NULL) + { + free(object.mmc); + return NULL; + } + + *ret = object; + + /* Try to create the initial size of the pool. An allocation failure at + * this time is not fatal.. + */ + for (int ii=0; ii < initial; ++ii) + if (grow_pool(ret) == -1) + break; + } + + return ret; +} + +memcached_st* memcached_pool_destroy(memcached_pool_st* pool) +{ + memcached_st *ret = pool->master; + + for (int ii = 0; ii <= pool->firstfree; ++ii) + { + memcached_free(pool->mmc[ii]); + free(pool->mmc[ii]); + pool->mmc[ii] = NULL; + } + + pthread_mutex_destroy(&pool->mutex); + pthread_cond_destroy(&pool->cond); + free(pool->mmc); + free(pool); + + return ret; +} + +memcached_st* memcached_pool_pop(memcached_pool_st* pool, bool block) { + memcached_st *ret = NULL; + mutex_enter(&pool->mutex); + do + { + if (pool->firstfree > -1) + ret = pool->mmc[pool->firstfree--]; + else if (pool->current_size == pool->size) + { + if (!block) + { + mutex_exit(&pool->mutex); + return NULL; + } + + if (pthread_cond_wait(&pool->cond, &pool->mutex) == -1) + { + /* + ** This means something is seriously wrong (an internal error in the + ** posix library. Print out an error message and abort the program. + */ + fprintf(stderr, "pthread cond_wait %s\n", strerror(errno)); + fflush(stderr); + abort(); + } + } + else if (grow_pool(pool) == -1) + { + mutex_exit(&pool->mutex); + return NULL; + } + } + while (ret == NULL); + + mutex_exit(&pool->mutex); + return ret; +} + +void memcached_pool_push(memcached_pool_st* pool, memcached_st *mmc) +{ + mutex_enter(&pool->mutex); + pool->mmc[++pool->firstfree] = mmc; + + if (pool->firstfree == 0 && pool->current_size == pool->size) + { + /* we might have people waiting for a connection.. wake them up :-) */ + pthread_cond_broadcast(&pool->cond); + } + mutex_exit(&pool->mutex); +} diff --git a/tests/Makefile.am b/tests/Makefile.am index b06d208a..d085faf8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,11 @@ INCLUDES = -I$(top_builddir) LDADDS = $(top_builddir)/libmemcached/libmemcached.la +if BUILD_LIBMEMCACHEDUTIL +LDADDS+= $(top_builddir)/libmemcachedutil/libmemcachedutil.la +endif + + EXTRA_DIST = output.res output2.res\ r/memcat.res\ r/memcp.res\ diff --git a/tests/function.c b/tests/function.c index bd03ee70..d7e70ac2 100644 --- a/tests/function.c +++ b/tests/function.c @@ -24,6 +24,11 @@ #include "test.h" +#ifdef HAVE_LIBMEMCACHEDUTIL +#include +#include "libmemcached/memcached_util.h" +#endif + #define GLOBAL_COUNT 10000 #define GLOBAL2_COUNT 100 #define SERVERS_TO_CREATE 5 @@ -3370,6 +3375,62 @@ static test_return analyzer_test(memcached_st *memc) return TEST_SUCCESS; } +#ifdef HAVE_LIBMEMCACHEDUTIL +static void* connection_release(void *arg) { + struct { + memcached_pool_st* pool; + memcached_st* mmc; + } *resource= arg; + + usleep(250); + memcached_pool_push(resource->pool, resource->mmc); +} + +static test_return connection_pool_test(memcached_st *memc) +{ + memcached_pool_st* pool= memcached_pool_create(memc, 5, 10); + assert(pool != NULL); + memcached_st* mmc[10]; + + for (int x= 0; x < 10; ++x) { + mmc[x]= memcached_pool_pop(pool, false); + assert(mmc[x] != NULL); + } + + assert(memcached_pool_pop(pool, false) == NULL); + pthread_t tid; + struct { + memcached_pool_st* pool; + memcached_st* mmc; + } item= { .pool = pool, .mmc = mmc[9] }; + pthread_create(&tid, NULL, connection_release, &item); + mmc[9]= memcached_pool_pop(pool, true); + pthread_join(tid, NULL); + assert(mmc[9] == item.mmc); + const char *key= "key"; + size_t keylen= strlen(key); + + // verify that I can do ops with all connections + memcached_return rc; + rc= memcached_set(mmc[0], key, keylen, "0", 1, 0, 0); + assert(rc == MEMCACHED_SUCCESS); + + for (int x= 0; x < 10; ++x) { + uint64_t number_value; + rc= memcached_increment(mmc[x], key, keylen, 1, &number_value); + assert(rc == MEMCACHED_SUCCESS); + assert(number_value == (x+1)); + } + + // Release them.. + for (int x= 0; x < 10; ++x) + memcached_pool_push(pool, mmc[x]); + + assert(memcached_pool_destroy(pool) == memc); + return TEST_SUCCESS; +} +#endif + static void increment_request_id(uint16_t *id) { (*id)++; @@ -3779,6 +3840,9 @@ test_st tests[] ={ {"delete_through", 1, delete_through }, {"noreply", 1, noreply_test}, {"analyzer", 1, analyzer_test}, +#ifdef HAVE_LIBMEMCACHEDUTIL + {"connectionpool", 1, connection_pool_test }, +#endif {0, 0, 0} }; -- 2.30.2