Make the configuration store use a serf allocator instead of pools for its
data to allow clearing out values without a (very slow) memory leak.

* config_store.c
  (serf__config_hdr_t): Remove pool.
  (config_free_cb_t): New typedef.
  (config_entry_t): Add free callback.
  (create_config_hdr): Use allocator.
  (add_or_replace_entry): Allow storing how to free value.
  (add_or_replace_entry): Handle free support.
  (config_set_object): New helper function, extracted from
    serf__config_set_object.
  (cleanup_hdr,
   cleanup_store): New function.
  (serf__config_store_init): Create allocator. Hook explicit cleanup.
  (serf__config_store_create_ctx_config,
   serf__config_store_create_conn_config,
   serf__config_store_create_client_config,
   serf__config_store_create_listener_config): Remove out_pool, which is only
     partially used as result pool. Use the context pool directly.
  (serf_config_set_stringc): Use config_set_object and declare how to free.
  (serf_config_set_stringf): Add scratch_pool argument. Update caller.
  (serf_config_set_object): Move some code to config_set_object.

* context.c
  (serf_context_create_ex): Update caller.

* incoming.c
  (serf_incoming_create2,
   serf_listener_create): Update caller.

* outgoing.c
  (serf_connection_create2): Update caller.

* pump.c
  (serf_pump__store_ipaddresses_in_config): Update caller.

* serf.h
  (serf_config_set_stringf): Add argument.

* serf_private.h
  (serf_config_t): Store allocator instead of conn_pool.
  (serf__config_store_t): Store allocator. Update comment.
  (serf__config_store_create_conn_config,
   serf__config_store_create_client_config,
   serf__config_store_create_listener_config,
   serf__config_store_create_ctx_config): Remove argument.


git-svn-id: https://svn.apache.org/repos/asf/serf/trunk@1717012 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/config_store.c b/config_store.c
index 801e112..48f03ad 100644
--- a/config_store.c
+++ b/config_store.c
@@ -27,25 +27,28 @@
 /* Use a linked list to store the config values, as we'll only store a couple
    of values per context. */
 struct serf__config_hdr_t {
-    apr_pool_t *pool;
     struct config_entry_t *first;
 };
 
+typedef void (*config_free_cb_t)(serf_bucket_alloc_t *alloc, void *value);
+
 typedef struct config_entry_t {
     apr_uint32_t key;
     void *value;
     struct config_entry_t *next;
+    config_free_cb_t free_cb;
 } config_entry_t;
 
-static serf__config_hdr_t *create_config_hdr(apr_pool_t *pool)
+static serf__config_hdr_t *create_config_hdr(serf_bucket_alloc_t *allocator)
 {
-    serf__config_hdr_t *hdr = apr_pcalloc(pool, sizeof(serf__config_hdr_t));
-    hdr->pool = pool;
+    serf__config_hdr_t *hdr = serf_bucket_mem_calloc(allocator, sizeof(*hdr));
     return hdr;
 }
 
 static apr_status_t
