Blame buckets/allocator.c

Packit 3adb1e
/* ====================================================================
Packit 3adb1e
 *    Licensed to the Apache Software Foundation (ASF) under one
Packit 3adb1e
 *    or more contributor license agreements.  See the NOTICE file
Packit 3adb1e
 *    distributed with this work for additional information
Packit 3adb1e
 *    regarding copyright ownership.  The ASF licenses this file
Packit 3adb1e
 *    to you under the Apache License, Version 2.0 (the
Packit 3adb1e
 *    "License"); you may not use this file except in compliance
Packit 3adb1e
 *    with the License.  You may obtain a copy of the License at
Packit 3adb1e
 *
Packit 3adb1e
 *      http://www.apache.org/licenses/LICENSE-2.0
Packit 3adb1e
 *
Packit 3adb1e
 *    Unless required by applicable law or agreed to in writing,
Packit 3adb1e
 *    software distributed under the License is distributed on an
Packit 3adb1e
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
Packit 3adb1e
 *    KIND, either express or implied.  See the License for the
Packit 3adb1e
 *    specific language governing permissions and limitations
Packit 3adb1e
 *    under the License.
Packit 3adb1e
 * ====================================================================
Packit 3adb1e
 */
Packit 3adb1e
Packit 3adb1e
#include <stdlib.h>
Packit 3adb1e
Packit 3adb1e
#include <apr_pools.h>
Packit 3adb1e
Packit 3adb1e
#include "serf.h"
Packit 3adb1e
#include "serf_bucket_util.h"
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
typedef struct node_header_t {
Packit 3adb1e
    apr_size_t size;
Packit 3adb1e
    union {
Packit 3adb1e
        struct node_header_t *next;      /* if size == 0 (freed/inactive) */
Packit 3adb1e
        /* no data                          if size == STANDARD_NODE_SIZE */
Packit 3adb1e
        apr_memnode_t *memnode;          /* if size > STANDARD_NODE_SIZE */
Packit 3adb1e
    } u;
Packit 3adb1e
} node_header_t;
Packit 3adb1e
Packit 3adb1e
/* The size of a node_header_t, properly aligned. Note that (normally)
Packit 3adb1e
 * this macro will round the size to a multiple of 8 bytes. Keep this in
Packit 3adb1e
 * mind when altering the node_header_t structure. Also, keep in mind that
Packit 3adb1e
 * node_header_t is an overhead for every allocation performed through
Packit 3adb1e
 * the serf_bucket_mem_alloc() function.
Packit 3adb1e
 */
Packit 3adb1e
#define SIZEOF_NODE_HEADER_T  APR_ALIGN_DEFAULT(sizeof(node_header_t))
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
/* STANDARD_NODE_SIZE is manually set to an allocation size that will
Packit 3adb1e
 * capture most allocators performed via this API. It must be "large
Packit 3adb1e
 * enough" to avoid lots of spillage to allocating directly from the
Packit 3adb1e
 * apr_allocator associated with the bucket allocator. The apr_allocator
Packit 3adb1e
 * has a minimum size of 8k, which can be expensive if you missed the
Packit 3adb1e
 * STANDARD_NODE_SIZE by just a few bytes.
Packit 3adb1e
 */
Packit 3adb1e
/* ### we should define some rules or ways to determine how to derive
Packit 3adb1e
 * ### a "good" value for this. probably log some stats on allocs, then
Packit 3adb1e
 * ### analyze them for size "misses". then find the balance point between
Packit 3adb1e
 * ### wasted space due to min-size allocator, and wasted-space due to
Packit 3adb1e
 * ### size-spill to the 8k minimum.
Packit 3adb1e
 */
Packit 3adb1e
#define STANDARD_NODE_SIZE 128
Packit 3adb1e
Packit 3adb1e
/* When allocating a block of memory from the allocator, we should go for
Packit 3adb1e
 * an 8k block, minus the overhead that the allocator needs.
Packit 3adb1e
 */
Packit 3adb1e
#define ALLOC_AMT (8192 - APR_MEMNODE_T_SIZE)
Packit 3adb1e
Packit 3adb1e
/* Define DEBUG_DOUBLE_FREE if you're interested in debugging double-free
Packit 3adb1e
 * calls to serf_bucket_mem_free().
Packit 3adb1e
 */
