You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@apr.apache.org by pq...@apache.org on 2005/10/28 06:23:04 UTC

svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Author: pquerna
Date: Thu Oct 27 21:23:01 2005
New Revision: 329089

URL: http://svn.apache.org/viewcvs?rev=329089&view=rev
Log:
Import apr_memcache trunk to APR-Util.

Still TODO:
- Move CRC32 to a separate public function/API.
- Create a multi-get interface.
- Make locking and connection pooling optional.

Added:
    apr/apr-util/trunk/include/apr_memcache.h   (with props)
    apr/apr-util/trunk/memcache/
    apr/apr-util/trunk/memcache/apr_memcache.c   (with props)
Modified:
    apr/apr-util/trunk/build.conf

Modified: apr/apr-util/trunk/build.conf
URL: http://svn.apache.org/viewcvs/apr/apr-util/trunk/build.conf?rev=329089&r1=329088&r2=329089&view=diff
==============================================================================
--- apr/apr-util/trunk/build.conf (original)
+++ apr/apr-util/trunk/build.conf Thu Oct 27 21:23:01 2005
@@ -14,6 +14,7 @@
   hooks/*.c
   ldap/*.c
   misc/*.c
+  memcache/*.c
   uri/apr_uri.c
   xml/*.c
   strmatch/*.c

Added: apr/apr-util/trunk/include/apr_memcache.h
URL: http://svn.apache.org/viewcvs/apr/apr-util/trunk/include/apr_memcache.h?rev=329089&view=auto
==============================================================================
--- apr/apr-util/trunk/include/apr_memcache.h (added)
+++ apr/apr-util/trunk/include/apr_memcache.h Thu Oct 27 21:23:01 2005
@@ -0,0 +1,366 @@
+/* Copyright 2005 The Apache Software Foundation or its licensors, as
+ * applicable.
+ *
+ * Copyright 2004-2005 Paul Querna
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef APR_MEMCACHE_H
+#define APR_MEMCACHE_H
+
+/**
+ * @file apr_memcache.h
+ * @brief Client interface for memcached
+ * @remark To use this interface you must have a separate memcached
+ * server running. See the memcached website at http://www.danga.com/memcached/
+ * for more information.
+ */
+
+#include "apr.h"
+#include "apr_pools.h"
+#include "apr_time.h"
+#include "apr_strings.h"
+#include "apr_network_io.h"
+#include "apr_ring.h"
+#include "apr_buckets.h"
+#include "apr_reslist.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @defgroup APR_Util_MC Memcached Client Routines
+ * @ingroup APR_Util
+ * @{
+ */
+
+/** Specifies the status of a memcached server */
+typedef enum
+{
+    APR_MC_SERVER_LIVE, /**< Server is alive and responding to requests */
+    APR_MC_SERVER_DEAD  /**< Server is not responding to requests */
+} apr_memcache_server_status_t;
+
+/** Opaque memcache client connection object */
+typedef struct apr_memcache_conn_t apr_memcache_conn_t;
+
+/** Memcache Server Info Object */
+typedef struct apr_memcache_server_t apr_memcache_server_t;
+struct apr_memcache_server_t
+{
+    const char *host; /**< Hostname of this Server */
+    apr_port_t port; /**< Port of this Server */
+    apr_memcache_server_status_t status; /**< @see apr_memcache_server_status_t */
+#if APR_HAS_THREADS || defined(DOXYGEN)
+    apr_reslist_t *conns; /**< Resource list of actual client connections */
+#else
+    apr_memcache_conn_t *conn;
+#endif
+    apr_pool_t *p; /** Pool to use for private allocations */
+    apr_thread_mutex_t *lock;
+    apr_time_t btime;
+};
+
+/** Container for a set of memcached servers */
+typedef struct
+{
+    apr_uint32_t flags; /**< Flags, Not currently used */
+    apr_uint16_t nalloc; /**< Number of Servers Allocated */
+    apr_uint16_t ntotal; /**< Number of Servers Added */
+    apr_memcache_server_t **live_servers; /**< Array of Servers */
+    apr_pool_t *p; /** Pool to use for allocations */
+} apr_memcache_t;
+
+/**
+ * Creates a crc32 hash used to split keys between servers
+ * @param data Data to be hashed
+ * @param data_len Length of the data to use
+ * @return crc32 hash of data
+ * @remark The crc32 hash is not compatible with old memcached clients.
+ */
+APR_DECLARE(apr_uint32_t)
+apr_memcache_hash(const char *data, apr_uint32_t data_len);
+
+/**
+ * Picks a server based on a hash
+ * @param mc The memcache client object to use
+ * @param hash Hashed value of a Key
+ * @return server that controls specified hash
+ * @see apr_memcache_hash
+ */
+APR_DECLARE(apr_memcache_server_t *)
+apr_memcache_find_server_hash(apr_memcache_t *mc, const apr_uint32_t hash);
+
+/**
+ * Adds a server to a client object
+ * @param mc The memcache client object to use
+ * @param ms Server to add
+ * @remark Adding servers is not thread safe, and should be done once at startup.
+ * @warning Changing servers after startup may cause keys to go to
+ * different servers.
+ */
+APR_DECLARE(apr_status_t)
+apr_memcache_add_server(apr_memcache_t *mc,
+                        apr_memcache_server_t *server);
+
+
+/**
+ * Finds a Server object based on a hostname/port pair
+ * @param mc The memcache client object to use
+ * @param host Hostname of the server
+ * @param port Port of the server
+ * @return Server with matching Hostname and Port, or NULL if none was found.
+ */
+APR_DECLARE(apr_memcache_server_t *)
+apr_memcache_find_server(apr_memcache_t *mc,
+                         const char *host,
+                         apr_port_t port);
+
+/**
+ * Enables a Server for use again
+ * @param mc The memcache client object to use
+ * @param ms Server to Activate
+ */
+APR_DECLARE(apr_status_t)
+apr_memcache_enable_server(apr_memcache_t *mc,
+                           apr_memcache_server_t *ms);
+
+
+/**
+ * Disable a Server
+ * @param mc The memcache client object to use
+ * @param ms Server to Disable
+ */
+APR_DECLARE(apr_status_t)
+apr_memcache_disable_server(apr_memcache_t *mc,
+                            apr_memcache_server_t *ms);
+
+/**
+ * Creates a new Server Object
+ * @param p Pool to use
+ * @param host hostname of the server
+ * @param port port of the server
+ * @param min  minimum number of client sockets to open
+ * @param smax soft maximum number of client connections to open
+ * @param max  hard maximum number of client connections
+ * @param ttl  time to live in seconds of a client connection
+ * @param ns   location of the new server object
+ * @see apr_reslist_create
+ * @remark min, smax, and max are only used when APR_HAS_THREADS
+ */
+APR_DECLARE(apr_status_t)
+apr_memcache_server_create(apr_pool_t *p,
+                           const char *host,
+                           apr_port_t port,
+                           apr_uint32_t min,
+                           apr_uint32_t smax,
+                           apr_uint32_t max,
+                           apr_uint32_t ttl,
+                           apr_memcache_server_t **ns);
+/**
+ * Creates a new memcached client object
+ * @param p Pool to use
+ * @param max_servers maximum number of servers
+ * @param flags Not currently used
+ * @param mc   location of the new memcache client object
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_create(apr_pool_t *p,
+                    apr_uint16_t max_servers,
+                    apr_uint32_t flags,
+                    apr_memcache_t **mc);
+
+/**
+ * Gets a value from the server, allocating the value out of p
+ * @param mc client to use
+ * @param p Pool to use
+ * @param key null terminated string containing the key
+ * @param baton location of the allocated value
+ * @param len   length of data at baton
+ * @param flags any flags set by the client for this key
+ * @return 
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_getp(apr_memcache_t *mc, 
+                  apr_pool_t *p,
+                  const char* key,
+                  char **baton,
+                  apr_size_t *len,
+                  apr_uint16_t *flags);
+
+/**
+ * Sets a value by key on the server
+ * @param mc client to use
+ * @param key   null terminated string containing the key
+ * @param baton data to store on the server
+ * @param len   length of data at baton
+ * @param timeout time in seconds for the data to live on the server
+ * @param flags any flags set by the client for this key
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_set(apr_memcache_t *mc,
+                 const char *key,
+                 char *baton,
+                 const apr_uint32_t data_size,
+                 apr_uint32_t timeout,
+                 apr_uint16_t flags);
+
+/**
+ * Adds value by key on the server
+ * @param mc client to use
+ * @param key   null terminated string containing the key
+ * @param baton data to store on the server
+ * @param len   length of data at baton
+ * @param timeout time for the data to live on the server
+ * @param flags any flags set by the client for this key
+ * @return APR_SUCCESS if the key was added, APR_EEXIST if the key 
+ * already exists on the server.
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_add(apr_memcache_t *mc,
+                 const char *key,
+                 char *baton,
+                 const apr_uint32_t data_size,
+                 apr_uint32_t timeout,
+                 apr_uint16_t flags);
+
+/**
+ * Replaces value by key on the server
+ * @param mc client to use
+ * @param key   null terminated string containing the key
+ * @param baton data to store on the server
+ * @param len   length of data at baton
+ * @param timeout time for the data to live on the server
+ * @param flags any flags set by the client for this key
+ * @return APR_SUCCESS if the key was added, APR_EEXIST if the key 
+ * did not exist on the server.
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_replace(apr_memcache_t *mc,
+                 const char *key,
+                 char *data,
+                 const apr_uint32_t data_size,
+                 apr_uint32_t timeout,
+                 apr_uint16_t flags);
+/**
+ * Deletes a key from a server
+ * @param mc client to use
+ * @param key   null terminated string containing the key
+ * @param timeout time for the delete to stop other clients from adding
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_delete(apr_memcache_t *mc,
+                 const char *key,
+                 apr_uint32_t timeout);
+
+/**
+ * Increments a value
+ * @param mc client to use
+ * @param key   null terminated string containing the key
+ * @param n     number to increment by
+ * @param nv    new value after incrmenting
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_incr(apr_memcache_t *mc, 
+                 const char *key,
+                 apr_int32_t n,
+                 apr_uint32_t *nv);
+
+/**
+ * Decrements a value
+ * @param mc client to use
+ * @param key   null terminated string containing the key
+ * @param n     number to decrement by
+ * @param nv    new value after decrementing
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_decr(apr_memcache_t *mc, 
+                 const char *key,
+                 apr_int32_t n,
+                 apr_uint32_t *new_value);
+
+/**
+ * Query a server's version
+ * @param ms    server to query
+ * @param p     Pool to allocate answer from
+ * @param baton location to store server version string
+ * @param len   length of the server version string
+ */
+APR_DECLARE(apr_status_t)
+apr_memcache_version(apr_memcache_server_t *ms,
+                  apr_pool_t *p,
+                  char **baton);
+
+typedef struct
+{
+    /** Version string of this server */
+    const char *version;
+    /** Process id of this server process */
+    apr_uint32_t pid;
+    /** Number of seconds this server has been running */
+    apr_uint32_t uptime;
+    /** current UNIX time according to the server */
+    apr_time_t time;
+    /** Accumulated user time for this process */
+    apr_time_t rusage_user;
+    /** Accumulated system time for this process */
+    apr_time_t rusage_system;
+    /** Current number of items stored by the server */
+    apr_uint32_t curr_items;
+    /** Total number of items stored by this server */
+    apr_uint32_t total_items;
+    /** Current number of bytes used by this server to store items */
+    apr_uint64_t bytes;
+    /** Number of open connections */
+    apr_uint32_t curr_connections;
+    /** Total number of connections opened since the server started running */
+    apr_uint32_t total_connections;
+    /** Number of connection structures allocated by the server */
+    apr_uint32_t connection_structures;
+    /** Cumulative number of retrieval requests */
+    apr_uint32_t cmd_get;
+    /** Cumulative number of storage requests */
+    apr_uint32_t cmd_set;
+    /** Number of keys that have been requested and found present */
+    apr_uint32_t get_hits;
+    /** Number of items that have been requested and not found */
+    apr_uint32_t get_misses;
+    /** Total number of bytes read by this server */
+    apr_uint64_t bytes_read;
+    /** Total number of bytes sent by this server */
+    apr_uint64_t bytes_written;
+    /** Number of bytes this server is allowed to use for storage. */
+    apr_uint32_t limit_maxbytes;
+} apr_memcache_stats_t;
+
+/**
+ * Query a server for statistics
+ * @param ms    server to query
+ * @param p     Pool to allocate answer from
+ * @param stats location of the new statistics structure
+ */
+APR_DECLARE(apr_status_t) 
+apr_memcache_stats(apr_memcache_server_t *ms, 
+                  apr_pool_t *p,
+                  apr_memcache_stats_t **stats);
+
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APR_MEMCACHE_H */

Propchange: apr/apr-util/trunk/include/apr_memcache.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: apr/apr-util/trunk/memcache/apr_memcache.c
URL: http://svn.apache.org/viewcvs/apr/apr-util/trunk/memcache/apr_memcache.c?rev=329089&view=auto
==============================================================================
--- apr/apr-util/trunk/memcache/apr_memcache.c (added)
+++ apr/apr-util/trunk/memcache/apr_memcache.c Thu Oct 27 21:23:01 2005
@@ -0,0 +1,1225 @@
+/* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
+ * applicable.
+ *
+ * Copyright 2004 Paul Querna
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_memcache.h"
+#include "apr_version.h"
+#include <stdlib.h>
+
+#define BUFFER_SIZE 512
+struct apr_memcache_conn_t
+{
+    char *buffer;
+    apr_uint32_t blen;
+    apr_pool_t *p;
+    apr_socket_t *sock;
+    apr_bucket_alloc_t *balloc;
+    apr_bucket_brigade *bb;
+    apr_bucket_brigade *tb;
+    apr_memcache_server_t *ms;
+};                                                          
+
+/* Strings for Client Commands */
+
+#define MC_EOL "\r\n"
+#define MC_EOL_LEN (sizeof(MC_EOL)-1)
+
+#define MC_GET "get "
+#define MC_GET_LEN (sizeof(MC_GET)-1)
+
+#define MC_SET "set "
+#define MC_SET_LEN (sizeof(MC_SET)-1)
+
+#define MC_ADD "add "
+#define MC_ADD_LEN (sizeof(MC_ADD)-1)
+
+#define MC_REPLACE "replace "
+#define MC_REPLACE_LEN (sizeof(MC_REPLACE)-1)
+
+#define MC_DELETE "delete "
+#define MC_DELETE_LEN (sizeof(MC_DELETE)-1)
+
+#define MC_INCR "incr "
+#define MC_INCR_LEN (sizeof(MC_INCR)-1)
+
+#define MC_DECR "decr "
+#define MC_DECR_LEN (sizeof(MC_DECR)-1)
+
+#define MC_VERSION "version"
+#define MC_VERSION_LEN (sizeof(MC_VERSION)-1)
+
+#define MC_STATS "stats"
+#define MC_STATS_LEN (sizeof(MC_STATS)-1)
+
+/* Strings for Server Replies */
+
+#define MS_STORED "STORED"
+#define MS_STORED_LEN (sizeof(MS_STORED)-1)
+
+#define MS_NOT_STORED "NOT_STORED"
+#define MS_NOT_STORED_LEN (sizeof(MS_NOT_STORED)-1)
+
+#define MS_DELETED "DELETED"
+#define MS_DELETED_LEN (sizeof(MS_DELETED)-1)
+
+#define MS_NOT_FOUND "NOT_FOUND"
+#define MS_NOT_FOUND_LEN (sizeof(MS_NOT_FOUND)-1)
+
+#define MS_VALUE "VALUE"
+#define MS_VALUE_LEN (sizeof(MS_VALUE)-1)
+
+#define MS_ERROR "ERROR"
+#define MS_ERROR_LEN (sizeof(MS_ERROR)-1)
+
+#define MS_VERSION "VERSION"
+#define MS_VERSION_LEN (sizeof(MS_VERSION)-1)
+
+#define MS_STAT "STAT"
+#define MS_STAT_LEN (sizeof(MS_STAT)-1)
+
+#define MS_END "END"
+#define MS_END_LEN (sizeof(MS_END)-1)
+
+
+static apr_status_t make_server_dead(apr_memcache_t *mc, apr_memcache_server_t *ms)
+{
+    apr_thread_mutex_lock(ms->lock);
+    ms->status = APR_MC_SERVER_DEAD;
+    ms->btime = apr_time_now();
+    apr_thread_mutex_unlock(ms->lock);
+    return APR_SUCCESS;
+}
+
+static apr_status_t make_server_live(apr_memcache_t *mc, apr_memcache_server_t *ms)
+{
+    ms->status = APR_MC_SERVER_LIVE; 
+    return APR_SUCCESS;
+}
+
+
+APR_DECLARE(apr_status_t) apr_memcache_add_server(apr_memcache_t *mc, apr_memcache_server_t *ms)
+{
+    apr_status_t rv = APR_SUCCESS;
+
+    if(mc->ntotal >= mc->nalloc) {
+        return APR_ENOMEM;
+    }
+
+    mc->live_servers[mc->ntotal] = ms;
+    mc->ntotal++;
+    make_server_live(mc, ms);
+    return rv;
+}
+
+static apr_status_t mc_version_ping(apr_memcache_server_t *ms);
+
+APR_DECLARE(apr_memcache_server_t *) 
+apr_memcache_find_server_hash(apr_memcache_t *mc, const apr_uint32_t hash)
+{
+    apr_memcache_server_t *ms = NULL;
+    apr_uint32_t h = hash;
+    apr_uint32_t i = 0;
+    apr_time_t curtime = 0;
+   
+    if(mc->ntotal == 0) {
+        return NULL;
+    }
+
+    do {
+        ms = mc->live_servers[h % mc->ntotal];
+        if(ms->status == APR_MC_SERVER_LIVE) {
+            break;
+        }
+        else {
+            if (curtime == 0) {
+                curtime = apr_time_now();
+            }
+            apr_thread_mutex_lock(ms->lock);
+            /* Try the the dead server, every 5 seconds */
+            if (curtime - ms->btime >  apr_time_from_sec(5)) {
+                if (mc_version_ping(ms) == APR_SUCCESS) {
+                    ms->btime = curtime;
+                    make_server_live(mc, ms);
+                    apr_thread_mutex_unlock(ms->lock);
+                    break;
+                }
+            }
+            apr_thread_mutex_unlock(ms->lock);
+        }
+        h++;
+        i++;
+    } while(i < mc->ntotal);
+
+    if(i == mc->ntotal) {
+        ms = NULL;
+    }
+
+    return ms;
+}
+
+APR_DECLARE(apr_memcache_server_t *) apr_memcache_find_server(apr_memcache_t *mc, const char *host, apr_port_t port)
+{
+    int i;
+
+    for (i = 0; i < mc->ntotal; i++) {
+        if (strcmp(mc->live_servers[i]->host, host) == 0
+            && mc->live_servers[i]->port == port) {
+
+            return mc->live_servers[i];
+        }
+    }
+
+    return NULL;
+}
+
+static apr_status_t ms_find_conn(apr_memcache_server_t *ms, apr_memcache_conn_t **conn) 
+{
+#if APR_HAS_THREADS
+    return apr_reslist_acquire(ms->conns, (void **)conn);
+#else
+    *conn = ms->conn;
+    return APR_SUCCESS;
+#endif
+}
+
+static apr_status_t ms_bad_conn(apr_memcache_server_t *ms, apr_memcache_conn_t *conn) 
+{
+#if APR_HAS_THREADS
+    return apr_reslist_invalidate(ms->conns, conn);
+#else
+    return APR_SUCCESS;
+#endif
+}
+
+static apr_status_t ms_release_conn(apr_memcache_server_t *ms, apr_memcache_conn_t *conn) 
+{
+#if APR_HAS_THREADS
+    return apr_reslist_release(ms->conns, conn);
+#else
+    return APR_SUCCESS;
+#endif
+}
+
+APR_DECLARE(apr_status_t) apr_memcache_enable_server(apr_memcache_t *mc, apr_memcache_server_t *ms)
+{
+    apr_status_t rv = APR_SUCCESS;
+
+    if (ms->status == APR_MC_SERVER_LIVE) {
+        return rv;
+    }
+
+    rv = make_server_live(mc, ms);
+    return rv;
+}
+
+APR_DECLARE(apr_status_t) apr_memcache_disable_server(apr_memcache_t *mc, apr_memcache_server_t *ms)
+{
+    return make_server_dead(mc, ms);
+}
+
+static apr_status_t conn_connect(apr_memcache_conn_t *conn)
+{
+    apr_status_t rv = APR_SUCCESS;
+    apr_sockaddr_t *sa;
+
+    rv = apr_sockaddr_info_get(&sa, conn->ms->host, APR_INET, conn->ms->port, 0, conn->p);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_socket_connect(conn->sock, sa);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_socket_timeout_set(conn->sock, 1 * APR_USEC_PER_SEC);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    return rv;
+}
+
+
+static apr_status_t
+mc_conn_construct(void **conn_, void *params, apr_pool_t *pool)
+{
+    apr_status_t rv = APR_SUCCESS;
+    apr_memcache_conn_t *conn;
+    apr_bucket* e;
+    apr_pool_t* np;
+    apr_memcache_server_t *ms = params;
+
+    rv = apr_pool_create(&np, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    conn = apr_palloc(np, sizeof( apr_memcache_conn_t ));
+
+    conn->p = np;
+
+    rv = apr_socket_create(&conn->sock, APR_INET, SOCK_STREAM, 0, np);
+
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    conn->balloc = apr_bucket_alloc_create(conn->p);
+    conn->bb = apr_brigade_create(conn->p, conn->balloc);
+    conn->tb = apr_brigade_create(conn->p, conn->balloc);
+    conn->buffer = apr_palloc(conn->p, BUFFER_SIZE);
+    conn->blen = 0;
+    conn->ms = ms;
+
+    e = apr_bucket_socket_create(conn->sock, conn->balloc);
+    APR_BRIGADE_INSERT_TAIL(conn->bb, e);
+
+    rv = conn_connect(conn);
+    *conn_ = conn;
+
+    return rv;
+}
+
+static apr_status_t
+mc_conn_destruct(void *conn_, void *params, apr_pool_t *pool)
+{
+    /* Currently a NOOP */
+    return APR_SUCCESS;
+}
+
+APR_DECLARE(apr_status_t) apr_memcache_server_create(apr_pool_t *p, 
+                                                     const char *host, apr_port_t port, 
+                                                     apr_uint32_t min, apr_uint32_t smax,
+                                                     apr_uint32_t max, apr_uint32_t ttl,
+                                                     apr_memcache_server_t **ms)
+{
+    apr_status_t rv = APR_SUCCESS;
+    apr_memcache_server_t *server;
+    apr_pool_t* np;
+
+    rv = apr_pool_create(&np, p);
+
+    server = apr_palloc(np, sizeof(apr_memcache_server_t));
+
+    server->p = np;
+    server->host = apr_pstrdup(np, host);
+    server->port = port;
+    server->status = APR_MC_SERVER_DEAD;
+#if APR_HAS_THREADS
+    rv = apr_thread_mutex_create(&server->lock, APR_THREAD_MUTEX_DEFAULT, np);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_reslist_create(&server->conns, 
+                               min,                     /* hard minimum */
+                               smax,                    /* soft maximum */
+                               max,                     /* hard maximum */
+                               ttl,                     /* Time to live */
+                               mc_conn_construct,       /* Make a New Connection */
+                               mc_conn_destruct,        /* Kill Old Connection */
+                               server, np);
+#else
+    rv = mc_conn_construct((void**)&(server->conn), server, np);
+#endif
+
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    *ms = server;
+
+    return rv;
+}
+
+APR_DECLARE(apr_status_t) apr_memcache_create(apr_pool_t *p,
+                                              apr_uint16_t max_servers, apr_uint32_t flags,
+                                              apr_memcache_t **memcache) 
+{
+    apr_status_t rv = APR_SUCCESS;
+    apr_memcache_t *mc;
+    
+    mc = apr_palloc(p, sizeof(apr_memcache_t));
+    mc->p = p;
+    mc->nalloc = max_servers;
+    mc->ntotal = 0;
+    mc->live_servers = apr_palloc(p, mc->nalloc * sizeof(struct apr_memcache_server_t *));
+    *memcache = mc;
+    return rv;
+}
+
+
+/* The crc32 functions and data was originally written by Spencer
+ * Garrett <sr...@quick.com> and was gleaned from the PostgreSQL source
+ * tree via the files contrib/ltree/crc32.[ch] and from FreeBSD at
+ * src/usr.bin/cksum/crc32.c.
+ */ 
+
+static const apr_uint32_t crc32tab[256] = {
+  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+  0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+  0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+  0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+  0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+  0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+  0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+  0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+  0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+  0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+  0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+  0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+  0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+  0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+  0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+  0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+  0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+  0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+  0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+  0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+  0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+  0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+  0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+  0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+  0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+  0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+  0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+  0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+  0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+  0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+  0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+  0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+  0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+  0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+  0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+  0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+  0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+  0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+  0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+  0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+  0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+  0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+  0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+  0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+  0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+  0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+  0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+  0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+  0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+  0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+  0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+  0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+  0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+  0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+  0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+  0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+  0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+  0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+  0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+  0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+  0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+  0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+};
+
+APR_DECLARE(apr_uint32_t) apr_memcache_hash(const char *data, const apr_uint32_t data_len)
+{
+    apr_uint32_t i;
+    apr_uint32_t crc;
+    crc = ~0;
+
+    for (i = 0; i < data_len; i++)
+        crc = (crc >> 8) ^ crc32tab[(crc ^ (data[i])) & 0xff];
+  
+    return ((~crc >> 16) & 0x7fff);
+}
+
+static apr_status_t get_server_line(apr_memcache_conn_t *conn)
+{
+    apr_size_t bsize = BUFFER_SIZE;
+    apr_status_t rv = APR_SUCCESS;
+
+    rv = apr_brigade_split_line(conn->tb, conn->bb, APR_BLOCK_READ, BUFFER_SIZE);
+
+    if(rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_brigade_flatten(conn->tb, conn->buffer, &bsize);
+
+    if(rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    conn->blen = bsize;
+    conn->buffer[bsize] = '\0';
+
+    apr_brigade_cleanup(conn->tb);
+
+    if(rv != APR_SUCCESS) {
+        return rv;
+    }
+    return rv;
+}
+
+static apr_status_t storage_cmd_write(apr_memcache_t *mc,
+                                      char *cmd,
+                                      const apr_uint32_t cmd_size,
+                                      const char *key,
+                                      char *data,
+                                      const apr_uint32_t data_size,
+                                      apr_uint32_t timeout,
+                                      apr_uint16_t flags)
+{
+    apr_uint32_t hash;
+    apr_memcache_server_t *ms;
+    apr_memcache_conn_t *conn;
+    apr_status_t rv;
+    apr_size_t written;
+    struct iovec vec[5];
+    int klen;
+
+    int key_size = strlen(key);
+
+    hash = apr_memcache_hash(key, key_size);
+
+    ms = apr_memcache_find_server_hash(mc, hash);
+
+    if (ms == NULL)
+        return APR_NOTFOUND;
+
+    rv = ms_find_conn(ms, &conn);
+
+    if (rv != APR_SUCCESS) {
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    /* <command name> <key> <flags> <exptime> <bytes>\r\n<data>\r\n */
+
+    vec[0].iov_base = cmd;
+    vec[0].iov_len  = cmd_size;
+
+    vec[1].iov_base = (void*)key;
+    vec[1].iov_len  = key_size;
+
+    klen = snprintf(conn->buffer, BUFFER_SIZE, " %u %u %u" MC_EOL, flags, timeout, data_size);
+
+    vec[2].iov_base = conn->buffer;
+    vec[2].iov_len  = klen;
+
+    vec[3].iov_base = data;
+    vec[3].iov_len  = data_size;
+
+    vec[4].iov_base = MC_EOL;
+    vec[4].iov_len  = MC_EOL_LEN;
+
+    rv = apr_socket_sendv(conn->sock, vec, 5, &written);
+
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    rv = get_server_line(conn);
+
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    if (strcmp(conn->buffer, MS_STORED MC_EOL) == 0) {
+        rv = APR_SUCCESS;
+    }
+    else if (strcmp(conn->buffer, MS_NOT_STORED MC_EOL) == 0) {
+        rv = APR_EEXIST;
+    }
+    else {
+        rv = APR_EGENERAL;
+    }
+
+    ms_release_conn(ms, conn);
+
+    return rv;
+}
+
+APR_DECLARE(apr_status_t)
+apr_memcache_set(apr_memcache_t *mc,
+                 const char *key,
+                 char *data,
+                 const apr_uint32_t data_size,
+                 apr_uint32_t timeout,
+                 apr_uint16_t flags)
+{
+    return storage_cmd_write(mc,
+                           MC_SET, MC_SET_LEN,
+                           key,
+                           data, data_size,
+                           timeout, flags);
+}
+
+APR_DECLARE(apr_status_t)
+apr_memcache_add(apr_memcache_t *mc,
+                 const char *key,
+                 char *data,
+                 const apr_uint32_t data_size,
+                 apr_uint32_t timeout,
+                 apr_uint16_t flags)
+{
+    return storage_cmd_write(mc, 
+                           MC_ADD, MC_ADD_LEN,
+                           key,
+                           data, data_size,
+                           timeout, flags);
+}
+
+APR_DECLARE(apr_status_t)
+apr_memcache_replace(apr_memcache_t *mc,
+                 const char *key,
+                 char *data,
+                 const apr_uint32_t data_size,
+                 apr_uint32_t timeout,
+                 apr_uint16_t flags)
+{
+    return storage_cmd_write(mc,
+                           MC_REPLACE, MC_REPLACE_LEN,
+                           key,
+                           data, data_size,
+                           timeout, flags);
+
+}
+
+APR_DECLARE(apr_status_t)
+apr_memcache_getp(apr_memcache_t *mc,
+                  apr_pool_t *p,
+                  const char *key,
+                  char **baton,
+                  apr_size_t *new_length,
+                  apr_uint16_t *flags_)
+{
+    apr_status_t rv;
+    apr_memcache_server_t *ms;
+    apr_memcache_conn_t *conn;
+    apr_uint32_t hash;
+    apr_size_t written;
+    int klen = strlen(key);
+    struct iovec vec[3];
+
+    hash = apr_memcache_hash(key, klen);
+    ms = apr_memcache_find_server_hash(mc, hash);
+    if (ms == NULL)
+        return APR_NOTFOUND;
+    
+    rv = ms_find_conn(ms, &conn);
+
+    if (rv != APR_SUCCESS) {
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    /* get <key>[ <key>[...]]\r\n */
+    vec[0].iov_base = MC_GET;
+    vec[0].iov_len  = MC_GET_LEN;
+
+    vec[1].iov_base = (void*)key;
+    vec[1].iov_len  = klen;
+
+    vec[2].iov_base = MC_EOL;
+    vec[2].iov_len  = MC_EOL_LEN;
+
+    rv = apr_socket_sendv(conn->sock, vec, 3, &written);
+
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    rv = get_server_line(conn);
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    if (strncmp(MS_VALUE, conn->buffer, MS_VALUE_LEN) == 0) {
+        char *flags;
+        char *length;
+        char *start;
+        char *last;
+        apr_size_t len;
+
+        start = conn->buffer;
+        flags = apr_strtok(conn->buffer," ",&last);
+        flags = apr_strtok(NULL," ",&last);
+        flags = apr_strtok(NULL," ",&last);
+
+        if (flags_)
+            *flags_ = atoi(flags);
+
+        length = apr_strtok(NULL," ",&last);
+        len = atoi(length);
+        if (len < 0)  {
+            *new_length = 0;
+            *baton = NULL;
+        }
+        else {
+            apr_bucket_brigade *bbb;
+            apr_bucket *e;
+
+            /* eat the trailing \r\n */
+            rv = apr_brigade_partition(conn->bb, len+2, &e);
+
+            if (rv != APR_SUCCESS) {
+                ms_bad_conn(ms, conn);
+                apr_memcache_disable_server(mc, ms);
+                return rv;
+            }
+            
+            bbb = apr_brigade_split(conn->bb, e);
+
+            rv = apr_brigade_pflatten(conn->bb, baton, &len, p);
+
+            if (rv != APR_SUCCESS) {
+                ms_bad_conn(ms, conn);
+                apr_memcache_disable_server(mc, ms);
+                return rv;
+            }
+
+            rv = apr_brigade_destroy(conn->bb);
+            if (rv != APR_SUCCESS) {
+                ms_bad_conn(ms, conn);
+                apr_memcache_disable_server(mc, ms);
+                return rv;
+            }
+
+            conn->bb = bbb;
+
+            *new_length = len - 2;
+            (*baton)[*new_length] = '\0';
+        }
+        
+        rv = get_server_line(conn);
+        if (rv != APR_SUCCESS) {
+            ms_bad_conn(ms, conn);
+            apr_memcache_disable_server(mc, ms);
+            return rv;
+        }
+
+        if (strncmp(MS_END, conn->buffer, MS_END_LEN) != 0) {
+            rv = APR_EGENERAL;
+        }
+    }
+    else if (strncmp(MS_END, conn->buffer, MS_END_LEN) == 0) {
+        rv = APR_NOTFOUND;
+    }
+    else {
+        rv = APR_EGENERAL;
+    }
+
+    ms_release_conn(ms, conn);
+
+    return rv;
+}
+
+APR_DECLARE(apr_status_t)
+apr_memcache_delete(apr_memcache_t *mc,
+                    const char *key,
+                    apr_uint32_t timeout)
+{
+    apr_status_t rv;
+    apr_memcache_server_t* ms;
+    apr_memcache_conn_t* conn;
+    apr_uint32_t hash;
+    apr_size_t written;
+    struct iovec vec[3];
+    int klen = strlen(key);
+
+    hash = apr_memcache_hash(key, klen);
+    ms = apr_memcache_find_server_hash(mc, hash);
+    if (ms == NULL)
+        return APR_NOTFOUND;
+    
+    rv = ms_find_conn(ms, &conn);
+
+    if (rv != APR_SUCCESS) {
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    /* delete <key> <time>\r\n */
+    vec[0].iov_base = MC_DELETE;
+    vec[0].iov_len  = MC_DELETE_LEN;
+
+    vec[1].iov_base = (void*)key;
+    vec[1].iov_len  = klen;
+
+    klen = snprintf(conn->buffer, BUFFER_SIZE, " %u" MC_EOL, timeout);
+
+    vec[2].iov_base = conn->buffer;
+    vec[2].iov_len  = klen;
+
+    rv = apr_socket_sendv(conn->sock, vec, 3, &written);
+
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    rv = get_server_line(conn);
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    if (strncmp(MS_DELETED, conn->buffer, MS_DELETED_LEN) == 0) {
+        rv = APR_SUCCESS;
+    }
+    else if (strncmp(MS_NOT_FOUND, conn->buffer, MS_NOT_FOUND_LEN) == 0) {
+        rv = APR_NOTFOUND;
+    }
+    else {
+        rv = APR_EGENERAL;
+    }
+
+    ms_release_conn(ms, conn);
+
+    return rv;
+}
+
+static apr_status_t num_cmd_write(apr_memcache_t *mc,
+                                      char *cmd,
+                                      const apr_uint32_t cmd_size,
+                                      const char *key,
+                                      const apr_int32_t inc,
+                                      apr_uint32_t *new_value)
+{
+    apr_status_t rv;
+    apr_memcache_server_t *ms;
+    apr_memcache_conn_t *conn;
+    apr_uint32_t hash;
+    apr_size_t written;
+    struct iovec vec[3];
+    int klen = strlen(key);
+
+    hash = apr_memcache_hash(key, klen);
+    ms = apr_memcache_find_server_hash(mc, hash);
+    if (ms == NULL)
+        return APR_NOTFOUND;
+    
+    rv = ms_find_conn(ms, &conn);
+
+    if (rv != APR_SUCCESS) {
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    /* <cmd> <key> <value>\r\n */
+    vec[0].iov_base = cmd;
+    vec[0].iov_len  = cmd_size;
+
+    vec[1].iov_base = (void*)key;
+    vec[1].iov_len  = klen;
+
+    klen = snprintf(conn->buffer, BUFFER_SIZE, " %u" MC_EOL, inc);
+
+    vec[2].iov_base = conn->buffer;
+    vec[2].iov_len  = klen;
+
+    rv = apr_socket_sendv(conn->sock, vec, 3, &written);
+
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    rv = get_server_line(conn);
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        apr_memcache_disable_server(mc, ms);
+        return rv;
+    }
+
+    if (strncmp(MS_ERROR, conn->buffer, MS_ERROR_LEN) == 0) {
+        rv = APR_EGENERAL;
+    }
+    else if (strncmp(MS_NOT_FOUND, conn->buffer, MS_NOT_FOUND_LEN) == 0) {
+        rv = APR_NOTFOUND;
+    }
+    else {
+        if(new_value)
+            *new_value = atoi(conn->buffer);
+        rv = APR_SUCCESS;
+    }
+
+    ms_release_conn(ms, conn);
+
+    return rv;
+}
+
+APR_DECLARE(apr_status_t)
+apr_memcache_incr(apr_memcache_t *mc,
+                    const char* key,
+                    apr_int32_t inc,
+                    apr_uint32_t *new_value)
+{
+    return num_cmd_write(mc,
+                         MC_INCR,
+                         MC_INCR_LEN,
+                         key,
+                         inc, 
+                         new_value);
+}
+
+
+APR_DECLARE(apr_status_t)
+apr_memcache_decr(apr_memcache_t *mc,
+                    const char *key,
+                    apr_int32_t inc,
+                    apr_uint32_t *new_value)
+{
+    return num_cmd_write(mc,
+                         MC_DECR,
+                         MC_DECR_LEN,
+                         key,
+                         inc, 
+                         new_value);
+}
+
+
+
+APR_DECLARE(apr_status_t)
+apr_memcache_version(apr_memcache_server_t *ms,
+                  apr_pool_t *p,
+                  char **baton)
+{
+    apr_status_t rv;
+    apr_memcache_conn_t *conn;
+    apr_size_t written;
+    struct iovec vec[2];
+
+    rv = ms_find_conn(ms, &conn);
+
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    /* version\r\n */
+    vec[0].iov_base = MC_VERSION;
+    vec[0].iov_len  = MC_VERSION_LEN;
+
+    vec[1].iov_base = MC_EOL;
+    vec[1].iov_len  = MC_EOL_LEN;
+
+    rv = apr_socket_sendv(conn->sock, vec, 2, &written);
+
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        return rv;
+    }
+
+    rv = get_server_line(conn);
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        return rv;
+    }
+
+    if (strncmp(MS_VERSION, conn->buffer, MS_VERSION_LEN) == 0) {
+        *baton = apr_pstrmemdup(p, conn->buffer+MS_VERSION_LEN+1, 
+                                conn->blen - MS_VERSION_LEN - 2);
+        rv = APR_SUCCESS;
+    }
+    else {
+        rv = APR_EGENERAL;
+    }
+
+    ms_release_conn(ms, conn);
+
+    return rv;
+}
+
+apr_status_t mc_version_ping(apr_memcache_server_t *ms)
+{
+    apr_status_t rv;
+    apr_size_t written;
+    struct iovec vec[2];
+    apr_memcache_conn_t *conn;
+
+    rv = ms_find_conn(ms, &conn);
+
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    /* version\r\n */
+    vec[0].iov_base = MC_VERSION;
+    vec[0].iov_len  = MC_VERSION_LEN;
+
+    vec[1].iov_base = MC_EOL;
+    vec[1].iov_len  = MC_EOL_LEN;
+
+    rv = apr_socket_sendv(conn->sock, vec, 2, &written);
+
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        return rv;
+    }
+
+    rv = get_server_line(conn);
+    ms_release_conn(ms, conn);
+    return rv;
+}
+
+
+/**
+ * Define all of the strings for stats
+ */
+
+#define STAT_pid MS_STAT " pid "
+#define STAT_pid_LEN (sizeof(STAT_pid)-1)
+
+#define STAT_uptime MS_STAT " uptime "
+#define STAT_uptime_LEN (sizeof(STAT_uptime)-1)
+
+#define STAT_time MS_STAT " time "
+#define STAT_time_LEN (sizeof(STAT_time)-1)
+
+#define STAT_version MS_STAT " version "
+#define STAT_version_LEN (sizeof(STAT_version)-1)
+
+#define STAT_rusage_user MS_STAT " rusage_user "
+#define STAT_rusage_user_LEN (sizeof(STAT_rusage_user)-1)
+
+#define STAT_rusage_system MS_STAT " rusage_system "
+#define STAT_rusage_system_LEN (sizeof(STAT_rusage_system)-1)
+
+#define STAT_curr_items MS_STAT " curr_items "
+#define STAT_curr_items_LEN (sizeof(STAT_curr_items)-1)
+
+#define STAT_total_items MS_STAT " total_items "
+#define STAT_total_items_LEN (sizeof(STAT_total_items)-1)
+
+#define STAT_bytes MS_STAT " bytes "
+#define STAT_bytes_LEN (sizeof(STAT_bytes)-1)
+
+#define STAT_curr_connections MS_STAT " curr_connections "
+#define STAT_curr_connections_LEN (sizeof(STAT_curr_connections)-1)
+
+#define STAT_total_connections MS_STAT " total_connections "
+#define STAT_total_connections_LEN (sizeof(STAT_total_connections)-1)
+
+#define STAT_connection_structures MS_STAT " connection_structures "
+#define STAT_connection_structures_LEN (sizeof(STAT_connection_structures)-1)
+
+#define STAT_cmd_get MS_STAT " cmd_get "
+#define STAT_cmd_get_LEN (sizeof(STAT_cmd_get)-1)
+
+#define STAT_cmd_set MS_STAT " cmd_set "
+#define STAT_cmd_set_LEN (sizeof(STAT_cmd_set)-1)
+
+#define STAT_get_hits MS_STAT " get_hits "
+#define STAT_get_hits_LEN (sizeof(STAT_get_hits)-1)
+
+#define STAT_get_misses MS_STAT " get_misses "
+#define STAT_get_misses_LEN (sizeof(STAT_get_misses)-1)
+
+#define STAT_bytes_read MS_STAT " bytes_read "
+#define STAT_bytes_read_LEN (sizeof(STAT_bytes_read)-1)
+
+#define STAT_bytes_written MS_STAT " bytes_written "
+#define STAT_bytes_written_LEN (sizeof(STAT_bytes_written)-1)
+
+#define STAT_limit_maxbytes MS_STAT " limit_maxbytes "
+#define STAT_limit_maxbytes_LEN (sizeof(STAT_limit_maxbytes)-1)
+
+
+static const char* stat_read_string(apr_pool_t *p, char *buf, int len)
+{
+    /* remove trailing \r\n and null char */
+    return apr_pstrmemdup(p, buf, len-2);
+}
+
+static apr_uint32_t stat_read_uint32(apr_pool_t *p, char *buf, int len)
+{
+    buf[len-2] = '\0';
+    return atoi(buf);
+}
+
+static apr_uint64_t stat_read_uint64(apr_pool_t *p, char *buf, int len)
+{
+    buf[len-2] = '\0';
+    return apr_atoi64(buf);
+}
+
+static apr_time_t stat_read_time(apr_pool_t *p, char *buf, int len)
+{
+    buf[len-2] = '\0';
+    return apr_time_from_sec(atoi(buf));
+}
+
+static apr_time_t stat_read_rtime(apr_pool_t *p, char *buf, int len)
+{
+    char* tok;
+    char* secs;
+    char* usecs;
+    const char* sep = ":";
+
+    buf[len-2] = '\0';
+
+    secs = apr_strtok(buf, sep, &tok);
+    if (secs == NULL) {
+        sep = ".";
+        secs = apr_strtok(buf, sep, &tok);
+    }
+    usecs = apr_strtok(NULL, sep, &tok);
+    if (secs && usecs) {
+        return apr_time_make(atoi(secs), atoi(usecs));
+    }
+    else {
+        return apr_time_make(0, 0);
+    }
+}
+
+/**
+ * I got tired of Typing. Meh. 
+ *
+ * TODO: Convert it to static tables to make it cooler.
+ */
+
+#define mc_stat_cmp(name) \
+    strncmp(STAT_ ## name, conn->buffer, STAT_ ## name ## _LEN) == 0
+
+#define mc_stat_str(name) \
+    stat_read_string(p, conn->buffer + name, \
+                     conn->blen - name)
+
+#define mc_stat_uint32(name) \
+    stat_read_uint32(p, conn->buffer + name, \
+                     conn->blen - name)
+
+#define mc_stat_uint64(name) \
+    stat_read_uint64(p, conn->buffer + name, \
+                     conn->blen - name)
+
+#define mc_stat_time(name) \
+    stat_read_time(p, conn->buffer + name, \
+                     conn->blen - name)
+
+#define mc_stat_rtime(name) \
+    stat_read_rtime(p, conn->buffer + name, \
+                     conn->blen - name)
+
+
+#define mc_do_stat(name, type) \
+    if (mc_stat_cmp(name)) { \
+        stats-> name = mc_stat_ ## type ((STAT_ ## name ## _LEN)); \
+    } 
+
+static void update_stats(apr_pool_t *p, apr_memcache_conn_t *conn, 
+                         apr_memcache_stats_t *stats)
+{
+
+    mc_do_stat(version, str)
+    else mc_do_stat(pid, uint32)
+    else mc_do_stat(uptime, uint32)
+    else mc_do_stat(time, time)
+    else mc_do_stat(rusage_user, rtime)
+    else mc_do_stat(rusage_system, rtime)
+    else mc_do_stat(curr_items, uint32)
+    else mc_do_stat(total_items, uint32)
+    else mc_do_stat(bytes, uint64)
+    else mc_do_stat(curr_connections, uint32)
+    else mc_do_stat(total_connections, uint32)
+    else mc_do_stat(connection_structures, uint32)
+    else mc_do_stat(cmd_get, uint32)
+    else mc_do_stat(cmd_set, uint32)
+    else mc_do_stat(get_hits, uint32)
+    else mc_do_stat(get_misses, uint32)
+    else mc_do_stat(bytes_read, uint64)
+    else mc_do_stat(bytes_written, uint64)
+    else mc_do_stat(limit_maxbytes, uint32)
+}
+
+APR_DECLARE(apr_status_t)
+apr_memcache_stats(apr_memcache_server_t *ms,
+                  apr_pool_t *p,
+                  apr_memcache_stats_t **stats) 
+{
+    apr_memcache_stats_t *ret;
+    apr_status_t rv;
+    apr_memcache_conn_t *conn;
+    apr_size_t written;
+    struct iovec vec[2];
+
+    rv = ms_find_conn(ms, &conn);
+
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    /* version\r\n */
+    vec[0].iov_base = MC_STATS;
+    vec[0].iov_len  = MC_STATS_LEN;
+
+    vec[1].iov_base = MC_EOL;
+    vec[1].iov_len  = MC_EOL_LEN;
+
+    rv = apr_socket_sendv(conn->sock, vec, 2, &written);
+
+    if (rv != APR_SUCCESS) {
+        ms_bad_conn(ms, conn);
+        return rv;
+    }
+
+    ret = apr_pcalloc(p, sizeof(apr_memcache_stats_t));
+
+    do {
+        rv = get_server_line(conn);
+        if (rv != APR_SUCCESS) {
+            ms_bad_conn(ms, conn);
+            return rv;
+        }
+
+        if (strncmp(MS_END, conn->buffer, MS_END_LEN) == 0) {
+            rv = APR_SUCCESS;
+            break;
+        }
+        else if (strncmp(MS_STAT, conn->buffer, MS_STAT_LEN) == 0) {
+            update_stats(p, conn, ret);
+            continue;
+        }
+        else {
+            rv = APR_EGENERAL;
+            break;
+        }
+
+    } while(1);
+
+    ms_release_conn(ms, conn);
+
+    if(stats)
+        *stats = ret;
+
+    return rv;
+}
+

Propchange: apr/apr-util/trunk/memcache/apr_memcache.c
------------------------------------------------------------------------------
    svn:eol-style = native



Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by "William A. Rowe, Jr." <wr...@rowe-clan.net>.
Paul Querna wrote:
> William A. Rowe, Jr. wrote:
> 
>>pquerna@apache.org wrote:
>>
>>>Author: pquerna
>>>Date: Thu Oct 27 21:23:01 2005
>>>New Revision: 329089
>>>
>>>URL: http://svn.apache.org/viewcvs?rev=329089&view=rev
>>>Log:
>>>Import apr_memcache trunk to APR-Util.
>>>
>>>Still TODO:
>>>- Move CRC32 to a separate public function/API.
>>>- Create a multi-get interface.
>>>- Make locking and connection pooling optional.
>>
>>For the moment, -1, please revert and begin an apr-memcache sandbox
>>because...
>>
>> * it makes trunk/ unstable, preventing us from moving to 1.3
> 
> Then branch 1.3 and revert in the branch.  Trunk should always be safe
> to commit to, IMHO.

Is apr_memcache.h your final answer?  None of those API's will change?  I read
your TODO as a list of items that might change.  You can't do that on trunk,
only the 'final' API can be committed to trunk; we are stuck with it till 2.x.

svn is lovely, we can create all the sandboxes we need to put together 'final'
solutions that are ready to merge for the next minor bump.

>> * this seems entirely too specialized (in other words, useless code bloat)
>>   All of the concepts here -are- useful, but not in this aggregation.
> 
> I disagree.  I think many others disagree.  We voted on this 4 days ago,
> everyone who voted approved. It would of been helpful to see your
> opinion then.  I understand we are all busy, but this feels like going
> backwards in the discussion.  Its not bad, it just would of been nice to
> raise these concerns in the vote thread.

Sure, so next time leave the vote for major changes open more than four
days.  I counted four contributor votes in four days, that isn't exactly
a resounding concensus for a major scope change to an ASF project.

Give me the weekend to codify my specific objections and raise some
possible solutions.

>> * if we trust that this code is incomplete (my objections and your TODO)
>>   then we have to partition it off for a while, till it's fleshed out.
>>   With ABI/API stability a prime concern, dropping in an interface that
>>   the group will suggest improvements and code style corrections for, is
>>   a bad thing in a live 1.x release at this stage of it's development.
> 
> The code is complete. There is no API instability. It has been stable as
> a separate project for nearly a full year.
> 
> My TODO that I mentioned in the commit log is to ADD features to the
> API. The existing API would not be changed in any way.

Ack, I misunderstood this.

I still look at this as feature creep, an indirect endorsement of some edge
solution that's not based on any std/rfc/ietf/iata/w3c or other standards
body.  It's got a very enthusiastic but narrow audience as it appears to
be a very good solution for specialized (thus far) use cases.

If we can offer an API which provides general utility and multiple solutions,
as opposed to locking users into a very specialized solution, then I'm likely
to endorse it.  Obviously for ssl session caching and dozens of other examples
in httpd, we aren't doing things well, and this merits improvement.

As I said, I need free cycles, something I don't have most workdays, before
I can really spell out my objections.  But this commit definately went in the
opposite direction raised over the last week, that many users already find
apr-util hopelessly twisted into a compile-to-the-target-machine mess, and
are begging us to deliver the truly utilitarian parts of apr-util independently
of all the rest of the cruft.

Bill

Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by Ian Holsman <li...@holsman.net>.
William A. Rowe, Jr. wrote:
> Paul Querna wrote:
>> Joe Orton wrote:
>>
>>> - apr_memcache_hash's data_len argument, apr_memcache_conn_t.blen , 
>>> storage_cmd_write's *_size  arguments are all apr_uint32_t and look 
>>> like they should be apr_size_t, any reason why that is?
>>
>> Ah, now I remember why.  The memcached protocol states that these data
>> lengths are all unsigned 32bit ints.  So, they can never hold values
>> larger, or the server will not understand.
> 
> So, we violate a basic apr design principal for an oddball service that we
> are seeking to support?  Or return APR_EINVAL when someone tries to set
> aside 10GB?  (This is a rather funny quibble when you consider how horrid
> using an external socket-cached data store to set aside 10GB for 'quick
> access' later on ;-)

first, can you stop with the 'oddball' comments. If you don't think 
memcached is useful, fine. but for others it serves a need which isn't 
there in the current apr/util family, and that is a distributed/shared 
cache.
memcached has been around for years, has a large user base, and plays a 
part in several large corporations, while you might not find this 
useful, others do.

as for the parameter issue, from what I remember the general APR ethos 
is garbage in garbage out.
We don't do parameter checking, in most of the apis we have, from what I 
recall. and making it a different field size will cause surprise for 
users when they try to cache 2^32+1 bytes and it fails. better to tell 
the user this is exactly how much you can do straight off and let the 
compiler issue the warning instead of doing it at runtime.

It also sets the perception that this is for caching *smallish* things, 
not 700M ISOs.

> 
> If apr-util is designed to be a pluggable abstraction layer, and we all
> concur that
> 
>   char *buf;
>   apr_size_t *bufsize;
>   apr_dosomething(*buf, &bufsize);
> 
> is our normalized style, obviously this patch was correct, irrespective
> of whatever this particular backend cache supports.
> 
> FYI - why apr_memcache?  Unless it's either an in-memory cache, or some
> cache virtualized by page-fault retrieval, this is definately misnamed,
> or beholden to one oddball external project.
> 
apr_memcache is a interface to a single-process memory cache.
It also has the advanced feature of segregating by the key value to a 
set of single process memory caches (cachee?), which can be distributed 
across a set of machines. (and provide a common cache across a entire 
server farm)

so the 'mem' stands for memory as it doesn't use disk (similar to 
mod_mem_cache and mod_disk_cache)

and the 'cache' stands for non-persistent, as there is no guarantee that 
the data you put in there 5 seconds ago will still be there the next 
time you ask.

we could call it apr_dist_mem_cache if you prefer ?

> Bill
> 


Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by Colm MacCarthaigh <co...@stdlib.net>.
On Fri, Oct 28, 2005 at 04:34:28PM -0500, William A. Rowe, Jr. wrote:
> So, we violate a basic apr design principal for an oddball service that we
> are seeking to support?  Or return APR_EINVAL when someone tries to set
> aside 10GB?  (This is a rather funny quibble when you consider how horrid
> using an external socket-cached data store to set aside 10GB for 'quick
> access' later on ;-)

.. or we split it into 2GB chunks with a small header and send them off, 
and do the converse on retrieval.

 memcache is a memory cache, and there are already platforms on which
more than 2GB (or even less) can't be stored contigously in memory. So
this seems like a portability problem the application will already have
hit. Moving around 2Gb linearly addressed chunks of memory is already
non-portable, no?

> FYI - why apr_memcache?  Unless it's either an in-memory cache, or some
> cache virtualized by page-fault retrieval, this is definately misnamed,
> or beholden to one oddball external project.

memcache is the name of the service:

	http://www.danga.com/memcached/

-- 
Colm MacCárthaigh                        Public Key: colm+pgp@stdlib.net

Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by "William A. Rowe, Jr." <wr...@rowe-clan.net>.
Paul Querna wrote:
> Joe Orton wrote:
> 
>>- apr_memcache_hash's data_len argument, apr_memcache_conn_t.blen , 
>>storage_cmd_write's *_size  arguments are all apr_uint32_t and look like 
>>they should be apr_size_t, any reason why that is?
> 
> Ah, now I remember why.  The memcached protocol states that these data
> lengths are all unsigned 32bit ints.  So, they can never hold values
> larger, or the server will not understand.

So, we violate a basic apr design principal for an oddball service that we
are seeking to support?  Or return APR_EINVAL when someone tries to set
aside 10GB?  (This is a rather funny quibble when you consider how horrid
using an external socket-cached data store to set aside 10GB for 'quick
access' later on ;-)

If apr-util is designed to be a pluggable abstraction layer, and we all
concur that

   char *buf;
   apr_size_t *bufsize;
   apr_dosomething(*buf, &bufsize);

is our normalized style, obviously this patch was correct, irrespective
of whatever this particular backend cache supports.

FYI - why apr_memcache?  Unless it's either an in-memory cache, or some
cache virtualized by page-fault retrieval, this is definately misnamed,
or beholden to one oddball external project.

Bill

Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by Paul Querna <ch...@force-elite.com>.
Joe Orton wrote:
> On Thu, Oct 27, 2005 at 11:22:42PM -0700, Paul Querna wrote:
>> William A. Rowe, Jr. wrote:
>>> pquerna@apache.org wrote:
>>>> Author: pquerna
>>>> Date: Thu Oct 27 21:23:01 2005
>>>> New Revision: 329089
>>>>
>>>> URL: http://svn.apache.org/viewcvs?rev=329089&view=rev
>>>> Log:
>>>> Import apr_memcache trunk to APR-Util.
>>>>
>>>> Still TODO:
>>>> - Move CRC32 to a separate public function/API.
>>>> - Create a multi-get interface.
>>>> - Make locking and connection pooling optional.
>>> For the moment, -1, please revert and begin an apr-memcache sandbox
>>> because...
>>>
>>>  * it makes trunk/ unstable, preventing us from moving to 1.3
>> Then branch 1.3 and revert in the branch.  Trunk should always be safe
>> to commit to, IMHO.
> 
> Agreed.  Some review:
> 
> - apr_memcache_hash's data_len argument, apr_memcache_conn_t.blen , 
> storage_cmd_write's *_size  arguments are all apr_uint32_t and look like 
> they should be apr_size_t, any reason why that is?

Ah, now I remember why.  The memcached protocol states that these data
lengths are all unsigned 32bit ints.  So, they can never hold values
larger, or the server will not understand.

-Paul

Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by Joe Orton <jo...@redhat.com>.
On Thu, Oct 27, 2005 at 11:22:42PM -0700, Paul Querna wrote:
> William A. Rowe, Jr. wrote:
> > pquerna@apache.org wrote:
> >> Author: pquerna
> >> Date: Thu Oct 27 21:23:01 2005
> >> New Revision: 329089
> >>
> >> URL: http://svn.apache.org/viewcvs?rev=329089&view=rev
> >> Log:
> >> Import apr_memcache trunk to APR-Util.
> >>
> >> Still TODO:
> >> - Move CRC32 to a separate public function/API.
> >> - Create a multi-get interface.
> >> - Make locking and connection pooling optional.
> > 
> > For the moment, -1, please revert and begin an apr-memcache sandbox
> > because...
> > 
> >  * it makes trunk/ unstable, preventing us from moving to 1.3
> 
> Then branch 1.3 and revert in the branch.  Trunk should always be safe
> to commit to, IMHO.

Agreed.  Some review:

- apr_memcache_hash's data_len argument, apr_memcache_conn_t.blen , 
storage_cmd_write's *_size  arguments are all apr_uint32_t and look like 
they should be apr_size_t, any reason why that is?

similarly "int key_size = strlen(key);" -> apr_size_t

- code style nits, "if(" -> "if (", lots of indenting weirdness, some 
"char* foo"

- use apr_snprintf not snprintf

- I suppose apr_brigade_split() use should be "considered harmful" due 
to memory consumption ;)

joe

Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by Paul Querna <ch...@force-elite.com>.
William A. Rowe, Jr. wrote:
> pquerna@apache.org wrote:
>> Author: pquerna
>> Date: Thu Oct 27 21:23:01 2005
>> New Revision: 329089
>>
>> URL: http://svn.apache.org/viewcvs?rev=329089&view=rev
>> Log:
>> Import apr_memcache trunk to APR-Util.
>>
>> Still TODO:
>> - Move CRC32 to a separate public function/API.
>> - Create a multi-get interface.
>> - Make locking and connection pooling optional.
> 
> For the moment, -1, please revert and begin an apr-memcache sandbox
> because...
> 
>  * it makes trunk/ unstable, preventing us from moving to 1.3

Then branch 1.3 and revert in the branch.  Trunk should always be safe
to commit to, IMHO.

>  * this seems entirely too specialized (in other words, useless code bloat)
>    All of the concepts here -are- useful, but not in this aggregation.

I disagree.  I think many others disagree.  We voted on this 4 days ago,
everyone who voted approved. It would of been helpful to see your
opinion then.  I understand we are all busy, but this feels like going
backwards in the discussion.  Its not bad, it just would of been nice to
raise these concerns in the vote thread.

>  * if we trust that this code is incomplete (my objections and your TODO)
>    then we have to partition it off for a while, till it's fleshed out.
>    With ABI/API stability a prime concern, dropping in an interface that
>    the group will suggest improvements and code style corrections for, is
>    a bad thing in a live 1.x release at this stage of it's development.

The code is complete. There is no API instability. It has been stable as
a separate project for nearly a full year.

My TODO that I mentioned in the commit log is to ADD features to the
API. The existing API would not be changed in any way.

-Paul

Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by "William A. Rowe, Jr." <wr...@rowe-clan.net>.
pquerna@apache.org wrote:
> Author: pquerna
> Date: Thu Oct 27 21:23:01 2005
> New Revision: 329089
> 
> URL: http://svn.apache.org/viewcvs?rev=329089&view=rev
> Log:
> Import apr_memcache trunk to APR-Util.
> 
> Still TODO:
> - Move CRC32 to a separate public function/API.
> - Create a multi-get interface.
> - Make locking and connection pooling optional.

For the moment, -1, please revert and begin an apr-memcache sandbox because...

  * it makes trunk/ unstable, preventing us from moving to 1.3

  * this seems entirely too specialized (in other words, useless code bloat)
    All of the concepts here -are- useful, but not in this aggregation.

  * if we trust that this code is incomplete (my objections and your TODO)
    then we have to partition it off for a while, till it's fleshed out.
    With ABI/API stability a prime concern, dropping in an interface that
    the group will suggest improvements and code style corrections for, is
    a bad thing in a live 1.x release at this stage of it's development.

I.O.W. I like many of the things we could accomplish here, but single-purpose
code doesn't belong in apr-util.  Let's flesh out these ideas here on list.

Bill

Re: svn commit: r329089 - in /apr/apr-util/trunk: build.conf include/apr_memcache.h memcache/ memcache/apr_memcache.c

Posted by "William A. Rowe, Jr." <wr...@rowe-clan.net>.
pquerna@apache.org wrote:
> Author: pquerna
> Date: Thu Oct 27 21:23:01 2005
> New Revision: 329089
> 
> URL: http://svn.apache.org/viewcvs?rev=329089&view=rev
> Log:
> Import apr_memcache trunk to APR-Util.
> 
> Still TODO:
> - Move CRC32 to a separate public function/API.
> - Create a multi-get interface.
> - Make locking and connection pooling optional.

For the moment, -1, please revert and begin an apr-memcache sandbox because...

  * it makes trunk/ unstable, preventing us from moving to 1.3

  * this seems entirely too specialized (in other words, useless code bloat)
    All of the concepts here -are- useful, but not in this aggregation.

  * if we trust that this code is incomplete (my objections and your TODO)
    then we have to partition it off for a while, till it's fleshed out.
    With ABI/API stability a prime concern, dropping in an interface that
    the group will suggest improvements and code style corrections for, is
    a bad thing in a live 1.x release at this stage of it's development.

I.O.W. I like many of the things we could accomplish here, but single-purpose
code doesn't belong in apr-util.  Let's flesh out these ideas here on list.

Bill