Added libmemcachedutil containing utility functions
authorTrond Norbye <trond.norbye@sun.com>
Mon, 11 May 2009 11:51:40 +0000 (13:51 +0200)
committerTrond Norbye <trond.norbye@sun.com>
Mon, 11 May 2009 11:51:40 +0000 (13:51 +0200)
14 files changed:
.hgignore
Makefile.am
config/util.m4 [new file with mode: 0644]
configure.ac
docs/Makefile.am
docs/libmemcachedutil.pod [new file with mode: 0755]
docs/memcached_pool.pod [new file with mode: 0755]
libmemcached/Makefile.am
libmemcached/memcached_pool.h [new file with mode: 0644]
libmemcached/memcached_util.h [new file with mode: 0644]
libmemcachedutil/Makefile.am [new file with mode: 0644]
libmemcachedutil/memcached_pool.c [new file with mode: 0644]
tests/Makefile.am
tests/function.c

index 7988d72c8a084a3730ccbf0e1c4e0b343bc79b77..ad0a989f835b39bcae23662b4d9fa1ba95921f0b 100644 (file)
--- 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)/*$
index f4a98c4be0ada8207850006869f62cce7d6a328c..c2de2f6d37e58a6163e8bfca1ec3c0b631dcbfdc 100644 (file)
@@ -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 (file)
index 0000000..e1fc17b
--- /dev/null
@@ -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"])
index a84ae0718fbed9ad25488a94a64255172c233fd8..ba496a4970337a7c26c1efd2fd34d661cc84acc2 100644 (file)
@@ -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)
index 1d48b1c81e1b1a3b28259a3a8af0932e640a6fee..becd24e5fbb22c4434ed5d9899bb6ab3810c4038 100644 (file)
@@ -1,6 +1,7 @@
 INCLUDES = include\r
 \r
 EXTRA_DIST = libmemcached.pod\\r
+       libmemcachedutil.pod\\r
        memcached_flush.pod\\r
        memcached_stats.pod\\r
        memrm.pod\\r
@@ -18,6 +19,7 @@ EXTRA_DIST = libmemcached.pod\
        memcached_server_st.pod\\r
        memcat.pod\\r
        memcached_create.pod\\r
+       memcached_pool.pod\\r
        memcached_servers.pod\\r
        memcp.pod\\r
        memcached_delete.pod\\r
@@ -90,9 +92,21 @@ man_MANS = libmemcached.3\
         memcached_flush_buffers.3\\r
         memcached_generate_hash_value.3\r
 \r
+if BUILD_LIBMEMCACHEDUTIL\r
+man_MANS+= libmemcachedutil.3 \\r
+       memcached_pool_create.3 \\r
+       memcached_pool_destroy.3 \\r
+       memcached_pool_push.3 \\r
+       memcached_pool_pop.3\r
+endif\r
+\r
+\r
 libmemcached.3: libmemcached.pod \r
        @POD2MAN@ -c "libmemcached" -r "" -s 3 libmemcached.pod > libmemcached.3\r
 \r
+libmemcachedutil.3: libmemcachedutil.pod \r
+       @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 libmemcachedutil.pod > libmemcachedutil.3\r
+\r
 libmemcached_examples.3: libmemcached_examples.pod\r
        @POD2MAN@ -c "libmemcached" -r "" -s 3 libmemcached_examples.pod > libmemcached_examples.3\r
 \r
@@ -285,6 +299,18 @@ memcached_analyze.3: memcached_analyze.pod
 memcached_generate_hash_value.3: memcached_generate_hash_value.pod\r
        @POD2MAN@ -c "libmemcached" -r "" -s 3 memcached_generate_hash_value.pod > memcached_generate_hash_value.3\r
 \r
+memcached_pool_create.3: memcached_pool.pod \r
+       @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 memcached_pool.pod > memcached_pool_create.3\r
+\r
+memcached_pool_destroy.3: memcached_pool.pod \r
+       @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 memcached_pool.pod > memcached_pool_destroy.3\r
+\r
+memcached_pool_pop.3: memcached_pool.pod \r
+       @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 memcached_pool.pod > memcached_pool_pop.3\r
+\r
+memcached_pool_push.3: memcached_pool.pod \r
+       @POD2MAN@ -c "libmemcachedutil" -r "" -s 3 memcached_pool.pod > memcached_pool_push.3\r
+\r
 memcp.1: memcp.pod\r
        @POD2MAN@ -c "libmemcached" -r "" -s 1 memcp.pod > memcp.1\r
 \r
@@ -332,6 +358,7 @@ test:
        podchecker memcached_version.pod\r
        podchecker memflush.pod\r
        podchecker memcached_flush_buffers.pod\r
+       podchecker memcached_pool.pod\r
 \r
 html:\r
        pod2htmltree "/libmemcached" .\r
diff --git a/docs/libmemcachedutil.pod b/docs/libmemcachedutil.pod
new file mode 100755 (executable)
index 0000000..d5c6b48
--- /dev/null
@@ -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 <libmemcached/memcached_util.h>
+
+=head1 DESCRIPTION
+
+B<libmemcachedutil> is a small and thread-safe client library that provides
+extra functionality built on top of B<libmemcached>.
+
+=head1 THREADS AND PROCESSES
+
+When using threads or forked processes it is important to keep an instance
+of C<memcached_st> per process or thread. Without creating your own locking
+structures you can not share a single C<memcached_st>. You can though call
+memcached_quit(3) on a C<memcached_st> and then use the resulting cloned
+structure.
+
+=head1 HOME
+
+To find out more information please check:
+L<http://tangent.org/552/libmemcached.html>
+
+=head1 AUTHOR
+
+Trond Norbye, E<lt>trond.norbye@sun.comE<gt>
+
+=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 (executable)
index 0000000..c0a818e
--- /dev/null
@@ -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 <libmemcached/memcached_pool.h>
+
+  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<memcached_st> objects. The mmc argument should be an
+initialised C<memcached_st> 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<initial> argument
+specifies the initial size of the connection pool, and the C<max>
+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<memcached_st> 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<http://tangent.org/552/libmemcached.html>
+
+=head1 AUTHOR
+
+Trond Norbye, E<lt>trond.norbye@sun.comE<gt>
+
+=head1 SEE ALSO
+
+memcached(1) libmemcached(3) memcached_create(3) memcached_free(3) libmemcachedutil(3)
+
+=cut
index 19a6015279e11e39b319cf4d12c43afb8035ac45..772564cd752f14259926a38e921b218f45d8c93f 100644 (file)
@@ -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 (file)
index 0000000..e364feb
--- /dev/null
@@ -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 <libmemcached/memcached.h>
+
+#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 (file)
index 0000000..ec8afa8
--- /dev/null
@@ -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 <libmemcached/memcached_pool.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MEMCACHED_UTIL_H */
diff --git a/libmemcachedutil/Makefile.am b/libmemcachedutil/Makefile.am
new file mode 100644 (file)
index 0000000..73e8f6d
--- /dev/null
@@ -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 (file)
index 0000000..78ba40e
--- /dev/null
@@ -0,0 +1,186 @@
+#include "common.h"
+#include "libmemcached/memcached_pool.h"
+#include <pthread.h>
+
+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);
+}
index b06d208ac9f3b04bfe73f8683bbf8fc0503b5c53..d085faf8dcac89cc6c7f5b5cb1dfc9d5c5e6c6e2 100644 (file)
@@ -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\
index bd03ee7012daf0c40ca93f89193b1313293e4708..d7e70ac2e2cd77644663dfad3a6c04f53a1a03c1 100644 (file)
 
 #include "test.h"
 
+#ifdef HAVE_LIBMEMCACHEDUTIL
+#include <pthread.h>
+#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}
 };