/** BEGIN COPYRIGHT BLOCK * Copyright 2001 Sun Microsystems, Inc. * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. * All rights reserved. * END COPYRIGHT BLOCK **/ /* * Generic pool handling routines. * * Hopefully these reduce the number of malloc/free calls. * * * Thread warning: * This implementation is thread safe. However, simultaneous * mallocs/frees to the same "pool" are not safe. If you wish to * use this module across multiple threads, you should define * POOL_LOCKING which will make the malloc pools safe. * * Mike Belshe * 11-20-95 * */ #include "netsite.h" #include "base/systems.h" #include "base/systhr.h" #ifdef MALLOC_POOLS #include "base/pool.h" #include "base/ereport.h" #include "base/session.h" #include "frame/req.h" #include "frame/http.h" #include "base/util.h" #include "base/crit.h" #include "base/dbtbase.h" #ifdef DEBUG #define POOL_ZERO_DEBUG #endif #undef POOL_LOCKING #define BLOCK_SIZE (32 * 1024) #define MAX_FREELIST_SIZE (BLOCK_SIZE * 32) /* WORD SIZE 8 sets us up for 8 byte alignment. */ #define WORD_SIZE 8 #undef ALIGN #define ALIGN(x) ( (x + WORD_SIZE-1) & (~(WORD_SIZE-1)) ) /* block_t * When the user allocates space, a BLOCK_SIZE (or larger) block is created in * the pool. This block is used until all the space is eaten within it. * When all the space is gone, a new block is created. * */ typedef struct block_t { char *data; /* the real alloc'd space */ char *start; /* first free byte in block */ char *end; /* ptr to end of block */ struct block_t *next; /* ptr to next block */ } block_t; /* pool_t * A pool is a collection of blocks. The blocks consist of multiple * allocations of memory, but a single allocation cannot be freed by * itself. Once the memory is allocated it is allocated until the * entire pool is freed. */ typedef struct pool_t { #ifdef DEBUG_CACHES time_t time_created; #endif #ifdef POOL_LOCKING CRITICAL lock; /* lock for modifying the pool */ #endif block_t *curr_block; /* current block being used */ block_t *used_blocks; /* blocks that are all used up */ long size; /* size of memory in pool */ struct pool_t *next; /* known_pools list */ } pool_t; /* known_pools * Primarily for debugging, keep a list of all active malloc pools. */ static pool_t *known_pools = NULL; static CRITICAL known_pools_lock = NULL; static unsigned long pool_blocks_created = 0; static unsigned long pool_blocks_freed = 0; /* freelist * Internally we maintain a list of free blocks which we try to pull from * whenever possible. This list will never have more than MAX_FREELIST_SIZE * bytes within it. */ static CRITICAL freelist_lock = NULL; static block_t *freelist = NULL; static unsigned long freelist_size = 0; static unsigned long freelist_max = MAX_FREELIST_SIZE; static int pool_disable = 0; int pool_internal_init() { if (pool_disable == 0) { if (known_pools_lock == NULL) { known_pools_lock = crit_init(); freelist_lock = crit_init(); } } else ereport(LOG_INFORM, XP_GetAdminStr(DBT_poolInitMemoryPoolsDisabled_)); return 0; } NSAPI_PUBLIC int pool_init(pblock *pb, Session *sn, Request *rq) { char *str_free_size = pblock_findval("free-size", pb); char *str_pool_disable = pblock_findval("disable", pb); if (str_free_size != NULL) { if ( (freelist_max = atoi(str_free_size)) <= 0) { ereport(LOG_WARN, XP_GetAdminStr(DBT_poolInitFreeSize0UsingD_), MAX_FREELIST_SIZE); freelist_max = MAX_FREELIST_SIZE; } } if (str_pool_disable && strcasecmp(str_pool_disable, "false") ) pool_disable = 1; else pool_disable = 0; if (known_pools_lock == NULL) { known_pools_lock = crit_init(); freelist_lock = crit_init(); } return REQ_PROCEED; } static block_t * _create_block(int size) { block_t *newblock = NULL; long bytes = ALIGN(size); block_t *free_ptr, *last_free_ptr = NULL; /* check freelist for large enough block first */ crit_enter(freelist_lock); free_ptr = freelist; while(free_ptr && ((free_ptr->end - free_ptr->data) < bytes)) { last_free_ptr = free_ptr; free_ptr = free_ptr->next; } if (free_ptr) { newblock = free_ptr; if (last_free_ptr) last_free_ptr->next = free_ptr->next; else freelist = free_ptr->next; freelist_size -= (newblock->end - newblock->data); crit_exit(freelist_lock); bytes = free_ptr->end - free_ptr->data; } else { pool_blocks_created++; crit_exit(freelist_lock); if (((newblock = (block_t *)PERM_MALLOC(sizeof(block_t))) == NULL) || ((newblock->data = (char *)PERM_MALLOC(bytes)) == NULL)) { ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolCreateBlockOutOfMemory_)); if (newblock) PERM_FREE(newblock); return NULL; } } newblock->start = newblock->data; newblock->end = newblock->data + bytes; newblock->next = NULL; return newblock; } /* Caller must hold lock for the pool */ static void _free_block(block_t *block) { #ifdef POOL_ZERO_DEBUG memset(block->data, 0xa, block->end-block->data); #endif /* POOL_ZERO_DEBUG */ if ((freelist_size + block->end - block->data) > freelist_max) { /* Just have to delete the whole block! */ crit_enter(freelist_lock); pool_blocks_freed++; crit_exit(freelist_lock); PERM_FREE(block->data); #ifdef POOL_ZERO_DEBUG memset(block, 0xa, sizeof(block)); #endif /* POOL_ZERO_DEBUG */ PERM_FREE(block); return; } crit_enter(freelist_lock); freelist_size += (block->end - block->data); block->start = block->data; block->next = freelist; freelist = block; crit_exit(freelist_lock); } /* 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, void *ptr) { block_t *block_ptr = NULL; /* try to find a block which contains this ptr */ if ( ((char *)ptr < (char *)pool->curr_block->end) && ((char *)ptr >= (char *)pool->curr_block->data) ) block_ptr = pool->curr_block; else for( block_ptr = pool->used_blocks; block_ptr && (((char *)ptr >= (char *)block_ptr->end) && ((char *)ptr < (char *)block_ptr->data)); block_ptr = block_ptr->next); return block_ptr; } NSAPI_PUBLIC pool_handle_t * pool_create() { pool_t *newpool; if (pool_disable) return NULL; 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 (known_pools_lock == NULL) { known_pools_lock = crit_init(); freelist_lock = crit_init(); } if ( (newpool->curr_block =_create_block(BLOCK_SIZE)) == NULL) { ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolCreateOutOfMemory_)); PERM_FREE(newpool); return NULL; } newpool->used_blocks = NULL; newpool->size = 0; newpool->next = NULL; #ifdef POOL_LOCKING newpool->lock = crit_init(); #endif #ifdef DEBUG_CACHES newpool->time_created = time(NULL); #endif /* Add to known pools list */ crit_enter(known_pools_lock); newpool->next = known_pools; known_pools = newpool; crit_exit(known_pools_lock); } else ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolCreateOutOfMemory_1)); return (pool_handle_t *)newpool; } NSAPI_PUBLIC void pool_destroy(pool_handle_t *pool_handle) { pool_t *pool = (pool_t *)pool_handle; block_t *tmp_blk; pool_t *last, *search; if (pool_disable) return; crit_enter(known_pools_lock); #ifdef POOL_LOCKING crit_enter(pool->lock); #endif if (pool->curr_block) _free_block(pool->curr_block); while(pool->used_blocks) { tmp_blk = pool->used_blocks; pool->used_blocks = pool->used_blocks->next; _free_block(tmp_blk); } /* Remove from the known pools list */ for (last = NULL, search = known_pools; search; last = search, search = search->next) if (search == pool) break; if (search) { if(last) last->next = search->next; else known_pools = search->next; } #ifdef POOL_LOCKING crit_exit(pool->lock); crit_terminate(pool->lock); #endif crit_exit(known_pools_lock); #ifdef POOL_ZERO_DEBUG memset(pool, 0xa, 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; long reqsize, blocksize; char *ptr; if (pool == NULL || pool_disable) { return PERM_MALLOC(size); } #ifdef DEBUG if (size == 0) return NULL; #endif #ifdef POOL_LOCKING crit_enter(pool->lock); #endif reqsize = ALIGN(size); ptr = pool->curr_block->start; pool->curr_block->start += reqsize; /* does this fit into the last allocated block? */ if (pool->curr_block->start > pool->curr_block->end) { /* Did not fit; time to allocate a new block */ pool->curr_block->start -= reqsize; /* keep structs in tact */ pool->curr_block->next = pool->used_blocks; pool->used_blocks = pool->curr_block; /* Allocate a chunk of memory which is a multiple of BLOCK_SIZE * bytes */ blocksize = ( (size + BLOCK_SIZE-1) / BLOCK_SIZE ) * BLOCK_SIZE; if ( (pool->curr_block = _create_block(blocksize)) == NULL) { ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolMallocOutOfMemory_)); #ifdef POOL_LOCKING crit_exit(pool->lock); #endif return NULL; } ptr = pool->curr_block->start; reqsize = ALIGN(size); pool->curr_block->start += reqsize; } pool->size += reqsize; #ifdef POOL_LOCKING crit_exit(pool->lock); #endif return ptr; } void _pool_free_error() { ereport(LOG_WARN, XP_GetAdminStr(DBT_freeUsedWherePermFreeShouldHaveB_)); return; } NSAPI_PUBLIC void pool_free(pool_handle_t *pool_handle, void *ptr) { if (pool_handle == NULL || pool_disable) { PERM_FREE(ptr); return; } #ifdef DEBUG /* Just to be nice, check to see if the ptr was allocated in a pool. * If not, issue a warning and do a REAL free just to make sure that * we don't leak memory. */ if ( !_ptr_in_pool((pool_t *)pool_handle, ptr) ) { _pool_free_error(); PERM_FREE(ptr); } #endif return; } NSAPI_PUBLIC void * pool_calloc(pool_handle_t *pool_handle, size_t nelem, size_t elsize) { void *ptr; if (pool_handle == NULL || pool_disable) return PERM_CALLOC(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; int oldsize; if (pool_handle == NULL || pool_disable) 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... */ #ifdef POOL_LOCKING crit_enter(pool->lock); #endif 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); #ifdef POOL_LOCKING crit_exit(pool->lock); #endif 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 || pool_disable) 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() { #ifndef THREAD_ANY /* we don't have USE_NSPR defined so systhread_getdata is undef'ed */ return 0; #else if (pool_disable || (getThreadMallocKey() == -1) ) return 0; if (!systhread_getdata(getThreadMallocKey())) return 0; return 1; #endif } /* pool_service_debug() * NSAPI service routine to print state information about the existing * pools. Hopefully useful in debugging. * */ #define MAX_DEBUG_LINE 1024 #ifdef DEBUG_CACHES /* XXXrobm causes entanglement in install and admserv cgis */ NSAPI_PUBLIC int pool_service_debug(pblock *pb, Session *sn, Request *rq) { char tmp_buf[MAX_DEBUG_LINE]; char cbuf[DEF_CTIMEBUF]; int len; pool_t *pool_ptr; block_t *block_ptr; int pool_cnt, block_cnt; param_free(pblock_remove("content-type", rq->srvhdrs)); pblock_nvinsert("content-type", "text/html", rq->srvhdrs); protocol_status(sn, rq, PROTOCOL_OK, NULL); protocol_start_response(sn, rq); len = util_sprintf(tmp_buf, "
\n"); net_write(sn->csd, tmp_buf, len); len = util_sprintf(tmp_buf, "Freelist size: %d/%d
", freelist_size, freelist_max); net_write(sn->csd, tmp_buf, len); len = util_sprintf(tmp_buf, "Pool disabled: %d
", pool_disable); net_write(sn->csd, tmp_buf, len); len = util_sprintf(tmp_buf, "Blocks created: %d
Blocks freed: %d", pool_blocks_created, pool_blocks_freed); net_write(sn->csd, tmp_buf, len); /* Create an HTML table */ len = util_sprintf(tmp_buf, "
Pool # | \n"); net_write(sn->csd, tmp_buf, len); len = util_sprintf(tmp_buf, "Pool size # | \n"); net_write(sn->csd, tmp_buf, len); #ifdef DEBUG_CACHES len = util_sprintf(tmp_buf, "Time Created | \n"); net_write(sn->csd, tmp_buf, len); #endif len = util_sprintf(tmp_buf, "Blocks | \n"); net_write(sn->csd, tmp_buf, len); crit_enter(known_pools_lock); for (pool_cnt = 0, pool_ptr = known_pools; pool_ptr; pool_ptr = pool_ptr->next, pool_cnt++) { #ifdef POOL_LOCKING crit_enter(pool_ptr->lock); #endif len = util_snprintf(tmp_buf, MAX_DEBUG_LINE, #ifndef DEBUG_CACHES "|||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
%d | %d |
|