-add_or_replace_entry(serf__config_hdr_t *hdr, serf_config_key_t key, void *value)
+add_or_replace_entry(serf_config_t *config,
+                     serf__config_hdr_t *hdr, serf_config_key_t key, void *value,
+                     config_free_cb_t free_cb)
 {
     config_entry_t *iter = hdr->first;
     config_entry_t *last = iter;
@@ -63,12 +66,18 @@
 
     if (found) {
         iter->key = key;
+        if (iter->free_cb)
+            iter->free_cb(config->allocator, iter->value);
         iter->value = value;
+        iter->free_cb = free_cb;
     } else {
         /* Not found, create a new entry and append it to the list. */
-        config_entry_t *entry = apr_palloc(hdr->pool, sizeof(config_entry_t));
+        config_entry_t *entry = serf_bucket_mem_alloc(config->allocator,
+                                                      sizeof(*entry));
+
         entry->key = key;
         entry->value = value;
+        entry->free_cb = free_cb;
         entry->next = NULL;
 
         if (last)
@@ -80,16 +89,103 @@
     return APR_SUCCESS;
 }
 
+static apr_status_t config_set_object(serf_config_t *config,
+                                      serf_config_key_t key,
+                                      void *value,
+                                      config_free_cb_t free_cb)
+{
+    serf__config_hdr_t *target;
+
+    /* Set the value in the hash table of the selected category */
+    if (key & SERF_CONFIG_PER_CONTEXT) {
+        target = config->per_context;
+    }
+    else if (key & SERF_CONFIG_PER_HOST) {
+        target = config->per_host;
+    }
+    else {
+        target = config->per_conn;
+    }
+
+    if (!target) {
+        /* Config object doesn't manage keys in this category */
+        return APR_EINVAL;
+    }
+
+    return add_or_replace_entry(config, target, key, value, free_cb);
+}
+
+static void cleanup_hdr(serf__config_store_t *store, serf__config_hdr_t *hdr)
+{
+    config_entry_t *e = hdr->first;
+
+    serf_bucket_mem_free(store->allocator, hdr);
+
+    while (e) {
+        config_entry_t *next = e->next;
+
+        if (e->free_cb)
+            e->free_cb(store->allocator, e->value);
+
+        serf_bucket_mem_free(store->allocator, e);
+        e = next;
+    }
+}
+
+static apr_status_t cleanup_store(void *baton)
+{
+    serf__config_store_t *store = baton;
+    apr_hash_index_t *hi;
+
+    for (hi = apr_hash_first(store->pool, store->global_per_host);
+         hi;
+         hi = apr_hash_next(hi))
+      {
+          const char *key;
+          void *val;
+
+          apr_hash_this(hi, &key, NULL, &val);
+
+          serf_bucket_mem_free(store->allocator, (void *)key);
+
+          cleanup_hdr(store, val);
+      }
+
+    for (hi = apr_hash_first(store->pool, store->global_per_conn);
+         hi;
+         hi = apr_hash_next(hi))
+      {
+          const char *key;
+          void *val;
+
+          apr_hash_this(hi, &key, NULL, &val);
+
+          serf_bucket_mem_free(store->allocator, (void *)key);
+
+          cleanup_hdr(store, val);
+      }
+
+    cleanup_hdr(store, store->global_per_context);
+
+    return APR_SUCCESS;
+}
+
 /*** Config Store ***/
 apr_status_t serf__config_store_init(serf_context_t *ctx)
 {
     apr_pool_t *pool = ctx->pool;
+    serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(pool,
+                                                              NULL, NULL);
 
     ctx->config_store.pool = pool;
-    ctx->config_store.global_per_context = create_config_hdr(pool);
+    ctx->config_store.allocator = alloc;
+    ctx->config_store.global_per_context = create_config_hdr(alloc);
     ctx->config_store.global_per_host = apr_hash_make(pool);
     ctx->config_store.global_per_conn = apr_hash_make(pool);
 
+    apr_pool_cleanup_register(pool, &ctx->config_store, cleanup_store,
+                              apr_pool_cleanup_null);
+
     return APR_SUCCESS;
 }
 
@@ -125,15 +221,14 @@
     return apr_psprintf(pool, "%pp", listener);
 }
 
