summaryrefslogtreecommitdiff
path: root/buckets/allocator.c
diff options
context:
space:
mode:
Diffstat (limited to 'buckets/allocator.c')
-rw-r--r--buckets/allocator.c434
1 files changed, 434 insertions, 0 deletions
diff --git a/buckets/allocator.c b/buckets/allocator.c
new file mode 100644
index 0000000..857cc74
--- /dev/null
+++ b/buckets/allocator.c
@@ -0,0 +1,434 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * 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 <stdlib.h>
+
+#include <apr_pools.h>
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+
+
+typedef struct node_header_t {
+ apr_size_t size;
+ union {
+ struct node_header_t *next; /* if size == 0 (freed/inactive) */
+ /* no data if size == STANDARD_NODE_SIZE */
+ apr_memnode_t *memnode; /* if size > STANDARD_NODE_SIZE */
+ } u;
+} node_header_t;
+
+/* The size of a node_header_t, properly aligned. Note that (normally)
+ * this macro will round the size to a multiple of 8 bytes. Keep this in
+ * mind when altering the node_header_t structure. Also, keep in mind that
+ * node_header_t is an overhead for every allocation performed through
+ * the serf_bucket_mem_alloc() function.
+ */
+#define SIZEOF_NODE_HEADER_T APR_ALIGN_DEFAULT(sizeof(node_header_t))
+
+
+/* STANDARD_NODE_SIZE is manually set to an allocation size that will
+ * capture most allocators performed via this API. It must be "large
+ * enough" to avoid lots of spillage to allocating directly from the
+ * apr_allocator associated with the bucket allocator. The apr_allocator
+ * has a minimum size of 8k, which can be expensive if you missed the
+ * STANDARD_NODE_SIZE by just a few bytes.
+ */
+/* ### we should define some rules or ways to determine how to derive
+ * ### a "good" value for this. probably log some stats on allocs, then
+ * ### analyze them for size "misses". then find the balance point between
+ * ### wasted space due to min-size allocator, and wasted-space due to
+ * ### size-spill to the 8k minimum.
+ */
+#define STANDARD_NODE_SIZE 128
+
+/* When allocating a block of memory from the allocator, we should go for
+ * an 8k block, minus the overhead that the allocator needs.
+ */
+#define ALLOC_AMT (8192 - APR_MEMNODE_T_SIZE)
+
+/* Define DEBUG_DOUBLE_FREE if you're interested in debugging double-free
+ * calls to serf_bucket_mem_free().
+ */
+#define DEBUG_DOUBLE_FREE
+
+
+typedef struct {
+ const serf_bucket_t *bucket;
+ apr_status_t last;
+} read_status_t;
+
+#define TRACK_BUCKET_COUNT 100 /* track N buckets' status */
+
+typedef struct {
+ int next_index; /* info[] is a ring. next bucket goes at this idx. */
+ int num_used;
+
+ read_status_t info[TRACK_BUCKET_COUNT];
+} track_state_t;
+
+
+struct serf_bucket_alloc_t {
+ apr_pool_t *pool;
+ apr_allocator_t *allocator;
+ int own_allocator;
+
+ serf_unfreed_func_t unfreed;
+ void *unfreed_baton;
+
+ apr_uint32_t num_alloc;
+
+ node_header_t *freelist; /* free STANDARD_NODE_SIZE blocks */
+ apr_memnode_t *blocks; /* blocks we allocated for subdividing */
+
+ track_state_t *track;
+};
+
+/* ==================================================================== */
+
+
+static apr_status_t allocator_cleanup(void *data)
+{
+ serf_bucket_alloc_t *allocator = data;
+
+ /* If we allocated anything, give it back. */
+ if (allocator->blocks) {
+ apr_allocator_free(allocator->allocator, allocator->blocks);
+ }
+
+ /* If we allocated our own allocator (?!), destroy it here. */
+ if (allocator->own_allocator) {
+ apr_allocator_destroy(allocator->allocator);
+ }
+
+ return APR_SUCCESS;
+}
+
+serf_bucket_alloc_t *serf_bucket_allocator_create(
+ apr_pool_t *pool,
+ serf_unfreed_func_t unfreed,
+ void *unfreed_baton)
+{
+ serf_bucket_alloc_t *allocator = apr_pcalloc(pool, sizeof(*allocator));
+
+ allocator->pool = pool;
+ allocator->allocator = apr_pool_allocator_get(pool);
+ if (allocator->allocator == NULL) {
+ /* This most likely means pools are running in debug mode, create our
+ * own allocator to deal with memory ourselves */
+ apr_allocator_create(&allocator->allocator);
+ allocator->own_allocator = 1;
+ }
+ allocator->unfreed = unfreed;
+ allocator->unfreed_baton = unfreed_baton;
+
+#ifdef SERF_DEBUG_BUCKET_USE
+ {
+ track_state_t *track;
+
+ track = allocator->track = apr_palloc(pool, sizeof(*allocator->track));
+ track->next_index = 0;
+ track->num_used = 0;
+ }
+#endif
+
+ /* ### this implies buckets cannot cross a fork/exec. desirable?
+ *
+ * ### hmm. it probably also means that buckets cannot be AROUND
+ * ### during a fork/exec. the new process will try to clean them
+ * ### up and figure out there are unfreed blocks...
+ */
+ apr_pool_cleanup_register(pool, allocator,
+ allocator_cleanup, allocator_cleanup);
+
+ return allocator;
+}
+
+apr_pool_t *serf_bucket_allocator_get_pool(
+ const serf_bucket_alloc_t *allocator)
+{
+ return allocator->pool;
+}
+
+
+void *serf_bucket_mem_alloc(
+ serf_bucket_alloc_t *allocator,
+ apr_size_t size)
+{
+ node_header_t *node;
+
+ ++allocator->num_alloc;
+
+ size += SIZEOF_NODE_HEADER_T;
+ if (size <= STANDARD_NODE_SIZE) {
+ if (allocator->freelist) {
+ /* just pull a node off our freelist */
+ node = allocator->freelist;
+ allocator->freelist = node->u.next;
+#ifdef DEBUG_DOUBLE_FREE
+ /* When we free an item, we set its size to zero. Thus, when
+ * we return it to the caller, we must ensure the size is set
+ * properly.
+ */
+ node->size = STANDARD_NODE_SIZE;
+#endif
+ }
+ else {
+ apr_memnode_t *active = allocator->blocks;
+
+ if (active == NULL
+ || active->first_avail + STANDARD_NODE_SIZE >= active->endp) {
+ apr_memnode_t *head = allocator->blocks;
+
+ /* ran out of room. grab another block. */
+ active = apr_allocator_alloc(allocator->allocator, ALLOC_AMT);
+
+ /* System couldn't provide us with memory. */
+ if (active == NULL)
+ return NULL;
+
+ /* link the block into our tracking list */
+ allocator->blocks = active;
+ active->next = head;
+ }
+
+ node = (node_header_t *)active->first_avail;
+ node->size = STANDARD_NODE_SIZE;
+ active->first_avail += STANDARD_NODE_SIZE;
+ }
+ }
+ else {
+ apr_memnode_t *memnode = apr_allocator_alloc(allocator->allocator,
+ size);
+
+ if (memnode == NULL)
+ return NULL;
+
+ node = (node_header_t *)memnode->first_avail;
+ node->u.memnode = memnode;
+ node->size = size;
+ }
+
+ return ((char *)node) + SIZEOF_NODE_HEADER_T;
+}
+
+
+void *serf_bucket_mem_calloc(
+ serf_bucket_alloc_t *allocator,
+ apr_size_t size)
+{
+ void *mem;
+ mem = serf_bucket_mem_alloc(allocator, size);
+ if (mem == NULL)
+ return NULL;
+ memset(mem, 0, size);
+ return mem;
+}
+
+
+void serf_bucket_mem_free(
+ serf_bucket_alloc_t *allocator,
+ void *block)
+{
+ node_header_t *node;
+
+ --allocator->num_alloc;
+
+ node = (node_header_t *)((char *)block - SIZEOF_NODE_HEADER_T);
+
+ if (node->size == STANDARD_NODE_SIZE) {
+ /* put the node onto our free list */
+ node->u.next = allocator->freelist;
+ allocator->freelist = node;
+
+#ifdef DEBUG_DOUBLE_FREE
+ /* note that this thing was freed. */
+ node->size = 0;
+ }
+ else if (node->size == 0) {
+ /* damn thing was freed already. */
+ abort();
+#endif
+ }
+ else {
+#ifdef DEBUG_DOUBLE_FREE
+ /* note that this thing was freed. */
+ node->size = 0;
+#endif
+
+ /* now free it */
+ apr_allocator_free(allocator->allocator, node->u.memnode);
+ }
+}
+
+
+/* ==================================================================== */
+
+
+#ifdef SERF_DEBUG_BUCKET_USE
+
+static read_status_t *find_read_status(
+ track_state_t *track,
+ const serf_bucket_t *bucket,
+ int create_rs)
+{
+ read_status_t *rs;
+
+ if (track->num_used) {
+ int count = track->num_used;
+ int idx = track->next_index;
+
+ /* Search backwards. In all likelihood, the bucket which just got
+ * read was read very recently.
+ */
+ while (count-- > 0) {
+ if (!idx--) {
+ /* assert: track->num_used == TRACK_BUCKET_COUNT */
+ idx = track->num_used - 1;
+ }
+ if ((rs = &track->info[idx])->bucket == bucket) {
+ return rs;
+ }
+ }
+ }
+
+ /* Only create a new read_status_t when asked. */
+ if (!create_rs)
+ return NULL;
+
+ if (track->num_used < TRACK_BUCKET_COUNT) {
+ /* We're still filling up the ring. */
+ ++track->num_used;
+ }
+
+ rs = &track->info[track->next_index];
+ rs->bucket = bucket;
+ rs->last = APR_SUCCESS; /* ### the right initial value? */
+
+ if (++track->next_index == TRACK_BUCKET_COUNT)
+ track->next_index = 0;
+
+ return rs;
+}
+
+#endif /* SERF_DEBUG_BUCKET_USE */
+
+
+apr_status_t serf_debug__record_read(
+ const serf_bucket_t *bucket,
+ apr_status_t status)
+{
+#ifndef SERF_DEBUG_BUCKET_USE
+ return status;
+#else
+
+ track_state_t *track = bucket->allocator->track;
+ read_status_t *rs = find_read_status(track, bucket, 1);
+
+ /* Validate that the previous status value allowed for another read. */
+ if (APR_STATUS_IS_EAGAIN(rs->last) /* ### or APR_EOF? */) {
+ /* Somebody read when they weren't supposed to. Bail. */
+ abort();
+ }
+
+ /* Save the current status for later. */
+ rs->last = status;
+
+ return status;
+#endif
+}
+
+
+void serf_debug__entered_loop(serf_bucket_alloc_t *allocator)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+
+ track_state_t *track = allocator->track;
+ read_status_t *rs = &track->info[0];
+
+ for ( ; track->num_used; --track->num_used, ++rs ) {
+ if (rs->last == APR_SUCCESS) {
+ /* Somebody should have read this bucket again. */
+ abort();
+ }
+
+ /* ### other status values? */
+ }
+
+ /* num_used was reset. also need to reset the next index. */
+ track->next_index = 0;
+
+#endif
+}
+
+
+void serf_debug__closed_conn(serf_bucket_alloc_t *allocator)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+
+ /* Just reset the number used so that we don't examine the info[] */
+ allocator->track->num_used = 0;
+ allocator->track->next_index = 0;
+
+#endif
+}
+
+
+void serf_debug__bucket_destroy(const serf_bucket_t *bucket)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+
+ track_state_t *track = bucket->allocator->track;
+ read_status_t *rs = find_read_status(track, bucket, 0);
+
+ if (rs != NULL && rs->last != APR_EOF) {
+ /* The bucket was destroyed before it was read to completion. */
+
+ /* Special exception for socket buckets. If a connection remains
+ * open, they are not read to completion.
+ */
+ if (SERF_BUCKET_IS_SOCKET(bucket))
+ return;
+
+ /* Ditto for SSL Decrypt buckets. */
+ if (SERF_BUCKET_IS_SSL_DECRYPT(bucket))
+ return;
+
+ /* Ditto for SSL Encrypt buckets. */
+ if (SERF_BUCKET_IS_SSL_ENCRYPT(bucket))
+ return;
+
+ /* Ditto for barrier buckets. */
+ if (SERF_BUCKET_IS_BARRIER(bucket))
+ return;
+
+
+ abort();
+ }
+
+#endif
+}
+
+
+void serf_debug__bucket_alloc_check(
+ serf_bucket_alloc_t *allocator)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+ if (allocator->num_alloc != 0) {
+ abort();
+ }
+#endif
+}
+