src/server/util/pool.c

Wed, 27 Nov 2024 23:00:07 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 27 Nov 2024 23:00:07 +0100
changeset 563
6ca97c99173e
parent 415
d938228c382e
permissions
-rw-r--r--

add TODO to use a future ucx feature

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
 *
 * THE BSD LICENSE
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution. 
 *
 * Neither the name of the  nor the names of its contributors may be
 * used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Generic pool handling routines.
 *
 * These routines reduce contention on the heap and guard against
 * memory leaks.
 *
 * Thread warning:
 *     This implementation is thread safe.  However, simultaneous 
 *     mallocs/frees to the same "pool" are not safe.  Do not share
 *     pools across multiple threads without providing your own
 *     synchronization.
 *
 * Mike Belshe
 * 11-20-95
 *
 */

//include "netsite.h"
//include "systems.h"
#include "systhr.h"
#include "pool_pvt.h"
//include "ereport.h"
//include "base/session.h"
//include "frame/req.h"
//include "frame/http.h"
#include "util.h"
//include "base/crit.h"

//include "base/dbtbase.h"



#include <stdlib.h>
#include <string.h>
#include <limits.h>
//define PERM_MALLOC   malloc
//define PERM_FREE     free
//define PERM_REALLOC  realloc
//define PERM_CALLOC   calloc
//define PERM_STRDUP   strdup

/* Pool configuration parameters */
static pool_config_t pool_config = POOL_CONFIG_INIT;

/* Pool global statistics */
static pool_global_stats_t pool_global_stats;

/* ucx allocator pool class */
static cx_allocator_class pool_allocator_class = {
    (void *(*)(void *,size_t )) pool_malloc,
    (void *(*)(void *,void *, size_t )) pool_realloc,
    (void *(*)(void *,size_t ,size_t  )) pool_calloc,
    (void (*)(void *, void *))pool_free
};

static int 
pool_internal_init()
{
    //if (pool_global_stats.lock == NULL) {
    //    pool_global_stats.lock = PR_NewLock(); // TODO: remove
    //}

    if (pool_config.block_size == 0) {
        //ereport(LOG_INFORM, XP_GetAdminStr(DBT_poolInitInternalAllocatorDisabled_));
    }

    return 0;
}

#define POOL_MIN_BLOCKSIZE 128

NSAPI_PUBLIC int
pool_init(pblock *pb, Session *sn, Request *rq)
{
    //char *str_block_size = pblock_findval("block-size", pb);
    //char *str_pool_disable = pblock_findval("disable", pb);
    char *str_block_size = "16384";
    char *str_pool_disable = "false";
    int n;

    //printf("standard block size: %d\n", pool_config.block_size);
    
    if (str_block_size != NULL) {
        int64_t value;
        if(!util_strtoint(str_block_size, &value)) {
            log_ereport(LOG_MISCONFIG, "pool-init: param 'block-size' is not an integer");
            return REQ_ABORTED;
        }
        if(value > INT_MAX) {
            log_ereport(LOG_MISCONFIG, "pool-init: block-size is too big");
            return REQ_ABORTED;
        }
        if(value < POOL_MIN_BLOCKSIZE) {
            log_ereport(LOG_MISCONFIG, "pool-init: block-size is too small");
            return REQ_ABORTED;
        }
        pool_config.block_size = value;
    }

    if (str_pool_disable && util_getboolean(str_pool_disable, PR_TRUE)) {
        /* We'll call PERM_MALLOC() on each pool_malloc() call */
        pool_config.block_size = 0;
        pool_config.retain_size = 0;
        pool_config.retain_num = 0;
    }

    pool_internal_init();

    return REQ_PROCEED;
}

CxAllocator* pool_allocator(pool_handle_t *pool) {
    return &((pool_t *)pool)->allocator;
}