-
 apr_status_t serf__config_store_create_ctx_config(serf_context_t *ctx,
-                                                  serf_config_t **config,
-                                                  apr_pool_t *out_pool)
+                                                  serf_config_t **config)
 {
     serf__config_store_t *config_store = &ctx->config_store;
 
-    serf_config_t *cfg = apr_pcalloc(out_pool, sizeof(serf_config_t));
-    cfg->ctx_pool = ctx->pool;
+    serf_config_t *cfg = apr_pcalloc(ctx->pool, sizeof(serf_config_t));
+    cfg->ctx_pool = config_store->pool;
+    cfg->allocator = config_store->allocator;
     cfg->per_context = config_store->global_per_context;
 
     *config = cfg;
@@ -141,8 +236,7 @@
 }
 
 apr_status_t serf__config_store_create_conn_config(serf_connection_t *conn,
-                                                   serf_config_t **config,
-                                                   apr_pool_t *out_pool)
+                                                   serf_config_t **config)
 {
     serf__config_store_t *config_store = &conn->ctx->config_store;
     const char *host_key, *conn_key;
@@ -150,13 +244,12 @@
     apr_pool_t *tmp_pool;
     apr_status_t status;
 
-    serf_config_t *cfg = apr_pcalloc(out_pool, sizeof(serf_config_t));
-    cfg->ctx_pool = conn->ctx->pool;
+    serf_config_t *cfg = apr_pcalloc(conn->pool, sizeof(serf_config_t));
+    cfg->ctx_pool = config_store->pool;
+    cfg->allocator = config_store->allocator;
     cfg->per_context = config_store->global_per_context;
 
-    cfg->conn_pool = conn->pool;
-
-    if ((status = apr_pool_create(&tmp_pool, out_pool)) != APR_SUCCESS)
+    if ((status = apr_pool_create(&tmp_pool, cfg->ctx_pool)) != APR_SUCCESS)
         return status;
 
     /* Find the config values for this connection, create empty structure
@@ -165,10 +258,10 @@
     per_conn = apr_hash_get(config_store->global_per_conn, conn_key,
                             APR_HASH_KEY_STRING);
     if (!per_conn) {
-        per_conn = create_config_hdr(conn->pool);
+        per_conn = create_config_hdr(cfg->allocator);
         apr_hash_set(config_store->global_per_conn,
-                        apr_pstrdup(conn->pool, conn_key),
-                        APR_HASH_KEY_STRING, per_conn);
+                     serf_bstrdup(cfg->allocator, conn_key),
+                     APR_HASH_KEY_STRING, per_conn);
     }
     cfg->per_conn = per_conn;
 
@@ -179,10 +272,10 @@
                             host_key,
                             APR_HASH_KEY_STRING);
     if (!per_host) {
-        per_host = create_config_hdr(config_store->pool);
+        per_host = create_config_hdr(cfg->allocator);
         apr_hash_set(config_store->global_per_host,
-                        apr_pstrdup(config_store->pool, host_key),
-                        APR_HASH_KEY_STRING, per_host);
+                     serf_bstrdup(cfg->allocator, host_key),
+                     APR_HASH_KEY_STRING, per_host);
     }
     cfg->per_host = per_host;
 
@@ -194,8 +287,7 @@
 }
 
 apr_status_t serf__config_store_create_client_config(serf_incoming_t *client,
-                                                     serf_config_t **config,
-                                                     apr_pool_t *out_pool)
+                                                     serf_config_t **config)
 {
     serf__config_store_t *config_store = &client->ctx->config_store;
     const char *client_key;
@@ -203,14 +295,12 @@
     apr_pool_t *tmp_pool;
     apr_status_t status;
 
-    serf_config_t *cfg = apr_pcalloc(out_pool, sizeof(serf_config_t));
-    cfg->ctx_pool = client->ctx->pool;
+    serf_config_t *cfg = apr_pcalloc(client->pool, sizeof(serf_config_t));
+    cfg->ctx_pool = config_store->pool;
+    cfg->allocator = config_store->allocator;
     cfg->per_context = config_store->global_per_context;
 
-
-    cfg->conn_pool = client->pool;
-
-    if ((status = apr_pool_create(&tmp_pool, out_pool)) != APR_SUCCESS)
+    if ((status = apr_pool_create(&tmp_pool, client->pool)) != APR_SUCCESS)
         return status;
 
     /* Find the config values for this connection, create empty structure
@@ -219,10 +309,10 @@
     per_conn = apr_hash_get(config_store->global_per_conn, client_key,
                             APR_HASH_KEY_STRING);
     if (!per_conn) {
-        per_conn = create_config_hdr(client->pool);
+        per_conn = create_config_hdr(cfg->allocator);
         apr_hash_set(config_store->global_per_conn,
-                        apr_pstrdup(client->pool, client_key),
-                        APR_HASH_KEY_STRING, per_conn);
+                     serf_bstrdup(cfg->allocator, client_key),
+                     APR_HASH_KEY_STRING, per_conn);
     }
     cfg->per_conn = per_conn;
     cfg->per_host = NULL;
@@ -235,8 +325,7 @@
 }
 
 apr_status_t serf__config_store_create_listener_config(serf_listener_t *listener,
-                                                       serf_config_t **config,
-                                                       apr_pool_t *out_pool)
+                                                       serf_config_t **config)
 {
     serf__config_store_t *config_store = &listener->ctx->config_store;
     const char *client_key;
@@ -244,13 +333,12 @@
     apr_pool_t *tmp_pool;
     apr_status_t status;
 
-    serf_config_t *cfg = apr_pcalloc(out_pool, sizeof(serf_config_t));
-    cfg->ctx_pool = listener->ctx->pool;
+    serf_config_t *cfg = apr_pcalloc(listener->pool, sizeof(serf_config_t));
+    cfg->ctx_pool = config_store->pool;
+    cfg->allocator = config_store->allocator;
     cfg->per_context = config_store->global_per_context;
 
-    cfg->conn_pool = listener->pool;
-
-    if ((status = apr_pool_create(&tmp_pool, out_pool)) != APR_SUCCESS)
+    if ((status = apr_pool_create(&tmp_pool, listener->pool)) != APR_SUCCESS)
         return status;
 
     /* Find the config values for this connection, create empty structure
@@ -259,9 +347,9 @@
     per_conn = apr_hash_get(config_store->global_per_conn, client_key,
                             APR_HASH_KEY_STRING);
     if (!per_conn) {
-        per_conn = create_config_hdr(listener->pool);
+        per_conn = create_config_hdr(cfg->allocator);
         apr_hash_set(config_store->global_per_conn,
-                     apr_pstrdup(listener->pool, client_key),
+                     serf_bstrdup(cfg->allocator, client_key),
                      APR_HASH_KEY_STRING, per_conn);
     }
     cfg->per_conn = per_conn;
@@ -310,66 +398,34 @@
                                      serf_config_key_t key,
                                      const char *value)
 {
-    const char *cvalue;
-    apr_pool_t *pool;
+    char *cvalue;
 
-    if (key & SERF_CONFIG_PER_CONTEXT ||
-        key & SERF_CONFIG_PER_HOST) {
-        pool = config->ctx_pool;
-    } else {
-        pool = config->conn_pool;
-    }
+    cvalue = serf_bstrdup(config->allocator, value);
 
-    cvalue = apr_pstrdup(pool, value);
-
-    return serf_config_set_string(config, key, cvalue);
+    return config_set_object(config, key, cvalue,
+                             serf_bucket_mem_free);
 }
 
 apr_status_t serf_config_set_stringf(serf_config_t *config,
                                      serf_config_key_t key,
+                                     apr_pool_t *scratch_pool,
                                      const char *fmt, ...)
 {
-    apr_pool_t *pool;
     va_list argp;
     char *cvalue;
 
-    if (key & SERF_CONFIG_PER_CONTEXT)
-        pool = config->ctx_pool;
-    else if (key & SERF_CONFIG_PER_HOST)
-        pool = config->ctx_pool;
-    else
-        pool = config->conn_pool;
-
     va_start(argp, fmt);
-    cvalue = apr_pvsprintf(pool, fmt, argp);
+    cvalue = apr_pvsprintf(scratch_pool, fmt, argp);
     va_end(argp);
 
-    return serf_config_set_string(config, key, cvalue);
+    return serf_config_set_stringc(config, key, cvalue);
 }
 
 apr_status_t serf_config_set_object(serf_config_t *config,
                                     serf_config_key_t key,
                                     void *value)
 {
-    serf__config_hdr_t *target;
-
-    /* Set the value in the hash table of the selected category */
-    if (key & SERF_CONFIG_PER_CONTEXT) {
-        target = config->per_context;
-    }
-    else if (key & SERF_CONFIG_PER_HOST) {
-        target = config->per_host;
-    }
-    else {
-        target = config->per_conn;
-    }
-
-    if (!target) {
-        /* Config object doesn't manage keys in this category */
-        return APR_EINVAL;
-    }
-
-    return add_or_replace_entry(target, key, value);
+    return config_set_object(config, key, value, NULL);
 }
 
 apr_status_t serf_config_get_string(serf_config_t *config,
diff --git a/context.c b/context.c
index 86c256b..f471fed 100644
--- a/context.c
+++ b/context.c
@@ -195,7 +195,7 @@
     /* Assume returned status is APR_SUCCESS */
     serf__config_store_init(ctx);
 
-    serf__config_store_create_ctx_config(ctx, &ctx->config, ctx->pool);
+    serf__config_store_create_ctx_config(ctx, &ctx->config);
 
     serf__log_init(ctx);
 
diff --git a/incoming.c b/incoming.c
index 3323629..36679bd 100644
--- a/incoming.c
+++ b/incoming.c
@@ -577,7 +577,7 @@
     ic->closed_baton = closed_baton;
 
     /* Store the connection specific info in the configuration store */
-    status = serf__config_store_create_client_config(ic, &config, client_pool);
+    status = serf__config_store_create_client_config(ic, &config);
     if (status)
         return status;
 
@@ -689,7 +689,7 @@
     }
 
     /* Store the connection specific info in the configuration store */