Packit 3adb1e
#define DEBUG_DOUBLE_FREE
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
typedef struct {
Packit 3adb1e
    const serf_bucket_t *bucket;
Packit 3adb1e
    apr_status_t last;
Packit 3adb1e
} read_status_t;
Packit 3adb1e
Packit 3adb1e
#define TRACK_BUCKET_COUNT 100  /* track N buckets' status */
Packit 3adb1e
Packit 3adb1e
typedef struct {
Packit 3adb1e
    int next_index;    /* info[] is a ring. next bucket goes at this idx. */
Packit 3adb1e
    int num_used;
Packit 3adb1e
Packit 3adb1e
    read_status_t info[TRACK_BUCKET_COUNT];
Packit 3adb1e
} track_state_t;
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
struct serf_bucket_alloc_t {
Packit 3adb1e
    apr_pool_t *pool;
Packit 3adb1e
    apr_allocator_t *allocator;
Packit 3adb1e
    int own_allocator;
Packit 3adb1e
Packit 3adb1e
    serf_unfreed_func_t unfreed;
Packit 3adb1e
    void *unfreed_baton;
Packit 3adb1e
Packit 3adb1e
    apr_uint32_t num_alloc;
Packit 3adb1e
Packit 3adb1e
    node_header_t *freelist;    /* free STANDARD_NODE_SIZE blocks */
Packit 3adb1e
    apr_memnode_t *blocks;      /* blocks we allocated for subdividing */
Packit 3adb1e
Packit 3adb1e
    track_state_t *track;
Packit 3adb1e
};
Packit 3adb1e
Packit 3adb1e
/* ==================================================================== */
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
static apr_status_t allocator_cleanup(void *data)
Packit 3adb1e
{
Packit 3adb1e
    serf_bucket_alloc_t *allocator = data;
Packit 3adb1e
Packit 3adb1e
    /* If we allocated anything, give it back. */
Packit 3adb1e
    if (allocator->blocks) {
Packit 3adb1e
        apr_allocator_free(allocator->allocator, allocator->blocks);
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* If we allocated our own allocator (?!), destroy it here. */
Packit 3adb1e
    if (allocator->own_allocator) {
Packit 3adb1e
        apr_allocator_destroy(allocator->allocator);
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    return APR_SUCCESS;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
serf_bucket_alloc_t *serf_bucket_allocator_create(
Packit 3adb1e
    apr_pool_t *pool,
Packit 3adb1e
    serf_unfreed_func_t unfreed,
Packit 3adb1e
    void *unfreed_baton)
Packit 3adb1e
{
Packit 3adb1e
    serf_bucket_alloc_t *allocator = apr_pcalloc(pool, sizeof(*allocator));
Packit 3adb1e
Packit 3adb1e
    allocator->pool = pool;
Packit 3adb1e
    allocator->allocator = apr_pool_allocator_get(pool);
Packit 3adb1e
    if (allocator->allocator == NULL) {
Packit 3adb1e
        /* This most likely means pools are running in debug mode, create our
Packit 3adb1e
         * own allocator to deal with memory ourselves */
Packit 3adb1e
        apr_allocator_create(&allocator->allocator);
Packit 3adb1e
        allocator->own_allocator = 1;
Packit 3adb1e
    }
Packit 3adb1e
    allocator->unfreed = unfreed;
Packit 3adb1e
    allocator->unfreed_baton = unfreed_baton;
Packit 3adb1e
Packit 3adb1e
#ifdef SERF_DEBUG_BUCKET_USE
Packit 3adb1e
    {
Packit 3adb1e
        track_state_t *track;
Packit 3adb1e
Packit 3adb1e
        track = allocator->track = apr_palloc(pool, sizeof(*allocator->track));
Packit 3adb1e
        track->next_index = 0;
Packit 3adb1e
        track->num_used = 0;
Packit 3adb1e
    }
Packit 3adb1e
#endif
Packit 3adb1e
Packit 3adb1e
    /* NOTE: On a fork/exec, the child won't bother cleaning up memory.
Packit 3adb1e
             This is just fine... the memory will go away at exec.
Packit 3adb1e
Packit 3adb1e
       NOTE: If the child will NOT perform an exec, then the parent or
Packit 3adb1e
             the child will need to decide who to clean up any
Packit 3adb1e
             outstanding connection/buckets (as appropriate).  */
Packit 3adb1e
    apr_pool_cleanup_register(pool, allocator,
Packit 3adb1e
                              allocator_cleanup, apr_pool_cleanup_null);
Packit 3adb1e
Packit 3adb1e
    return allocator;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
apr_pool_t *serf_bucket_allocator_get_pool(
Packit 3adb1e
    const serf_bucket_alloc_t *allocator)
Packit 3adb1e
{
Packit 3adb1e
    return allocator->pool;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
void *serf_bucket_mem_alloc(
Packit 3adb1e
    serf_bucket_alloc_t *allocator,
Packit 3adb1e
    apr_size_t size)
Packit 3adb1e
{
Packit 3adb1e
    node_header_t *node;
Packit 3adb1e
Packit 3adb1e
    ++allocator->num_alloc;
Packit 3adb1e
Packit 3adb1e
    size += SIZEOF_NODE_HEADER_T;
Packit 3adb1e
    if (size <= STANDARD_NODE_SIZE) {
Packit 3adb1e
        if (allocator->freelist) {
Packit 3adb1e
            /* just pull a node off our freelist */
Packit 3adb1e
            node = allocator->freelist;
Packit 3adb1e
            allocator->freelist = node->u.next;
Packit 3adb1e
#ifdef DEBUG_DOUBLE_FREE
Packit 3adb1e
            /* When we free an item, we set its size to zero. Thus, when
Packit 3adb1e
             * we return it to the caller, we must ensure the size is set
Packit 3adb1e
             * properly.
Packit 3adb1e
             */
Packit 3adb1e
            node->size = STANDARD_NODE_SIZE;
Packit 3adb1e
#endif
Packit 3adb1e
        }
Packit 3adb1e
        else {
Packit 3adb1e
            apr_memnode_t *active = allocator->blocks;
Packit 3adb1e
Packit 3adb1e
            if (active == NULL
Packit 3adb1e
                || active->first_avail + STANDARD_NODE_SIZE >= active->endp) {
Packit 3adb1e
                apr_memnode_t *head = allocator->blocks;
Packit 3adb1e
Packit 3adb1e
                /* ran out of room. grab another block. */
Packit 3adb1e
                active = apr_allocator_alloc(allocator->allocator, ALLOC_AMT);
Packit 3adb1e
Packit 3adb1e
                /* System couldn't provide us with memory. */
Packit 3adb1e
                if (active == NULL)
Packit 3adb1e
                    return NULL;
Packit 3adb1e
Packit 3adb1e
                /* link the block into our tracking list */
Packit 3adb1e
                allocator->blocks = active;
Packit 3adb1e
                active->next = head;
Packit 3adb1e
            }
Packit 3adb1e
Packit 3adb1e
            node = (node_header_t *)active->first_avail;
Packit 3adb1e
            node->size = STANDARD_NODE_SIZE;
Packit 3adb1e
            active->first_avail += STANDARD_NODE_SIZE;
Packit 3adb1e
        }
Packit 3adb1e
    }
Packit 3adb1e
    else {
Packit 3adb1e
        apr_memnode_t *memnode = apr_allocator_alloc(allocator->allocator,
Packit 3adb1e
                                                     size);
Packit 3adb1e
Packit 3adb1e
        if (memnode == NULL)
Packit 3adb1e
            return NULL;
Packit 3adb1e
Packit 3adb1e
        node = (node_header_t *)memnode->first_avail;
Packit 3adb1e
        node->u.memnode = memnode;
Packit 3adb1e
        node->size = size;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    return ((char *)node) + SIZEOF_NODE_HEADER_T;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
void *serf_bucket_mem_calloc(
Packit 3adb1e
    serf_bucket_alloc_t *allocator,
Packit 3adb1e
    apr_size_t size)
Packit 3adb1e
{
Packit 3adb1e
    void *mem;
Packit 3adb1e
    mem = serf_bucket_mem_alloc(allocator, size);
Packit 3adb1e
    if (mem == NULL)
Packit 3adb1e
        return NULL;
Packit 3adb1e
    memset(mem, 0, size);
Packit 3adb1e
    return mem;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
void serf_bucket_mem_free(
Packit 3adb1e
    serf_bucket_alloc_t *allocator,
Packit 3adb1e
    void *block)
Packit 3adb1e
{
Packit 3adb1e
    node_header_t *node;
Packit 3adb1e
Packit 3adb1e
    --allocator->num_alloc;
Packit 3adb1e
Packit 3adb1e
    node = (node_header_t *)((char *)block - SIZEOF_NODE_HEADER_T);
Packit 3adb1e
Packit 3adb1e
    if (node->size == STANDARD_NODE_SIZE) {
Packit 3adb1e
        /* put the node onto our free list */
Packit 3adb1e
        node->u.next = allocator->freelist;
Packit 3adb1e
        allocator->freelist = node;
Packit 3adb1e
Packit 3adb1e
#ifdef DEBUG_DOUBLE_FREE
Packit 3adb1e
        /* note that this thing was freed. */
Packit 3adb1e
        node->size = 0;
Packit 3adb1e
    }
Packit 3adb1e
    else if (node->size == 0) {
Packit 3adb1e
        /* damn thing was freed already. */
Packit 3adb1e
        abort();
Packit 3adb1e
#endif
Packit 3adb1e
    }
Packit 3adb1e
    else {
Packit 3adb1e
#ifdef DEBUG_DOUBLE_FREE
Packit 3adb1e
        /* note that this thing was freed. */
Packit 3adb1e
        node->size = 0;
Packit 3adb1e
#endif
Packit 3adb1e
Packit 3adb1e
        /* now free it */
Packit 3adb1e
        apr_allocator_free(allocator->allocator, node->u.memnode);
Packit 3adb1e
    }
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
/* ==================================================================== */
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
#ifdef SERF_DEBUG_BUCKET_USE
Packit 3adb1e
Packit 3adb1e
static read_status_t *find_read_status(
Packit 3adb1e
    track_state_t *track,
Packit 3adb1e
    const serf_bucket_t *bucket,
Packit 3adb1e
    int create_rs)
Packit 3adb1e
{
Packit 3adb1e
    read_status_t *rs;
Packit 3adb1e
Packit 3adb1e
    if (track->num_used) {
Packit 3adb1e
        int count = track->num_used;
Packit 3adb1e
        int idx = track->next_index;
Packit 3adb1e
Packit 3adb1e
        /* Search backwards. In all likelihood, the bucket which just got
Packit 3adb1e
         * read was read very recently.
Packit 3adb1e
         */
Packit 3adb1e
        while (count-- > 0) {
Packit 3adb1e
            if (!idx--) {
Packit 3adb1e
                /* assert: track->num_used == TRACK_BUCKET_COUNT */
Packit 3adb1e
                idx = track->num_used - 1;
Packit 3adb1e
            }
Packit 3adb1e
            if ((rs = &track->info[idx])->bucket == bucket) {
Packit 3adb1e
                return rs;
Packit 3adb1e
            }
Packit 3adb1e
        }
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* Only create a new read_status_t when asked. */
Packit 3adb1e
    if (!create_rs)
Packit 3adb1e
        return NULL;
Packit 3adb1e
Packit 3adb1e
    if (track->num_used < TRACK_BUCKET_COUNT) {
Packit 3adb1e
        /* We're still filling up the ring. */
Packit 3adb1e
        ++track->num_used;
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    rs = &track->info[track->next_index];
Packit 3adb1e
    rs->bucket = bucket;
Packit 3adb1e
    rs->last = APR_SUCCESS;     /* ### the right initial value? */
Packit 3adb1e
Packit 3adb1e
    if (++track->next_index == TRACK_BUCKET_COUNT)
Packit 3adb1e
        track->next_index = 0;
Packit 3adb1e
Packit 3adb1e
    return rs;
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
#endif /* SERF_DEBUG_BUCKET_USE */
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
apr_status_t serf_debug__record_read(
Packit 3adb1e
    const serf_bucket_t *bucket,
Packit 3adb1e
    apr_status_t status)
Packit 3adb1e
{
Packit 3adb1e
#ifndef SERF_DEBUG_BUCKET_USE
Packit 3adb1e
    return status;
Packit 3adb1e
#else
Packit 3adb1e
Packit 3adb1e
    track_state_t *track = bucket->allocator->track;
Packit 3adb1e
    read_status_t *rs = find_read_status(track, bucket, 1);
Packit 3adb1e
Packit 3adb1e
    /* Validate that the previous status value allowed for another read. */
Packit 3adb1e
    if (APR_STATUS_IS_EAGAIN(rs->last) /* ### or APR_EOF? */) {
Packit 3adb1e
        /* Somebody read when they weren't supposed to. Bail. */
Packit 3adb1e
        abort();
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* Save the current status for later. */
Packit 3adb1e
    rs->last = status;
Packit 3adb1e
Packit 3adb1e
    return status;
Packit 3adb1e
#endif
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
void serf_debug__entered_loop(serf_bucket_alloc_t *allocator)
Packit 3adb1e
{
Packit 3adb1e
#ifdef SERF_DEBUG_BUCKET_USE
Packit 3adb1e
Packit 3adb1e
    track_state_t *track = allocator->track;
Packit 3adb1e
    read_status_t *rs = &track->info[0];
Packit 3adb1e
Packit 3adb1e
    for ( ; track->num_used; --track->num_used, ++rs ) {
Packit 3adb1e
        if (rs->last == APR_SUCCESS) {
Packit 3adb1e
            /* Somebody should have read this bucket again. */
Packit 3adb1e
            abort();
Packit 3adb1e
        }
Packit 3adb1e
Packit 3adb1e
        /* ### other status values? */
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
    /* num_used was reset. also need to reset the next index. */
Packit 3adb1e
    track->next_index = 0;
Packit 3adb1e
Packit 3adb1e
#endif
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
void serf_debug__closed_conn(serf_bucket_alloc_t *allocator)
Packit 3adb1e
{
Packit 3adb1e
#ifdef SERF_DEBUG_BUCKET_USE
Packit 3adb1e
Packit 3adb1e
    /* Just reset the number used so that we don't examine the info[] */
Packit 3adb1e
    allocator->track->num_used = 0;
Packit 3adb1e
    allocator->track->next_index = 0;
Packit 3adb1e
Packit 3adb1e
#endif
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
void serf_debug__bucket_destroy(const serf_bucket_t *bucket)
Packit 3adb1e
{
Packit 3adb1e
#ifdef SERF_DEBUG_BUCKET_USE
Packit 3adb1e
Packit 3adb1e
    track_state_t *track = bucket->allocator->track;
Packit 3adb1e
    read_status_t *rs = find_read_status(track, bucket, 0);
Packit 3adb1e
Packit 3adb1e
    if (rs != NULL && rs->last != APR_EOF) {
Packit 3adb1e
        /* The bucket was destroyed before it was read to completion. */
Packit 3adb1e
Packit 3adb1e
        /* Special exception for socket buckets. If a connection remains
Packit 3adb1e
         * open, they are not read to completion.
Packit 3adb1e
         */
Packit 3adb1e
        if (SERF_BUCKET_IS_SOCKET(bucket))
Packit 3adb1e
            return;
Packit 3adb1e
Packit 3adb1e
        /* Ditto for SSL Decrypt buckets. */
Packit 3adb1e
        if (SERF_BUCKET_IS_SSL_DECRYPT(bucket))
Packit 3adb1e
            return;
Packit 3adb1e
Packit 3adb1e
        /* Ditto for SSL Encrypt buckets. */
Packit 3adb1e
        if (SERF_BUCKET_IS_SSL_ENCRYPT(bucket))
Packit 3adb1e
            return;
Packit 3adb1e
Packit 3adb1e
        /* Ditto for barrier buckets. */
Packit 3adb1e
        if (SERF_BUCKET_IS_BARRIER(bucket))
Packit 3adb1e
            return;
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
        abort();
Packit 3adb1e
    }
Packit 3adb1e
Packit 3adb1e
#endif
Packit 3adb1e
}
Packit 3adb1e
Packit 3adb1e
Packit 3adb1e
void serf_debug__bucket_alloc_check(
Packit 3adb1e
    serf_bucket_alloc_t *allocator)
Packit 3adb1e
{
Packit 3adb1e
#ifdef SERF_DEBUG_BUCKET_USE
Packit 3adb1e
    if (allocator->num_alloc != 0) {
Packit 3adb1e
        abort();
Packit 3adb1e
    }
Packit 3adb1e
#endif
Packit 3adb1e
}
Packit 3adb1e