/* * Copyright (C) 2004-2009 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 1997-2003 Internet Software Consortium. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* $Id: mem.c,v 1.137.16.8 2009/02/16 03:17:29 marka Exp $ */ /*! \file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MCTXLOCK(m, l) if (((m)->flags & ISC_MEMFLAG_NOLOCK) == 0) LOCK(l) #define MCTXUNLOCK(m, l) if (((m)->flags & ISC_MEMFLAG_NOLOCK) == 0) UNLOCK(l) #ifndef ISC_MEM_DEBUGGING #define ISC_MEM_DEBUGGING 0 #endif LIBISC_EXTERNAL_DATA unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING; /* * Constants. */ #define DEF_MAX_SIZE 1100 #define DEF_MEM_TARGET 4096 #define ALIGNMENT_SIZE 8U /*%< must be a power of 2 */ #define NUM_BASIC_BLOCKS 64 /*%< must be > 1 */ #define TABLE_INCREMENT 1024 #define DEBUGLIST_COUNT 1024 /* * Types. */ #if ISC_MEM_TRACKLINES typedef struct debuglink debuglink_t; struct debuglink { ISC_LINK(debuglink_t) link; const void *ptr[DEBUGLIST_COUNT]; unsigned int size[DEBUGLIST_COUNT]; const char *file[DEBUGLIST_COUNT]; unsigned int line[DEBUGLIST_COUNT]; unsigned int count; }; #define FLARG_PASS , file, line #define FLARG , const char *file, int line #else #define FLARG_PASS #define FLARG #endif typedef struct element element; struct element { element * next; }; typedef struct { /*! * This structure must be ALIGNMENT_SIZE bytes. */ union { size_t size; isc_mem_t *ctx; char bytes[ALIGNMENT_SIZE]; } u; } size_info; struct stats { unsigned long gets; unsigned long totalgets; unsigned long blocks; unsigned long freefrags; }; #define MEM_MAGIC ISC_MAGIC('M', 'e', 'm', 'C') #define VALID_CONTEXT(c) ISC_MAGIC_VALID(c, MEM_MAGIC) #if ISC_MEM_TRACKLINES typedef ISC_LIST(debuglink_t) debuglist_t; #endif /* List of all active memory contexts. */ static ISC_LIST(isc_mem_t) contexts; static isc_once_t once = ISC_ONCE_INIT; static isc_mutex_t lock; /*% * Total size of lost memory due to a bug of external library. * Locked by the global lock. */ static isc_uint64_t totallost; struct isc_mem { unsigned int magic; isc_ondestroy_t ondestroy; unsigned int flags; isc_mutex_t lock; isc_memalloc_t memalloc; isc_memfree_t memfree; void * arg; size_t max_size; isc_boolean_t checkfree; struct stats * stats; unsigned int references; char name[16]; void * tag; size_t quota; size_t total; size_t inuse; size_t maxinuse; size_t hi_water; size_t lo_water; isc_boolean_t hi_called; isc_mem_water_t water; void * water_arg; ISC_LIST(isc_mempool_t) pools; unsigned int poolcnt; /* ISC_MEMFLAG_INTERNAL */ size_t mem_target; element ** freelists; element * basic_blocks; unsigned char ** basic_table; unsigned int basic_table_count; unsigned int basic_table_size; unsigned char * lowest; unsigned char * highest; #if ISC_MEM_TRACKLINES debuglist_t * debuglist; unsigned int debuglistcnt; #endif unsigned int memalloc_failures; ISC_LINK(isc_mem_t) link; }; #define MEMPOOL_MAGIC ISC_MAGIC('M', 'E', 'M', 'p') #define VALID_MEMPOOL(c) ISC_MAGIC_VALID(c, MEMPOOL_MAGIC) struct isc_mempool { /* always unlocked */ unsigned int magic; /*%< magic number */ isc_mutex_t *lock; /*%< optional lock */ isc_mem_t *mctx; /*%< our memory context */ /*%< locked via the memory context's lock */ ISC_LINK(isc_mempool_t) link; /*%< next pool in this mem context */ /*%< optionally locked from here down */ element *items; /*%< low water item list */ size_t size; /*%< size of each item on this pool */ unsigned int maxalloc; /*%< max number of items allowed */ unsigned int allocated; /*%< # of items currently given out */ unsigned int freecount; /*%< # of items on reserved list */ unsigned int freemax; /*%< # of items allowed on free list */ unsigned int fillcount; /*%< # of items to fetch on each fill */ /*%< Stats only. */ unsigned int gets; /*%< # of requests to this pool */ /*%< Debugging only. */ #if ISC_MEMPOOL_NAMES char name[16]; /*%< printed name in stats reports */ #endif }; /* * Private Inline-able. */ #if ! ISC_MEM_TRACKLINES #define ADD_TRACE(a, b, c, d, e) #define DELETE_TRACE(a, b, c, d, e) #else #define ADD_TRACE(a, b, c, d, e) \ do { \ if ((isc_mem_debugging & (ISC_MEM_DEBUGTRACE | \ ISC_MEM_DEBUGRECORD)) != 0 && \ b != NULL) \ add_trace_entry(a, b, c, d, e); \ } while (0) #define DELETE_TRACE(a, b, c, d, e) delete_trace_entry(a, b, c, d, e) static void print_active(isc_mem_t *ctx, FILE *out); /*! * mctx must be locked. */ static inline void add_trace_entry(isc_mem_t *mctx, const void *ptr, unsigned int size FLARG) { debuglink_t *dl; unsigned int i; if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) fprintf(stderr, isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_ADDTRACE, "add %p size %u " "file %s line %u mctx %p\n"), ptr, size, file, line, mctx); if (mctx->debuglist == NULL) return; if (size > mctx->max_size) size = mctx->max_size; dl = ISC_LIST_HEAD(mctx->debuglist[size]); while (dl != NULL) { if (dl->count == DEBUGLIST_COUNT) goto next; for (i = 0; i < DEBUGLIST_COUNT; i++) { if (dl->ptr[i] == NULL) { dl->ptr[i] = ptr; dl->size[i] = size; dl->file[i] = file; dl->line[i] = line; dl->count++; return; } } next: dl = ISC_LIST_NEXT(dl, link); } dl = malloc(sizeof(debuglink_t)); INSIST(dl != NULL); ISC_LINK_INIT(dl, link); for (i = 1; i < DEBUGLIST_COUNT; i++) { dl->ptr[i] = NULL; dl->size[i] = 0; dl->file[i] = NULL; dl->line[i] = 0; } dl->ptr[0] = ptr; dl->size[0] = size; dl->file[0] = file; dl->line[0] = line; dl->count = 1; ISC_LIST_PREPEND(mctx->debuglist[size], dl, link); mctx->debuglistcnt++; } static inline void delete_trace_entry(isc_mem_t *mctx, const void *ptr, unsigned int size, const char *file, unsigned int line) { debuglink_t *dl; unsigned int i; if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) fprintf(stderr, isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_DELTRACE, "del %p size %u " "file %s line %u mctx %p\n"), ptr, size, file, line, mctx); if (mctx->debuglist == NULL) return; if (size > mctx->max_size) size = mctx->max_size; dl = ISC_LIST_HEAD(mctx->debuglist[size]); while (dl != NULL) { for (i = 0; i < DEBUGLIST_COUNT; i++) { if (dl->ptr[i] == ptr) { dl->ptr[i] = NULL; dl->size[i] = 0; dl->file[i] = NULL; dl->line[i] = 0; INSIST(dl->count > 0); dl->count--; if (dl->count == 0) { ISC_LIST_UNLINK(mctx->debuglist[size], dl, link); free(dl); } return; } } dl = ISC_LIST_NEXT(dl, link); } /* * If we get here, we didn't find the item on the list. We're * screwed. */ INSIST(dl != NULL); } #endif /* ISC_MEM_TRACKLINES */ static inline size_t rmsize(size_t size) { /* * round down to ALIGNMENT_SIZE */ return (size & (~(ALIGNMENT_SIZE - 1))); } static inline size_t quantize(size_t size) { /*! * Round up the result in order to get a size big * enough to satisfy the request and be aligned on ALIGNMENT_SIZE * byte boundaries. */ if (size == 0U) return (ALIGNMENT_SIZE); return ((size + ALIGNMENT_SIZE - 1) & (~(ALIGNMENT_SIZE - 1))); } static inline isc_boolean_t more_basic_blocks(isc_mem_t *ctx) { void *new; unsigned char *curr, *next; unsigned char *first, *last; unsigned char **table; unsigned int table_size; size_t increment; int i; /* Require: we hold the context lock. */ /* * Did we hit the quota for this context? */ increment = NUM_BASIC_BLOCKS * ctx->mem_target; if (ctx->quota != 0U && ctx->total + increment > ctx->quota) return (ISC_FALSE); INSIST(ctx->basic_table_count <= ctx->basic_table_size); if (ctx->basic_table_count == ctx->basic_table_size) { table_size = ctx->basic_table_size + TABLE_INCREMENT; table = (ctx->memalloc)(ctx->arg, table_size * sizeof(unsigned char *)); if (table == NULL) { ctx->memalloc_failures++; return (ISC_FALSE); } if (ctx->basic_table_size != 0) { memcpy(table, ctx->basic_table, ctx->basic_table_size * sizeof(unsigned char *)); (ctx->memfree)(ctx->arg, ctx->basic_table); } ctx->basic_table = table; ctx->basic_table_size = table_size; } new = (ctx->memalloc)(ctx->arg, NUM_BASIC_BLOCKS * ctx->mem_target); if (new == NULL) { ctx->memalloc_failures++; return (ISC_FALSE); } ctx->total += increment; ctx->basic_table[ctx->basic_table_count] = new; ctx->basic_table_count++; curr = new; next = curr + ctx->mem_target; for (i = 0; i < (NUM_BASIC_BLOCKS - 1); i++) { ((element *)curr)->next = (element *)next; curr = next; next += ctx->mem_target; } /* * curr is now pointing at the last block in the * array. */ ((element *)curr)->next = NULL; first = new; last = first + NUM_BASIC_BLOCKS * ctx->mem_target - 1; if (first < ctx->lowest || ctx->lowest == NULL) ctx->lowest = first; if (last > ctx->highest) ctx->highest = last; ctx->basic_blocks = new; return (ISC_TRUE); } static inline isc_boolean_t more_frags(isc_mem_t *ctx, size_t new_size) { int i, frags; size_t total_size; void *new; unsigned char *curr, *next; /*! * Try to get more fragments by chopping up a basic block. */ if (ctx->basic_blocks == NULL) { if (!more_basic_blocks(ctx)) { /* * We can't get more memory from the OS, or we've * hit the quota for this context. */ /* * XXXRTH "At quota" notification here. */ return (ISC_FALSE); } } total_size = ctx->mem_target; new = ctx->basic_blocks; ctx->basic_blocks = ctx->basic_blocks->next; frags = total_size / new_size; ctx->stats[new_size].blocks++; ctx->stats[new_size].freefrags += frags; /* * Set up a linked-list of blocks of size * "new_size". */ curr = new; next = curr + new_size; total_size -= new_size; for (i = 0; i < (frags - 1); i++) { ((element *)curr)->next = (element *)next; curr = next; next += new_size; total_size -= new_size; } /* * Add the remaining fragment of the basic block to a free list. */ total_size = rmsize(total_size); if (total_size > 0U) { ((element *)next)->next = ctx->freelists[total_size]; ctx->freelists[total_size] = (element *)next; ctx->stats[total_size].freefrags++; } /* * curr is now pointing at the last block in the * array. */ ((element *)curr)->next = NULL; ctx->freelists[new_size] = new; return (ISC_TRUE); } static inline void * mem_getunlocked(isc_mem_t *ctx, size_t size) { size_t new_size = quantize(size); void *ret; if (size >= ctx->max_size || new_size >= ctx->max_size) { /* * memget() was called on something beyond our upper limit. */ if (ctx->quota != 0U && ctx->total + size > ctx->quota) { ret = NULL; goto done; } ret = (ctx->memalloc)(ctx->arg, size); if (ret == NULL) { ctx->memalloc_failures++; goto done; } ctx->total += size; ctx->inuse += size; ctx->stats[ctx->max_size].gets++; ctx->stats[ctx->max_size].totalgets++; /* * If we don't set new_size to size, then the * ISC_MEM_FILL code might write over bytes we * don't own. */ new_size = size; goto done; } /* * If there are no blocks in the free list for this size, get a chunk * of memory and then break it up into "new_size"-sized blocks, adding * them to the free list. */ if (ctx->freelists[new_size] == NULL && !more_frags(ctx, new_size)) return (NULL); /* * The free list uses the "rounded-up" size "new_size". */ ret = ctx->freelists[new_size]; ctx->freelists[new_size] = ctx->freelists[new_size]->next; /* * The stats[] uses the _actual_ "size" requested by the * caller, with the caveat (in the code above) that "size" >= the * max. size (max_size) ends up getting recorded as a call to * max_size. */ ctx->stats[size].gets++; ctx->stats[size].totalgets++; ctx->stats[new_size].freefrags--; ctx->inuse += new_size; done: #if ISC_MEM_FILL if (ret != NULL) memset(ret, 0xbe, new_size); /* Mnemonic for "beef". */ #endif return (ret); } #if ISC_MEM_FILL && ISC_MEM_CHECKOVERRUN static inline void check_overrun(void *mem, size_t size, size_t new_size) { unsigned char *cp; cp = (unsigned char *)mem; cp += size; while (size < new_size) { INSIST(*cp == 0xbe); cp++; size++; } } #endif static inline void mem_putunlocked(isc_mem_t *ctx, void *mem, size_t size) { size_t new_size = quantize(size); if (size == ctx->max_size || new_size >= ctx->max_size) { /* * memput() called on something beyond our upper limit. */ #if ISC_MEM_FILL memset(mem, 0xde, size); /* Mnemonic for "dead". */ #endif (ctx->memfree)(ctx->arg, mem); INSIST(ctx->stats[ctx->max_size].gets != 0U); ctx->stats[ctx->max_size].gets--; INSIST(size <= ctx->total); ctx->inuse -= size; ctx->total -= size; return; } #if ISC_MEM_FILL #if ISC_MEM_CHECKOVERRUN check_overrun(mem, size, new_size); #endif memset(mem, 0xde, new_size); /* Mnemonic for "dead". */ #endif /* * The free list uses the "rounded-up" size "new_size". */ ((element *)mem)->next = ctx->freelists[new_size]; ctx->freelists[new_size] = (element *)mem; /* * The stats[] uses the _actual_ "size" requested by the * caller, with the caveat (in the code above) that "size" >= the * max. size (max_size) ends up getting recorded as a call to * max_size. */ INSIST(ctx->stats[size].gets != 0U); ctx->stats[size].gets--; ctx->stats[new_size].freefrags++; ctx->inuse -= new_size; } /*! * Perform a malloc, doing memory filling and overrun detection as necessary. */ static inline void * mem_get(isc_mem_t *ctx, size_t size) { char *ret; #if ISC_MEM_CHECKOVERRUN size += 1; #endif ret = (ctx->memalloc)(ctx->arg, size); if (ret == NULL) ctx->memalloc_failures++; #if ISC_MEM_FILL if (ret != NULL) memset(ret, 0xbe, size); /* Mnemonic for "beef". */ #else # if ISC_MEM_CHECKOVERRUN if (ret != NULL) ret[size-1] = 0xbe; # endif #endif return (ret); } /*! * Perform a free, doing memory filling and overrun detection as necessary. */ static inline void mem_put(isc_mem_t *ctx, void *mem, size_t size) { #if ISC_MEM_CHECKOVERRUN INSIST(((unsigned char *)mem)[size] == 0xbe); #endif #if ISC_MEM_FILL memset(mem, 0xde, size); /* Mnemonic for "dead". */ #else UNUSED(size); #endif (ctx->memfree)(ctx->arg, mem); } /*! * Update internal counters after a memory get. */ static inline void mem_getstats(isc_mem_t *ctx, size_t size) { ctx->total += size; ctx->inuse += size; if (size > ctx->max_size) { ctx->stats[ctx->max_size].gets++; ctx->stats[ctx->max_size].totalgets++; } else { ctx->stats[size].gets++; ctx->stats[size].totalgets++; } } /*! * Update internal counters after a memory put. */ static inline void mem_putstats(isc_mem_t *ctx, void *ptr, size_t size) { UNUSED(ptr); INSIST(ctx->inuse >= size); ctx->inuse -= size; if (size > ctx->max_size) { INSIST(ctx->stats[ctx->max_size].gets > 0U); ctx->stats[ctx->max_size].gets--; } else { INSIST(ctx->stats[size].gets > 0U); ctx->stats[size].gets--; } } /* * Private. */ static void * default_memalloc(void *arg, size_t size) { UNUSED(arg); if (size == 0U) size = 1; return (malloc(size)); } static void default_memfree(void *arg, void *ptr) { UNUSED(arg); free(ptr); } static void initialize_action(void) { RUNTIME_CHECK(isc_mutex_init(&lock) == ISC_R_SUCCESS); ISC_LIST_INIT(contexts); totallost = 0; } /* * Public. */ isc_result_t isc_mem_createx(size_t init_max_size, size_t target_size, isc_memalloc_t memalloc, isc_memfree_t memfree, void *arg, isc_mem_t **ctxp) { return (isc_mem_createx2(init_max_size, target_size, memalloc, memfree, arg, ctxp, ISC_MEMFLAG_DEFAULT)); } isc_result_t isc_mem_createx2(size_t init_max_size, size_t target_size, isc_memalloc_t memalloc, isc_memfree_t memfree, void *arg, isc_mem_t **ctxp, unsigned int flags) { isc_mem_t *ctx; isc_result_t result; REQUIRE(ctxp != NULL && *ctxp == NULL); REQUIRE(memalloc != NULL); REQUIRE(memfree != NULL); INSIST((ALIGNMENT_SIZE & (ALIGNMENT_SIZE - 1)) == 0); RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); ctx = (memalloc)(arg, sizeof(*ctx)); if (ctx == NULL) return (ISC_R_NOMEMORY); if ((flags & ISC_MEMFLAG_NOLOCK) == 0) { result = isc_mutex_init(&ctx->lock); if (result != ISC_R_SUCCESS) { (memfree)(arg, ctx); return (result); } } if (init_max_size == 0U) ctx->max_size = DEF_MAX_SIZE; else ctx->max_size = init_max_size; ctx->flags = flags; ctx->references = 1; memset(ctx->name, 0, sizeof(ctx->name)); ctx->tag = NULL; ctx->quota = 0; ctx->total = 0; ctx->inuse = 0; ctx->maxinuse = 0; ctx->hi_water = 0; ctx->lo_water = 0; ctx->hi_called = ISC_FALSE; ctx->water = NULL; ctx->water_arg = NULL; ctx->magic = MEM_MAGIC; isc_ondestroy_init(&ctx->ondestroy); ctx->memalloc = memalloc; ctx->memfree = memfree; ctx->arg = arg; ctx->stats = NULL; ctx->checkfree = ISC_TRUE; #if ISC_MEM_TRACKLINES ctx->debuglist = NULL; ctx->debuglistcnt = 0; #endif ISC_LIST_INIT(ctx->pools); ctx->poolcnt = 0; ctx->freelists = NULL; ctx->basic_blocks = NULL; ctx->basic_table = NULL; ctx->basic_table_count = 0; ctx->basic_table_size = 0; ctx->lowest = NULL; ctx->highest = NULL; ctx->stats = (memalloc)(arg, (ctx->max_size+1) * sizeof(struct stats)); if (ctx->stats == NULL) { result = ISC_R_NOMEMORY; goto error; } memset(ctx->stats, 0, (ctx->max_size + 1) * sizeof(struct stats)); if ((flags & ISC_MEMFLAG_INTERNAL) != 0) { if (target_size == 0U) ctx->mem_target = DEF_MEM_TARGET; else ctx->mem_target = target_size; ctx->freelists = (memalloc)(arg, ctx->max_size * sizeof(element *)); if (ctx->freelists == NULL) { result = ISC_R_NOMEMORY; goto error; } memset(ctx->freelists, 0, ctx->max_size * sizeof(element *)); } #if ISC_MEM_TRACKLINES if ((isc_mem_debugging & ISC_MEM_DEBUGRECORD) != 0) { unsigned int i; ctx->debuglist = (memalloc)(arg, (ctx->max_size+1) * sizeof(debuglist_t)); if (ctx->debuglist == NULL) { result = ISC_R_NOMEMORY; goto error; } for (i = 0; i <= ctx->max_size; i++) ISC_LIST_INIT(ctx->debuglist[i]); } #endif ctx->memalloc_failures = 0; LOCK(&lock); ISC_LIST_INITANDAPPEND(contexts, ctx, link); UNLOCK(&lock); *ctxp = ctx; return (ISC_R_SUCCESS); error: if (ctx != NULL) { if (ctx->stats != NULL) (memfree)(arg, ctx->stats); if (ctx->freelists != NULL) (memfree)(arg, ctx->freelists); #if ISC_MEM_TRACKLINES if (ctx->debuglist != NULL) (ctx->memfree)(ctx->arg, ctx->debuglist); #endif /* ISC_MEM_TRACKLINES */ if ((ctx->flags & ISC_MEMFLAG_NOLOCK) == 0) DESTROYLOCK(&ctx->lock); (memfree)(arg, ctx); } return (result); } isc_result_t isc_mem_create(size_t init_max_size, size_t target_size, isc_mem_t **ctxp) { return (isc_mem_createx2(init_max_size, target_size, default_memalloc, default_memfree, NULL, ctxp, ISC_MEMFLAG_DEFAULT)); } isc_result_t isc_mem_create2(size_t init_max_size, size_t target_size, isc_mem_t **ctxp, unsigned int flags) { return (isc_mem_createx2(init_max_size, target_size, default_memalloc, default_memfree, NULL, ctxp, flags)); } static void destroy(isc_mem_t *ctx) { unsigned int i; isc_ondestroy_t ondest; ctx->magic = 0; LOCK(&lock); ISC_LIST_UNLINK(contexts, ctx, link); totallost += ctx->inuse; UNLOCK(&lock); INSIST(ISC_LIST_EMPTY(ctx->pools)); #if ISC_MEM_TRACKLINES if (ctx->debuglist != NULL) { if (ctx->checkfree) { for (i = 0; i <= ctx->max_size; i++) { if (!ISC_LIST_EMPTY(ctx->debuglist[i])) print_active(ctx, stderr); INSIST(ISC_LIST_EMPTY(ctx->debuglist[i])); } } else { debuglink_t *dl; for (i = 0; i <= ctx->max_size; i++) for (dl = ISC_LIST_HEAD(ctx->debuglist[i]); dl != NULL; dl = ISC_LIST_HEAD(ctx->debuglist[i])) { ISC_LIST_UNLINK(ctx->debuglist[i], dl, link); free(dl); } } (ctx->memfree)(ctx->arg, ctx->debuglist); } #endif INSIST(ctx->references == 0); if (ctx->checkfree) { for (i = 0; i <= ctx->max_size; i++) { #if ISC_MEM_TRACKLINES if (ctx->stats[i].gets != 0U) print_active(ctx, stderr); #endif INSIST(ctx->stats[i].gets == 0U); } } (ctx->memfree)(ctx->arg, ctx->stats); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { for (i = 0; i < ctx->basic_table_count; i++) (ctx->memfree)(ctx->arg, ctx->basic_table[i]); (ctx->memfree)(ctx->arg, ctx->freelists); if (ctx->basic_table != NULL) (ctx->memfree)(ctx->arg, ctx->basic_table); } ondest = ctx->ondestroy; if ((ctx->flags & ISC_MEMFLAG_NOLOCK) == 0) DESTROYLOCK(&ctx->lock); (ctx->memfree)(ctx->arg, ctx); isc_ondestroy_notify(&ondest, ctx); } void isc_mem_attach(isc_mem_t *source, isc_mem_t **targetp) { REQUIRE(VALID_CONTEXT(source)); REQUIRE(targetp != NULL && *targetp == NULL); MCTXLOCK(source, &source->lock); source->references++; MCTXUNLOCK(source, &source->lock); *targetp = source; } void isc_mem_detach(isc_mem_t **ctxp) { isc_mem_t *ctx; isc_boolean_t want_destroy = ISC_FALSE; REQUIRE(ctxp != NULL); ctx = *ctxp; REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); INSIST(ctx->references > 0); ctx->references--; if (ctx->references == 0) want_destroy = ISC_TRUE; MCTXUNLOCK(ctx, &ctx->lock); if (want_destroy) destroy(ctx); *ctxp = NULL; } /* * isc_mem_putanddetach() is the equivalent of: * * mctx = NULL; * isc_mem_attach(ptr->mctx, &mctx); * isc_mem_detach(&ptr->mctx); * isc_mem_put(mctx, ptr, sizeof(*ptr); * isc_mem_detach(&mctx); */ void isc__mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size FLARG) { isc_mem_t *ctx; isc_boolean_t want_destroy = ISC_FALSE; size_info *si; size_t oldsize; REQUIRE(ctxp != NULL); ctx = *ctxp; REQUIRE(VALID_CONTEXT(ctx)); REQUIRE(ptr != NULL); /* * Must be before mem_putunlocked() as ctxp is usually within * [ptr..ptr+size). */ *ctxp = NULL; if ((isc_mem_debugging & (ISC_MEM_DEBUGSIZE|ISC_MEM_DEBUGCTX)) != 0) { if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) { si = &(((size_info *)ptr)[-1]); oldsize = si->u.size - ALIGNMENT_SIZE; if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) oldsize -= ALIGNMENT_SIZE; INSIST(oldsize == size); } isc__mem_free(ctx, ptr FLARG_PASS); MCTXLOCK(ctx, &ctx->lock); ctx->references--; if (ctx->references == 0) want_destroy = ISC_TRUE; MCTXUNLOCK(ctx, &ctx->lock); if (want_destroy) destroy(ctx); return; } if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { MCTXLOCK(ctx, &ctx->lock); mem_putunlocked(ctx, ptr, size); } else { mem_put(ctx, ptr, size); MCTXLOCK(ctx, &ctx->lock); mem_putstats(ctx, ptr, size); } DELETE_TRACE(ctx, ptr, size, file, line); INSIST(ctx->references > 0); ctx->references--; if (ctx->references == 0) want_destroy = ISC_TRUE; MCTXUNLOCK(ctx, &ctx->lock); if (want_destroy) destroy(ctx); } void isc_mem_destroy(isc_mem_t **ctxp) { isc_mem_t *ctx; /* * This routine provides legacy support for callers who use mctxs * without attaching/detaching. */ REQUIRE(ctxp != NULL); ctx = *ctxp; REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); #if ISC_MEM_TRACKLINES if (ctx->references != 1) print_active(ctx, stderr); #endif REQUIRE(ctx->references == 1); ctx->references--; MCTXUNLOCK(ctx, &ctx->lock); destroy(ctx); *ctxp = NULL; } isc_result_t isc_mem_ondestroy(isc_mem_t *ctx, isc_task_t *task, isc_event_t **event) { isc_result_t res; MCTXLOCK(ctx, &ctx->lock); res = isc_ondestroy_register(&ctx->ondestroy, task, event); MCTXUNLOCK(ctx, &ctx->lock); return (res); } void * isc__mem_get(isc_mem_t *ctx, size_t size FLARG) { void *ptr; isc_boolean_t call_water = ISC_FALSE; REQUIRE(VALID_CONTEXT(ctx)); if ((isc_mem_debugging & (ISC_MEM_DEBUGSIZE|ISC_MEM_DEBUGCTX)) != 0) return (isc__mem_allocate(ctx, size FLARG_PASS)); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { MCTXLOCK(ctx, &ctx->lock); ptr = mem_getunlocked(ctx, size); } else { ptr = mem_get(ctx, size); MCTXLOCK(ctx, &ctx->lock); if (ptr != NULL) mem_getstats(ctx, size); } ADD_TRACE(ctx, ptr, size, file, line); if (ctx->hi_water != 0U && !ctx->hi_called && ctx->inuse > ctx->hi_water) { call_water = ISC_TRUE; } if (ctx->inuse > ctx->maxinuse) { ctx->maxinuse = ctx->inuse; if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) fprintf(stderr, "maxinuse = %lu\n", (unsigned long)ctx->inuse); } MCTXUNLOCK(ctx, &ctx->lock); if (call_water) (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); return (ptr); } void isc__mem_put(isc_mem_t *ctx, void *ptr, size_t size FLARG) { isc_boolean_t call_water = ISC_FALSE; size_info *si; size_t oldsize; REQUIRE(VALID_CONTEXT(ctx)); REQUIRE(ptr != NULL); if ((isc_mem_debugging & (ISC_MEM_DEBUGSIZE|ISC_MEM_DEBUGCTX)) != 0) { if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) { si = &(((size_info *)ptr)[-1]); oldsize = si->u.size - ALIGNMENT_SIZE; if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) oldsize -= ALIGNMENT_SIZE; INSIST(oldsize == size); } isc__mem_free(ctx, ptr FLARG_PASS); return; } if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { MCTXLOCK(ctx, &ctx->lock); mem_putunlocked(ctx, ptr, size); } else { mem_put(ctx, ptr, size); MCTXLOCK(ctx, &ctx->lock); mem_putstats(ctx, ptr, size); } DELETE_TRACE(ctx, ptr, size, file, line); /* * The check against ctx->lo_water == 0 is for the condition * when the context was pushed over hi_water but then had * isc_mem_setwater() called with 0 for hi_water and lo_water. */ if (ctx->hi_called && (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U)) { if (ctx->water != NULL) call_water = ISC_TRUE; } MCTXUNLOCK(ctx, &ctx->lock); if (call_water) (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); } void isc_mem_waterack(isc_mem_t *ctx, int flag) { REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); if (flag == ISC_MEM_LOWATER) ctx->hi_called = ISC_FALSE; else if (flag == ISC_MEM_HIWATER) ctx->hi_called = ISC_TRUE; MCTXUNLOCK(ctx, &ctx->lock); } #if ISC_MEM_TRACKLINES static void print_active(isc_mem_t *mctx, FILE *out) { if (mctx->debuglist != NULL) { debuglink_t *dl; unsigned int i, j; const char *format; isc_boolean_t found; fprintf(out, "%s", isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_DUMPALLOC, "Dump of all outstanding " "memory allocations:\n")); found = ISC_FALSE; format = isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_PTRFILELINE, "\tptr %p size %u file %s line %u\n"); for (i = 0; i <= mctx->max_size; i++) { dl = ISC_LIST_HEAD(mctx->debuglist[i]); if (dl != NULL) found = ISC_TRUE; while (dl != NULL) { for (j = 0; j < DEBUGLIST_COUNT; j++) if (dl->ptr[j] != NULL) fprintf(out, format, dl->ptr[j], dl->size[j], dl->file[j], dl->line[j]); dl = ISC_LIST_NEXT(dl, link); } } if (!found) fprintf(out, "%s", isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_NONE, "\tNone.\n")); } } #endif /* * Print the stats[] on the stream "out" with suitable formatting. */ void isc_mem_stats(isc_mem_t *ctx, FILE *out) { size_t i; const struct stats *s; const isc_mempool_t *pool; REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); for (i = 0; i <= ctx->max_size; i++) { s = &ctx->stats[i]; if (s->totalgets == 0U && s->gets == 0U) continue; fprintf(out, "%s%5lu: %11lu gets, %11lu rem", (i == ctx->max_size) ? ">=" : " ", (unsigned long) i, s->totalgets, s->gets); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0 && (s->blocks != 0U || s->freefrags != 0U)) fprintf(out, " (%lu bl, %lu ff)", s->blocks, s->freefrags); fputc('\n', out); } /* * Note that since a pool can be locked now, these stats might be * somewhat off if the pool is in active use at the time the stats * are dumped. The link fields are protected by the isc_mem_t's * lock, however, so walking this list and extracting integers from * stats fields is always safe. */ pool = ISC_LIST_HEAD(ctx->pools); if (pool != NULL) { fprintf(out, "%s", isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLSTATS, "[Pool statistics]\n")); fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %10s %1s\n", isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLNAME, "name"), isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLSIZE, "size"), isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLMAXALLOC, "maxalloc"), isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLALLOCATED, "allocated"), isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLFREECOUNT, "freecount"), isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLFREEMAX, "freemax"), isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLFILLCOUNT, "fillcount"), isc_msgcat_get(isc_msgcat, ISC_MSGSET_MEM, ISC_MSG_POOLGETS, "gets"), "L"); } while (pool != NULL) { fprintf(out, "%15s %10lu %10u %10u %10u %10u %10u %10u %s\n", pool->name, (unsigned long) pool->size, pool->maxalloc, pool->allocated, pool->freecount, pool->freemax, pool->fillcount, pool->gets, (pool->lock == NULL ? "N" : "Y")); pool = ISC_LIST_NEXT(pool, link); } #if ISC_MEM_TRACKLINES print_active(ctx, out); #endif MCTXUNLOCK(ctx, &ctx->lock); } /* * Replacements for malloc() and free() -- they implicitly remember the * size of the object allocated (with some additional overhead). */ static void * isc__mem_allocateunlocked(isc_mem_t *ctx, size_t size) { size_info *si; size += ALIGNMENT_SIZE; if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) size += ALIGNMENT_SIZE; if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) si = mem_getunlocked(ctx, size); else si = mem_get(ctx, size); if (si == NULL) return (NULL); if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { si->u.ctx = ctx; si++; } si->u.size = size; return (&si[1]); } void * isc__mem_allocate(isc_mem_t *ctx, size_t size FLARG) { size_info *si; isc_boolean_t call_water = ISC_FALSE; REQUIRE(VALID_CONTEXT(ctx)); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { MCTXLOCK(ctx, &ctx->lock); si = isc__mem_allocateunlocked(ctx, size); } else { si = isc__mem_allocateunlocked(ctx, size); MCTXLOCK(ctx, &ctx->lock); if (si != NULL) mem_getstats(ctx, si[-1].u.size); } #if ISC_MEM_TRACKLINES ADD_TRACE(ctx, si, si[-1].u.size, file, line); #endif if (ctx->hi_water != 0U && !ctx->hi_called && ctx->inuse > ctx->hi_water) { ctx->hi_called = ISC_TRUE; call_water = ISC_TRUE; } if (ctx->inuse > ctx->maxinuse) { ctx->maxinuse = ctx->inuse; if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) fprintf(stderr, "maxinuse = %lu\n", (unsigned long)ctx->inuse); } MCTXUNLOCK(ctx, &ctx->lock); if (call_water) (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); return (si); } void * isc__mem_reallocate(isc_mem_t *ctx, void *ptr, size_t size FLARG) { void *new_ptr = NULL; size_t oldsize, copysize; REQUIRE(VALID_CONTEXT(ctx)); /* * This function emulates the realloc(3) standard library function: * - if size > 0, allocate new memory; and if ptr is non NULL, copy * as much of the old contents to the new buffer and free the old one. * Note that when allocation fails the original pointer is intact; * the caller must free it. * - if size is 0 and ptr is non NULL, simply free the given ptr. * - this function returns: * pointer to the newly allocated memory, or * NULL if allocation fails or doesn't happen. */ if (size > 0U) { new_ptr = isc__mem_allocate(ctx, size FLARG_PASS); if (new_ptr != NULL && ptr != NULL) { oldsize = (((size_info *)ptr)[-1]).u.size; INSIST(oldsize >= ALIGNMENT_SIZE); oldsize -= ALIGNMENT_SIZE; copysize = oldsize > size ? size : oldsize; memcpy(new_ptr, ptr, copysize); isc__mem_free(ctx, ptr FLARG_PASS); } } else if (ptr != NULL) isc__mem_free(ctx, ptr FLARG_PASS); return (new_ptr); } void isc__mem_free(isc_mem_t *ctx, void *ptr FLARG) { size_info *si; size_t size; isc_boolean_t call_water= ISC_FALSE; REQUIRE(VALID_CONTEXT(ctx)); REQUIRE(ptr != NULL); if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { si = &(((size_info *)ptr)[-2]); REQUIRE(si->u.ctx == ctx); size = si[1].u.size; } else { si = &(((size_info *)ptr)[-1]); size = si->u.size; } if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { MCTXLOCK(ctx, &ctx->lock); mem_putunlocked(ctx, si, size); } else { mem_put(ctx, si, size); MCTXLOCK(ctx, &ctx->lock); mem_putstats(ctx, si, size); } DELETE_TRACE(ctx, ptr, size, file, line); /* * The check against ctx->lo_water == 0 is for the condition * when the context was pushed over hi_water but then had * isc_mem_setwater() called with 0 for hi_water and lo_water. */ if (ctx->hi_called && (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U)) { ctx->hi_called = ISC_FALSE; if (ctx->water != NULL) call_water = ISC_TRUE; } MCTXUNLOCK(ctx, &ctx->lock); if (call_water) (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); } /* * Other useful things. */ char * isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) { size_t len; char *ns; REQUIRE(VALID_CONTEXT(mctx)); REQUIRE(s != NULL); len = strlen(s); ns = isc__mem_allocate(mctx, len + 1 FLARG_PASS); if (ns != NULL) strncpy(ns, s, len + 1); return (ns); } void isc_mem_setdestroycheck(isc_mem_t *ctx, isc_boolean_t flag) { REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); ctx->checkfree = flag; MCTXUNLOCK(ctx, &ctx->lock); } /* * Quotas */ void isc_mem_setquota(isc_mem_t *ctx, size_t quota) { REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); ctx->quota = quota; MCTXUNLOCK(ctx, &ctx->lock); } size_t isc_mem_getquota(isc_mem_t *ctx) { size_t quota; REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); quota = ctx->quota; MCTXUNLOCK(ctx, &ctx->lock); return (quota); } size_t isc_mem_inuse(isc_mem_t *ctx) { size_t inuse; REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); inuse = ctx->inuse; MCTXUNLOCK(ctx, &ctx->lock); return (inuse); } void isc_mem_setwater(isc_mem_t *ctx, isc_mem_water_t water, void *water_arg, size_t hiwater, size_t lowater) { isc_boolean_t callwater = ISC_FALSE; isc_mem_water_t oldwater; void *oldwater_arg; REQUIRE(VALID_CONTEXT(ctx)); REQUIRE(hiwater >= lowater); MCTXLOCK(ctx, &ctx->lock); oldwater = ctx->water; oldwater_arg = ctx->water_arg; if (water == NULL) { callwater = ctx->hi_called; ctx->water = NULL; ctx->water_arg = NULL; ctx->hi_water = 0; ctx->lo_water = 0; ctx->hi_called = ISC_FALSE; } else { if (ctx->hi_called && (ctx->water != water || ctx->water_arg != water_arg || ctx->inuse < lowater || lowater == 0U)) callwater = ISC_TRUE; ctx->water = water; ctx->water_arg = water_arg; ctx->hi_water = hiwater; ctx->lo_water = lowater; ctx->hi_called = ISC_FALSE; } MCTXUNLOCK(ctx, &ctx->lock); if (callwater && oldwater != NULL) (oldwater)(oldwater_arg, ISC_MEM_LOWATER); } void isc_mem_setname(isc_mem_t *ctx, const char *name, void *tag) { REQUIRE(VALID_CONTEXT(ctx)); LOCK(&ctx->lock); memset(ctx->name, 0, sizeof(ctx->name)); strncpy(ctx->name, name, sizeof(ctx->name) - 1); ctx->tag = tag; UNLOCK(&ctx->lock); } const char * isc_mem_getname(isc_mem_t *ctx) { REQUIRE(VALID_CONTEXT(ctx)); return (ctx->name); } void * isc_mem_gettag(isc_mem_t *ctx) { REQUIRE(VALID_CONTEXT(ctx)); return (ctx->tag); } /* * Memory pool stuff */ isc_result_t isc_mempool_create(isc_mem_t *mctx, size_t size, isc_mempool_t **mpctxp) { isc_mempool_t *mpctx; REQUIRE(VALID_CONTEXT(mctx)); REQUIRE(size > 0U); REQUIRE(mpctxp != NULL && *mpctxp == NULL); /* * Allocate space for this pool, initialize values, and if all works * well, attach to the memory context. */ mpctx = isc_mem_get(mctx, sizeof(isc_mempool_t)); if (mpctx == NULL) return (ISC_R_NOMEMORY); mpctx->magic = MEMPOOL_MAGIC; mpctx->lock = NULL; mpctx->mctx = mctx; mpctx->size = size; mpctx->maxalloc = UINT_MAX; mpctx->allocated = 0; mpctx->freecount = 0; mpctx->freemax = 1; mpctx->fillcount = 1; mpctx->gets = 0; #if ISC_MEMPOOL_NAMES mpctx->name[0] = 0; #endif mpctx->items = NULL; *mpctxp = mpctx; MCTXLOCK(mctx, &mctx->lock); ISC_LIST_INITANDAPPEND(mctx->pools, mpctx, link); mctx->poolcnt++; MCTXUNLOCK(mctx, &mctx->lock); return (ISC_R_SUCCESS); } void isc_mempool_setname(isc_mempool_t *mpctx, const char *name) { REQUIRE(name != NULL); #if ISC_MEMPOOL_NAMES if (mpctx->lock != NULL) LOCK(mpctx->lock); strncpy(mpctx->name, name, sizeof(mpctx->name) - 1); mpctx->name[sizeof(mpctx->name) - 1] = '\0'; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); #else UNUSED(mpctx); UNUSED(name); #endif } void isc_mempool_destroy(isc_mempool_t **mpctxp) { isc_mempool_t *mpctx; isc_mem_t *mctx; isc_mutex_t *lock; element *item; REQUIRE(mpctxp != NULL); mpctx = *mpctxp; REQUIRE(VALID_MEMPOOL(mpctx)); #if ISC_MEMPOOL_NAMES if (mpctx->allocated > 0) UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_mempool_destroy(): mempool %s " "leaked memory", mpctx->name); #endif REQUIRE(mpctx->allocated == 0); mctx = mpctx->mctx; lock = mpctx->lock; if (lock != NULL) LOCK(lock); /* * Return any items on the free list */ MCTXLOCK(mctx, &mctx->lock); while (mpctx->items != NULL) { INSIST(mpctx->freecount > 0); mpctx->freecount--; item = mpctx->items; mpctx->items = item->next; if ((mctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { mem_putunlocked(mctx, item, mpctx->size); } else { mem_put(mctx, item, mpctx->size); mem_putstats(mctx, item, mpctx->size); } } MCTXUNLOCK(mctx, &mctx->lock); /* * Remove our linked list entry from the memory context. */ MCTXLOCK(mctx, &mctx->lock); ISC_LIST_UNLINK(mctx->pools, mpctx, link); mctx->poolcnt--; MCTXUNLOCK(mctx, &mctx->lock); mpctx->magic = 0; isc_mem_put(mpctx->mctx, mpctx, sizeof(isc_mempool_t)); if (lock != NULL) UNLOCK(lock); *mpctxp = NULL; } void isc_mempool_associatelock(isc_mempool_t *mpctx, isc_mutex_t *lock) { REQUIRE(VALID_MEMPOOL(mpctx)); REQUIRE(mpctx->lock == NULL); REQUIRE(lock != NULL); mpctx->lock = lock; } void * isc__mempool_get(isc_mempool_t *mpctx FLARG) { element *item; isc_mem_t *mctx; unsigned int i; REQUIRE(VALID_MEMPOOL(mpctx)); mctx = mpctx->mctx; if (mpctx->lock != NULL) LOCK(mpctx->lock); /* * Don't let the caller go over quota */ if (mpctx->allocated >= mpctx->maxalloc) { item = NULL; goto out; } /* * if we have a free list item, return the first here */ item = mpctx->items; if (item != NULL) { mpctx->items = item->next; INSIST(mpctx->freecount > 0); mpctx->freecount--; mpctx->gets++; mpctx->allocated++; goto out; } /* * We need to dip into the well. Lock the memory context here and * fill up our free list. */ MCTXLOCK(mctx, &mctx->lock); for (i = 0; i < mpctx->fillcount; i++) { if ((mctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { item = mem_getunlocked(mctx, mpctx->size); } else { item = mem_get(mctx, mpctx->size); if (item != NULL) mem_getstats(mctx, mpctx->size); } if (item == NULL) break; item->next = mpctx->items; mpctx->items = item; mpctx->freecount++; } MCTXUNLOCK(mctx, &mctx->lock); /* * If we didn't get any items, return NULL. */ item = mpctx->items; if (item == NULL) goto out; mpctx->items = item->next; mpctx->freecount--; mpctx->gets++; mpctx->allocated++; out: if (mpctx->lock != NULL) UNLOCK(mpctx->lock); #if ISC_MEM_TRACKLINES if (item != NULL) { MCTXLOCK(mctx, &mctx->lock); ADD_TRACE(mctx, item, mpctx->size, file, line); MCTXUNLOCK(mctx, &mctx->lock); } #endif /* ISC_MEM_TRACKLINES */ return (item); } void isc__mempool_put(isc_mempool_t *mpctx, void *mem FLARG) { isc_mem_t *mctx; element *item; REQUIRE(VALID_MEMPOOL(mpctx)); REQUIRE(mem != NULL); mctx = mpctx->mctx; if (mpctx->lock != NULL) LOCK(mpctx->lock); INSIST(mpctx->allocated > 0); mpctx->allocated--; #if ISC_MEM_TRACKLINES MCTXLOCK(mctx, &mctx->lock); DELETE_TRACE(mctx, mem, mpctx->size, file, line); MCTXUNLOCK(mctx, &mctx->lock); #endif /* ISC_MEM_TRACKLINES */ /* * If our free list is full, return this to the mctx directly. */ if (mpctx->freecount >= mpctx->freemax) { if ((mctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { MCTXLOCK(mctx, &mctx->lock); mem_putunlocked(mctx, mem, mpctx->size); MCTXUNLOCK(mctx, &mctx->lock); } else { mem_put(mctx, mem, mpctx->size); MCTXLOCK(mctx, &mctx->lock); mem_putstats(mctx, mem, mpctx->size); MCTXUNLOCK(mctx, &mctx->lock); } if (mpctx->lock != NULL) UNLOCK(mpctx->lock); return; } /* * Otherwise, attach it to our free list and bump the counter. */ mpctx->freecount++; item = (element *)mem; item->next = mpctx->items; mpctx->items = item; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); } /* * Quotas */ void isc_mempool_setfreemax(isc_mempool_t *mpctx, unsigned int limit) { REQUIRE(VALID_MEMPOOL(mpctx)); if (mpctx->lock != NULL) LOCK(mpctx->lock); mpctx->freemax = limit; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); } unsigned int isc_mempool_getfreemax(isc_mempool_t *mpctx) { unsigned int freemax; REQUIRE(VALID_MEMPOOL(mpctx)); if (mpctx->lock != NULL) LOCK(mpctx->lock); freemax = mpctx->freemax; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); return (freemax); } unsigned int isc_mempool_getfreecount(isc_mempool_t *mpctx) { unsigned int freecount; REQUIRE(VALID_MEMPOOL(mpctx)); if (mpctx->lock != NULL) LOCK(mpctx->lock); freecount = mpctx->freecount; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); return (freecount); } void isc_mempool_setmaxalloc(isc_mempool_t *mpctx, unsigned int limit) { REQUIRE(limit > 0); REQUIRE(VALID_MEMPOOL(mpctx)); if (mpctx->lock != NULL) LOCK(mpctx->lock); mpctx->maxalloc = limit; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); } unsigned int isc_mempool_getmaxalloc(isc_mempool_t *mpctx) { unsigned int maxalloc; REQUIRE(VALID_MEMPOOL(mpctx)); if (mpctx->lock != NULL) LOCK(mpctx->lock); maxalloc = mpctx->maxalloc; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); return (maxalloc); } unsigned int isc_mempool_getallocated(isc_mempool_t *mpctx) { unsigned int allocated; REQUIRE(VALID_MEMPOOL(mpctx)); if (mpctx->lock != NULL) LOCK(mpctx->lock); allocated = mpctx->allocated; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); return (allocated); } void isc_mempool_setfillcount(isc_mempool_t *mpctx, unsigned int limit) { REQUIRE(limit > 0); REQUIRE(VALID_MEMPOOL(mpctx)); if (mpctx->lock != NULL) LOCK(mpctx->lock); mpctx->fillcount = limit; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); } unsigned int isc_mempool_getfillcount(isc_mempool_t *mpctx) { unsigned int fillcount; REQUIRE(VALID_MEMPOOL(mpctx)); if (mpctx->lock != NULL) LOCK(mpctx->lock); fillcount = mpctx->fillcount; if (mpctx->lock != NULL) UNLOCK(mpctx->lock); return (fillcount); } void isc_mem_printactive(isc_mem_t *ctx, FILE *file) { REQUIRE(VALID_CONTEXT(ctx)); REQUIRE(file != NULL); #if !ISC_MEM_TRACKLINES UNUSED(ctx); UNUSED(file); #else print_active(ctx, file); #endif } void isc_mem_printallactive(FILE *file) { #if !ISC_MEM_TRACKLINES UNUSED(file); #else isc_mem_t *ctx; RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); LOCK(&lock); for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; ctx = ISC_LIST_NEXT(ctx, link)) { fprintf(file, "context: %p\n", ctx); print_active(ctx, file); } UNLOCK(&lock); #endif } void isc_mem_checkdestroyed(FILE *file) { RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); LOCK(&lock); if (!ISC_LIST_EMPTY(contexts)) { #if ISC_MEM_TRACKLINES isc_mem_t *ctx; for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; ctx = ISC_LIST_NEXT(ctx, link)) { fprintf(file, "context: %p\n", ctx); print_active(ctx, file); } fflush(file); #endif INSIST(0); } UNLOCK(&lock); } #ifdef HAVE_LIBXML2 typedef struct summarystat { isc_uint64_t total; isc_uint64_t inuse; isc_uint64_t blocksize; isc_uint64_t contextsize; } summarystat_t; static void renderctx(isc_mem_t *ctx, summarystat_t *summary, xmlTextWriterPtr writer) { REQUIRE(VALID_CONTEXT(ctx)); xmlTextWriterStartElement(writer, ISC_XMLCHAR "context"); xmlTextWriterStartElement(writer, ISC_XMLCHAR "id"); xmlTextWriterWriteFormatString(writer, "%p", ctx); xmlTextWriterEndElement(writer); /* id */ if (ctx->name[0] != 0) { xmlTextWriterStartElement(writer, ISC_XMLCHAR "name"); xmlTextWriterWriteFormatString(writer, "%s", ctx->name); xmlTextWriterEndElement(writer); /* name */ } REQUIRE(VALID_CONTEXT(ctx)); MCTXLOCK(ctx, &ctx->lock); summary->contextsize += sizeof(*ctx) + (ctx->max_size + 1) * sizeof(struct stats) + ctx->max_size * sizeof(element *) + ctx->basic_table_count * sizeof(char *); #if ISC_MEM_TRACKLINES if (ctx->debuglist != NULL) { summary->contextsize += (ctx->max_size + 1) * sizeof(debuglist_t) + ctx->debuglistcnt * sizeof(debuglink_t); } #endif xmlTextWriterStartElement(writer, ISC_XMLCHAR "references"); xmlTextWriterWriteFormatString(writer, "%d", ctx->references); xmlTextWriterEndElement(writer); /* references */ summary->total += ctx->total; xmlTextWriterStartElement(writer, ISC_XMLCHAR "total"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", (isc_uint64_t)ctx->total); xmlTextWriterEndElement(writer); /* total */ summary->inuse += ctx->inuse; xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", (isc_uint64_t)ctx->inuse); xmlTextWriterEndElement(writer); /* inuse */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxinuse"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", (isc_uint64_t)ctx->maxinuse); xmlTextWriterEndElement(writer); /* maxinuse */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "blocksize"); if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { summary->blocksize += ctx->basic_table_count * NUM_BASIC_BLOCKS * ctx->mem_target; xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", (isc_uint64_t) ctx->basic_table_count * NUM_BASIC_BLOCKS * ctx->mem_target); } else xmlTextWriterWriteFormatString(writer, "%s", "-"); xmlTextWriterEndElement(writer); /* blocksize */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "pools"); xmlTextWriterWriteFormatString(writer, "%u", ctx->poolcnt); xmlTextWriterEndElement(writer); /* pools */ summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t); xmlTextWriterStartElement(writer, ISC_XMLCHAR "hiwater"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", (isc_uint64_t)ctx->hi_water); xmlTextWriterEndElement(writer); /* hiwater */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", (isc_uint64_t)ctx->lo_water); xmlTextWriterEndElement(writer); /* lowater */ MCTXUNLOCK(ctx, &ctx->lock); xmlTextWriterEndElement(writer); /* context */ } void isc_mem_renderxml(xmlTextWriterPtr writer) { isc_mem_t *ctx; summarystat_t summary; isc_uint64_t lost; memset(&summary, 0, sizeof(summary)); xmlTextWriterStartElement(writer, ISC_XMLCHAR "contexts"); RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); LOCK(&lock); lost = totallost; for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; ctx = ISC_LIST_NEXT(ctx, link)) { renderctx(ctx, &summary, writer); } UNLOCK(&lock); xmlTextWriterEndElement(writer); /* contexts */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "summary"); xmlTextWriterStartElement(writer, ISC_XMLCHAR "TotalUse"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", summary.total); xmlTextWriterEndElement(writer); /* TotalUse */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "InUse"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", summary.inuse); xmlTextWriterEndElement(writer); /* InUse */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "BlockSize"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", summary.blocksize); xmlTextWriterEndElement(writer); /* BlockSize */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "ContextSize"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", summary.contextsize); xmlTextWriterEndElement(writer); /* ContextSize */ xmlTextWriterStartElement(writer, ISC_XMLCHAR "Lost"); xmlTextWriterWriteFormatString(writer, "%" ISC_PRINT_QUADFORMAT "u", lost); xmlTextWriterEndElement(writer); /* Lost */ xmlTextWriterEndElement(writer); /* summary */ } #endif /* HAVE_LIBXML2 */