static block_t *
_create_block(pool_t *pool, int size)
{
    block_t *newblock;
    char *newdata;
    block_t **blk_ptr;
    long blen;

    /* Does the pool have any retained blocks on its free list? */
    for (blk_ptr = &pool->free_blocks;
         (newblock = *blk_ptr) != NULL; blk_ptr = &newblock->next) {

        /* Yes, is this block large enough? */
        blen = newblock->end - newblock->data;
        if (blen >= size) {

            /* Yes, take it off the free list */
            *blk_ptr = newblock->next;
            pool->free_size -= blen;
            --pool->free_num;

            /* Give the block to the caller */
            newblock->start = newblock->data;
            goto done;
        }
    }

    newblock = (block_t *)PERM_MALLOC(sizeof(block_t));
    newdata = (char *)PERM_MALLOC(size);
    if (newblock == NULL || (newdata == NULL && size != 0)) {
        //ereport(LOG_CATASTROPHE,
        //        XP_GetAdminStr(DBT_poolCreateBlockOutOfMemory_));
        PERM_FREE(newblock);
        PERM_FREE(newdata);
        //PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        return NULL;
    }
    newblock->data = newdata;
    newblock->start = newblock->data;
    newblock->end   = newblock->data + size;
    newblock->next  = NULL;
    blen = size;

#ifdef POOL_GLOBAL_STATISTICS
    PR_AtomicIncrement((PRInt32 *)&pool_global_stats.blkAlloc);
#endif /* POOL_GLOBAL_STATISTICS */

  done:

#ifdef PER_POOL_STATISTICS
    ++pool->stats.blkAlloc;
#endif /* PER_POOL_STATISTICS */

    return newblock;
}

static void 
_free_block(block_t *block)
{
#ifdef POOL_ZERO_DEBUG
    long blen = block->end - block->data;
    memset(block->data, POOL_ZERO_DEBUG, blen);
#endif /* POOL_ZERO_DEBUG */

    PERM_FREE(block->data);

#ifdef POOL_ZERO_DEBUG
    memset(block, POOL_ZERO_DEBUG, sizeof(block));
#endif /* POOL_ZERO_DEBUG */

    PERM_FREE(block);

#ifdef POOL_GLOBAL_STATISTICS
    PR_AtomicIncrement((PRInt32 *)&pool_global_stats.blkFree);
#endif /* POOL_GLOBAL_STATISTICS */
}

/* ptr_in_pool()
 * Checks to see if the given pointer is in the given pool.
 * If true, returns a ptr to the block_t containing the ptr;
 * otherwise returns NULL
 */
block_t * 
_ptr_in_pool(pool_t *pool, const void *ptr)
{
    block_t *block_ptr = NULL;

    /* try to find a block which contains this ptr */

    if (POOL_PTR_IN_BLOCK(pool->curr_block, ptr)) {
        block_ptr = pool->curr_block;
    }
    else {
        for (block_ptr = pool->used_blocks; block_ptr; block_ptr = block_ptr->next) {
            if (POOL_PTR_IN_BLOCK(block_ptr, ptr))
                break;
        }
    }
    return block_ptr;
}