-    rv = serf__config_store_create_listener_config(l, &config, l->pool);
+    rv = serf__config_store_create_listener_config(l, &config);
     if (rv)
         return rv;
 
diff --git a/outgoing.c b/outgoing.c
index 3e2ab84..371f1d0 100644
--- a/outgoing.c
+++ b/outgoing.c
@@ -1319,7 +1319,7 @@
     }
 
     /* Store the connection specific info in the configuration store */
-    status = serf__config_store_create_conn_config(c, &config, pool);
+    status = serf__config_store_create_conn_config(c, &config);
     if (status)
         return status;
     c->config = config;
diff --git a/pump.c b/pump.c
index 300968d..aa08bd2 100644
--- a/pump.c
+++ b/pump.c
@@ -192,13 +192,13 @@
         char buf[48];
         if (!apr_sockaddr_ip_getbuf(buf, sizeof(buf), sa))
             serf_config_set_stringf(pump->config, SERF_CONFIG_CONN_LOCALIP,
-                                    "%s:%d", buf, (int)sa->port);
+                                    pump->pool, "%s:%d", buf, (int)sa->port);
     }
     if (apr_socket_addr_get(&sa, APR_REMOTE, pump->skt) == APR_SUCCESS) {
         char buf[48];
         if (!apr_sockaddr_ip_getbuf(buf, sizeof(buf), sa))
             serf_config_set_stringf(pump->config, SERF_CONFIG_CONN_REMOTEIP,
-                                    "%s:%d", buf, (int)sa->port);
+                                    pump->pool, "%s:%d", buf, (int)sa->port);
     }
 }
 