NSAPI_PUBLIC pool_handle_t *
pool_create()
{
    pool_t *newpool;

    newpool = (pool_t *)PERM_MALLOC(sizeof(pool_t));

    if (newpool) {
        /* Have to initialize now, as pools get created sometimes
         * before pool_init can be called...
         */
        //if (pool_global_stats.lock == NULL) { // TODO: remove
        //    pool_internal_init();
        //}
        
        newpool->allocator.cl = &pool_allocator_class;
        newpool->allocator.data = newpool;
        
        newpool->used_blocks = NULL;
        newpool->free_blocks = NULL;
        newpool->free_size = 0;
        newpool->free_num = 0;
        newpool->size = 0;
        newpool->next = NULL;

#ifdef PER_POOL_STATISTICS
        /* Initial per pool statistics */
        memset((void *)(&newpool->stats), 0, sizeof(newpool->stats));
        newpool->stats.thread = PR_GetCurrentThread();
        newpool->stats.created = PR_Now();
#endif /* PER_POOL_STATISTICS */

        /* No need to lock, since pool has not been exposed yet */
        newpool->curr_block =_create_block(newpool, pool_config.block_size);
        if (newpool->curr_block == NULL) {
            //ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolCreateOutOfMemory_));
            pool_destroy((pool_handle_t *)newpool);
            //PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
            return NULL;
        }

        /* Add to known pools list */
        
        // NOTICE:
        // known pools list removed
        
        //PR_Lock(pool_global_stats.lock);
        //newpool->next = pool_global_stats.poolList;
        //pool_global_stats.poolList = newpool;
        //++pool_global_stats.createCnt;
#ifdef PER_POOL_STATISTICS
        newpool->stats.poolId = pool_global_stats.createCnt;
#endif /* PER_POOL_STATISTICS */
        //PR_Unlock(pool_global_stats.lock);

    }
    else {
        //ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolCreateOutOfMemory_1));
        //PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    }

    return (pool_handle_t *)newpool;
}

/*
 * pool_mark - get mark for subsequent recycle
 *
 * This function returns a value that can be used to free all pool
 * memory which is subsequently allocated, without freeing memory
 * that has already been allocated when pool_mark() is called.
 * The pool_recycle() function is used to free the memory allocated
 * since pool_mark() was called.
 *
 * This function may be called several times before pool_recycle()
 * is called, but some care must be taken not to pass an invalid
 * mark value to pool_recycle(), which would cause all pool memory
 * to be freed.  A mark value becomes invalid when pool_recycle is
 * called with a previously returned mark value.
 */
NSAPI_PUBLIC void *
pool_mark(pool_handle_t *pool_handle)
{
    pool_t *pool = (pool_t *)pool_handle;

    //PR_ASSERT(pool != NULL);

    if (pool == NULL)
        return NULL;

#ifdef PER_POOL_STATISTICS
    pool->stats.thread = PR_GetCurrentThread();
#endif /* PER_POOL_STATISTICS */

    /* Never return end as it points outside the block */
    if (pool->curr_block->start == pool->curr_block->end)
        return pool->curr_block;

    return (void *)(pool->curr_block->start);
}

/*
 * pool_recycle - recycle memory in a pool
 *
 * This function returns all the allocated memory for a pool back to
 * a free list associated with the pool. It is like pool_destroy() in
 * the sense that all data structures previously allocated from the
 * pool are freed, but it keeps the memory associated with the pool,
 * and doesn't actually destroy the pool.
 *
 * The "mark" argument can be a value previously returned by
 * pool_mark(), in which case the pool is returned to the state it
 * was in when pool_mark() was called, or it can be NULL, in which
 * case the pool is completely recycled.
 */
NSAPI_PUBLIC void
pool_recycle(pool_handle_t *pool_handle, void *mark)
{
    pool_t *pool = (pool_t *)pool_handle;
    block_t *tmp_blk;
    unsigned long blen;

    //PR_ASSERT(pool != NULL);

    if (pool == NULL)
        return;

    /* Fix up curr_block.  There should always be a curr_block. */
    tmp_blk = pool->curr_block;
    //PR_ASSERT(tmp_blk != NULL);

    /* Start with curr_block, then scan blocks on used_blocks list */
    for (;;) {

        /* Check if the mark is at the end of this block */
        if (tmp_blk == mark) {
            pool->curr_block = tmp_blk;
            break;
        }

        /* Look for a block containing the mark */
        if (POOL_PTR_IN_BLOCK(tmp_blk, mark)) {

            /* Reset block start pointer to marked spot */
            if (tmp_blk == pool->curr_block) {
                blen = tmp_blk->start - (char *)mark;
            } else {
                blen = tmp_blk->end - (char *)mark;
            }
            pool->size -= blen;
            //PR_ASSERT(pool->size >= 0);
            tmp_blk->start = (char *)mark;
            pool->curr_block = tmp_blk;
            break;
        }

        /* Reset block start pointer to base of block */
        if (tmp_blk == pool->curr_block) {
            /* Count just the allocated length in the current block */
            blen = tmp_blk->start - tmp_blk->data;
        }
        else {
            /* Count the entire size of a used_block */
            blen = tmp_blk->end - tmp_blk->data;
        }
        tmp_blk->start = tmp_blk->data;
        pool->size -= blen;
        //PR_ASSERT(pool->size >= 0);

        /*
         * If there are no more used blocks after this one, then set
         * this block up as the current block and return.
         */
        if (pool->used_blocks == NULL) {
            //PR_ASSERT(mark == NULL);
            pool->curr_block = tmp_blk;
            break;
        }

        /* Otherwise free this block one way or another */

        /* Add block length to total retained length and check limit */
        if ((pool->free_size + blen) <= pool_config.retain_size &&
            pool->free_num < pool_config.retain_num) {

            /* Retain block on pool free list */
            /*
             * XXX hep - could sort blocks on free list in order
             * ascending size to get "best fit" allocation in
             * _create_block(), but the default block size is large
             * enough that fit should rarely be an issue.
             */
            tmp_blk->next = pool->free_blocks;
            pool->free_blocks = tmp_blk;
            pool->free_size += blen;
            ++pool->free_num;
        }
        else {
            /* Limit exceeded - free block */
            _free_block(tmp_blk);
        }

#ifdef PER_POOL_STATISTICS
        //++pool->stats.blkFree;
#endif /* PER_POOL_STATISTICS */

        /* Remove next block from used blocks list */
        tmp_blk = pool->used_blocks;
        pool->used_blocks = tmp_blk->next;
    }
}

NSAPI_PUBLIC void
pool_destroy(pool_handle_t *pool_handle)
{
    pool_t *pool = (pool_t *)pool_handle;
    block_t *tmp_blk;

    //PR_ASSERT(pool != NULL);

    if (pool == NULL)
        return;

    if (pool->curr_block)
        _free_block(pool->curr_block);

    while(pool->used_blocks) {
        tmp_blk = pool->used_blocks;
        pool->used_blocks = tmp_blk->next;
        _free_block(tmp_blk);
    }

    while(pool->free_blocks) {
        tmp_blk = pool->free_blocks;
        pool->free_blocks = tmp_blk->next;
        _free_block(tmp_blk);
    }

    {
        //pool_t **ppool;

        /* Remove from the known pools list */
        // NOTICE: known pools list removed
        /*
        PR_Lock(pool_global_stats.lock);
        for (ppool = &pool_global_stats.poolList;
             *ppool; ppool = &(*ppool)->next) {
            if (*ppool == pool) {
                ++pool_global_stats.destroyCnt;
                *ppool = pool->next;
                break;
            }
        }
        PR_Unlock(pool_global_stats.lock);
        */
    }

#ifdef POOL_ZERO_DEBUG
    memset(pool, POOL_ZERO_DEBUG, sizeof(pool));
#endif /* POOL_ZERO_DEBUG */

    PERM_FREE(pool);

    return;
}


NSAPI_PUBLIC void *
pool_malloc(pool_handle_t *pool_handle, size_t size)
{
    pool_t *pool = (pool_t *)pool_handle;
    block_t *curr_block;
    long reqsize, blocksize;
    char *ptr;

    if (pool == NULL)
        return PERM_MALLOC(size);

    reqsize = ALIGN(size);
    if (reqsize == 0) {
        /* Assign a unique address to each 0-byte allocation */
        reqsize = WORD_SIZE;
    }

    curr_block = pool->curr_block;
    ptr = curr_block->start;
    curr_block->start += reqsize;

    /* does this fit into the last allocated block? */
    if (curr_block->start > curr_block->end) {

        /* Did not fit; time to allocate a new block */

        curr_block->start -= reqsize;  /* keep structs in tact */

        /* Count unallocated bytes in current block in pool size */
        pool->size += curr_block->end - curr_block->start;
        //PR_ASSERT(pool->size >= 0);
#ifdef PER_POOL_STATISTICS
        if (pool->size > pool->stats.maxAlloc) {
            pool->stats.maxAlloc = pool->size;
        }
#endif /* PER_POOL_STATISTICS */

        /* Move current block to used block list */
        curr_block->next = pool->used_blocks;
        pool->used_blocks = curr_block;

        /* Allocate a chunk of memory which is at least block_size bytes */
        blocksize = reqsize;
        if (blocksize < pool_config.block_size)
            blocksize = pool_config.block_size;

        curr_block = _create_block(pool, blocksize);
        pool->curr_block = curr_block;

        if (curr_block == NULL) {
            //ereport(LOG_CATASTROPHE,
            //        XP_GetAdminStr(DBT_poolMallocOutOfMemory_));
            //PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
            return NULL;
        }

        ptr = curr_block->start;
        curr_block->start += reqsize;
    }

    pool->size += reqsize;
    //PR_ASSERT(pool->size >= 0);

#ifdef PER_POOL_STATISTICS
    if (pool->size > pool->stats.maxAlloc) {
        pool->stats.maxAlloc = pool->size;
    }
    ++pool->stats.allocCnt;
    pool->stats.thread = PR_GetCurrentThread();
#endif /* PER_POOL_STATISTICS */

    return ptr;
}

NSAPI_PUBLIC void
pool_free(pool_handle_t *pool_handle, void *ptr)
{
    pool_t *pool = (pool_t *)pool_handle;

    if (ptr == NULL)
        return;

    if (pool == NULL) {
        PERM_FREE(ptr);
        return;
    }

    //PR_ASSERT(_ptr_in_pool(pool, ptr));

#ifdef PER_POOL_STATISTICS

    ++pool->stats.freeCnt;
    pool->stats.thread = PR_GetCurrentThread();

#endif /* PER_POOL_STATISTICS */

    return;
}

NSAPI_PUBLIC void *
pool_calloc(pool_handle_t *pool_handle, size_t nelem, size_t elsize)
{
    void *ptr;

    if (pool_handle == NULL)
        return calloc(1, elsize * nelem);

    ptr = pool_malloc(pool_handle, elsize * nelem);
    if (ptr)
        memset(ptr, 0, elsize * nelem);
    return ptr;
}

NSAPI_PUBLIC void *
pool_realloc(pool_handle_t *pool_handle, void *ptr, size_t size)
{
    pool_t *pool = (pool_t *)pool_handle;
    void *newptr;
    block_t *block_ptr;
    size_t oldsize;

    if (pool == NULL)
        return PERM_REALLOC(ptr, size);

    if ( (newptr = pool_malloc(pool_handle, size)) == NULL) 
        return NULL;

    /* With our structure we don't know exactly where the end
     * of the original block is.  But we do know an upper bound
     * which is a valid ptr.  Search the outstanding blocks
     * for the block which contains this ptr, and copy...
     */

    if ( !(block_ptr = _ptr_in_pool(pool, ptr)) ) {
        /* User is trying to realloc nonmalloc'd space! */
        return newptr;
    }

    oldsize = block_ptr->end - (char *)ptr ;
    if (oldsize > size)
        oldsize = size;
    memmove((char *)newptr, (char *)ptr, oldsize);

    return newptr;
}

NSAPI_PUBLIC char *
pool_strdup(pool_handle_t *pool_handle, const char *orig_str)
{
    char *new_str;
    int len = strlen(orig_str);

    if (pool_handle == NULL)
        return PERM_STRDUP(orig_str);

    new_str = (char *)pool_malloc(pool_handle, len+1);

    if (new_str) 
        memcpy(new_str, orig_str, len+1);

    return new_str;
}

NSAPI_PUBLIC long
pool_space(pool_handle_t *pool_handle)
{
    pool_t *pool = (pool_t *)pool_handle;

    return pool->size;
}

NSAPI_PUBLIC int pool_enabled()
{
    if (getThreadMallocKey() == -1)
        return 0;

    if (!systhread_getdata(getThreadMallocKey()))
        return 0;

    return 1;
}

#ifdef DEBUG
NSAPI_PUBLIC void INTpool_assert(pool_handle_t *pool_handle, const void *ptr)
{
    pool_t *pool = (pool_t *)pool_handle;

    if (pool == NULL)
        return;

    //PR_ASSERT(_ptr_in_pool(pool, ptr));
}
#endif

NSAPI_PUBLIC pool_config_t *pool_getConfig(void)
{
    return &pool_config;
}

#ifdef POOL_GLOBAL_STATISTICS
NSAPI_PUBLIC pool_global_stats_t *pool_getGlobalStats(void)
{
    return &pool_global_stats;
}
#endif /* POOL_GLOBAL_STATISTICS */

#ifdef PER_POOL_STATISTICS
NSAPI_PUBLIC pool_stats_t *pool_getPoolStats(pool_handle_t *pool_handle)
{
    pool_t *pool = (pool_t *)pool_handle;

    if (pool == NULL)
        return NULL;

    return &pool->stats;
}
#endif /* PER_POOL_STATISTICS */

// new
cxmutstr cx_strdup_pool(pool_handle_t *pool, cxmutstr s) {
    cxmutstr newstring;
    newstring.ptr = (char*)pool_malloc(pool, s.length + 1);
    if (newstring.ptr != NULL) {
        newstring.length = s.length;
        newstring.ptr[newstring.length] = 0;

        memcpy(newstring.ptr, s.ptr, s.length);
    }

    return newstring;
}

mercurial