diff --git a/serf.h b/serf.h
index e58f538..362eb12 100644
--- a/serf.h
+++ b/serf.h
@@ -1351,6 +1351,7 @@
  */
 apr_status_t serf_config_set_stringf(serf_config_t *config,
                                      serf_config_key_t key,
+                                     apr_pool_t *scratch_pool,
                                      const char *fmt, ...);
 
 /* Set a value of generic type for configuration item CATEGORY+KEY.
diff --git a/serf_private.h b/serf_private.h
index 43acc0c..5e179c5 100644
--- a/serf_private.h
+++ b/serf_private.h
@@ -290,11 +290,8 @@
 typedef struct serf__config_hdr_t serf__config_hdr_t;
 
 struct serf_config_t {
-    /* Pool for per-connection configuration values */
-    apr_pool_t *conn_pool;
-    /* Pool for per-host and per-context configuration values */
     apr_pool_t *ctx_pool;
-
+    serf_bucket_alloc_t *allocator;
 
     /* Configuration key/value pairs per context */
     serf__config_hdr_t *per_context;
@@ -306,19 +303,20 @@
 
 typedef struct serf__config_store_t {
     apr_pool_t *pool;
+    serf_bucket_alloc_t *allocator;
 
     /* Configuration key/value pairs per context */
     serf__config_hdr_t *global_per_context;
 
-    /* Configuration per host, dual-layered:
+    /* Configuration per host:
      Key: hostname:port
-     Value: hash table of per host key/value pairs
+     Value: serf__config_hdr_t *
      */
     apr_hash_t *global_per_host;
 
-    /* Configuration per connection, dual-layered:
-     Key: string(connection ptr) (?)
-     Value: hash table of per host key/value pairs
+    /* Configuration per connection:
+     Key: string(connection ptr as string)
+     Value: serf__config_hdr_t *
      */
     apr_hash_t *global_per_conn;
 
@@ -340,23 +338,19 @@
    lifecycle cannot extend beyond that of the serf context!
  */
 apr_status_t serf__config_store_create_conn_config(serf_connection_t *conn,
-                                                   serf_config_t **config,
-                                                   apr_pool_t *out_pool);
+                                                   serf_config_t **config);
 
 /* Same thing, but for incoming connections */
 apr_status_t serf__config_store_create_client_config(serf_incoming_t *client,
-                                                     serf_config_t **config,
-                                                     apr_pool_t *out_pool);
+                                                     serf_config_t **config);
 
 /* Same thing, but for listeners */
 apr_status_t serf__config_store_create_listener_config(serf_listener_t *listener,
-                                                       serf_config_t **config,
-                                                       apr_pool_t *out_pool);
+                                                       serf_config_t **config);
 
 /* Same thing, but for the context itself */
 apr_status_t serf__config_store_create_ctx_config(serf_context_t *ctx,
-                                                  serf_config_t **config,
-                                                  apr_pool_t *out_pool);
+                                                  serf_config_t **config);
 
 
 /* Cleans up all connection specific configuration values */