/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <nxt_main.h>
/*
* The pool allocator provides cheap allocation of small objects.
* The objects are allocated from larger preallocated chunks.
*
* aligned and non-aligned allocations,
* cache of reusable objects, lvlhsh-specific cache
* eliminating align padding
* data locality
* freeing on pool destruction
* freeing large allocations
*/
static void *nxt_mem_pool_align(nxt_mem_pool_t *mp, size_t alignment,
size_t size);
static void *nxt_mem_pool_ext(nxt_mem_pool_t *mp, size_t size);
static nxt_mem_pool_chunk_t *nxt_mem_pool_next_chunk(nxt_mem_pool_t *mp,
nxt_mem_pool_chunk_t *chunk);
static nxt_mem_pool_chunk_t *nxt_mem_pool_chunk(nxt_mem_pool_t *mp);
static void *nxt_mem_lvlhsh_alloc_chunk(nxt_mem_pool_cache_t *cache,
size_t size, nxt_uint_t nalloc);
#if (NXT_DEBUG)
static nxt_bool_t
nxt_mem_pool_thread_is_invalid(nxt_mem_pool_t *mp)
{
nxt_tid_t tid;
nxt_thread_t *thr;
thr = nxt_thread();
tid = nxt_thread_tid(thr);
if (nxt_slow_path(mp->tid != tid)) {
if (mp->pid == nxt_pid) {
nxt_log_alert(thr->log, "mem_pool locked by thread %PT", mp->tid);
nxt_abort();
return 1;
}
mp->pid = nxt_pid;
mp->tid = tid;
}
return 0;
}
/* SunC does not support C99 variadic macro with empty __VA_ARGS__. */
#define \
nxt_mem_pool_thread_assert(mp) \
if (nxt_mem_pool_thread_is_invalid(mp)) \
return
#define \
nxt_mem_pool_thread_assert_return(mp, ret) \
if (nxt_mem_pool_thread_is_invalid(mp)) \
return ret
#else /* !(NXT_DEBUG) */
#define \
nxt_mem_pool_thread_assert(mp)
#define \
nxt_mem_pool_thread_assert_return(mp, ret)
#endif
nxt_mem_pool_t *
nxt_mem_pool_create(size_t size)
{
u_char *p;
size_t min_ext_size;
nxt_mem_pool_t *mp;
mp = nxt_malloc(size);
if (nxt_fast_path(mp != NULL)) {
mp->chunk_size = (uint32_t) size;
min_ext_size = size - sizeof(nxt_mem_pool_t) + 1;
mp->min_ext_size = (uint32_t) nxt_min(min_ext_size,
NXT_MEM_POOL_MIN_EXT_SIZE);
nxt_malloc_usable_size(mp, size);
p = (u_char *) mp;
mp->chunk.free = p + sizeof(nxt_mem_pool_t);
mp->chunk.end = p + size;
mp->chunk.next = NULL;
mp->chunk.fails = 0;
mp->current = &mp->chunk;
mp->ext = NULL;
mp->cleanup = NULL;
mp->cache = NULL;
nxt_thread_log_debug("mem pool chunk size:%uz avail:%uz",
size, mp->chunk.end - mp->chunk.free);
nxt_mem_pool_debug_lock(mp, nxt_thread_tid(NULL));
}
return mp;
}
void
nxt_mem_pool_destroy(nxt_mem_pool_t *mp)
{
nxt_task_t *task;
nxt_mem_pool_ext_t *ext;
nxt_mem_pool_chunk_t *chunk, *next;
nxt_mem_pool_cleanup_t *mpcl;
task = NULL;
nxt_mem_pool_thread_assert(mp);
for (mpcl = mp->cleanup; mpcl != NULL; mpcl = mpcl->next) {
if (mpcl->handler != NULL) {
nxt_thread_log_debug("mem pool cleanup: %p", mpcl);
mpcl->handler(task, mpcl->data);
}
}
for (ext = mp->ext; ext != NULL; ext = ext->next) {
if (ext->data != NULL) {
nxt_free(ext->data);
}
}
chunk = &mp->chunk;
do {
nxt_thread_log_debug("mem pool chunk fails:%uD unused:%uz",
chunk->fails, chunk->end - chunk->free);
next = chunk->next;
nxt_free(chunk);
chunk = next;
} while (chunk != NULL);
}
void *
nxt_mem_align(nxt_mem_pool_t *mp, size_t alignment, size_t size)
{
nxt_mem_pool_thread_assert_return(mp, NULL);
if (nxt_fast_path(size < mp->min_ext_size)) {
return nxt_mem_pool_align(mp, alignment, size);
}
return nxt_mem_pool_ext(mp, size);
}
void *
nxt_mem_zalign(nxt_mem_pool_t *mp, size_t alignment, size_t size)
{
void *p;
p = nxt_mem_align(mp, alignment, size);
if (nxt_fast_path(p != NULL)) {
nxt_memzero(p, size);
}
return p;
}
/*
* Zero-filled aligned allocation, suitable for struct
* allocation without long double and SIMD values.
*/
void *
nxt_mem_zalloc(nxt_mem_pool_t *mp, size_t size)
{
void *p;
p = nxt_mem_alloc(mp, size);
if (nxt_fast_path(p != NULL)) {
nxt_memzero(p, size);
}
return p;
}
void *
nxt_mem_buf(nxt_mem_pool_t *mp, size_t *sizep, nxt_uint_t flags)
{
u_char *p;
size_t size;
nxt_mem_pool_thread_assert_return(mp, NULL);
size = *sizep;
if (nxt_fast_path(size >= mp->min_ext_size)) {
nxt_malloc_cutback(flags & NXT_MEM_BUF_CUTBACK, size);
/* Windows only: try to minimize number of allocated pages. */
p = nxt_mem_pool_ext(mp, size);
if (p != NULL) {
if (flags & NXT_MEM_BUF_USABLE) {
nxt_malloc_usable_size(p, size);
}
*sizep = size;
}
return p;
}
return nxt_mem_pool_align(mp, NXT_ALIGNMENT, size);
}
/* Non-aligned allocation, suitable for string allocation. */
void *
nxt_mem_nalloc(nxt_mem_pool_t *mp, size_t size)
{
u_char *p;
nxt_mem_pool_chunk_t *chunk;
nxt_mem_pool_thread_assert_return(mp, NULL);
if (nxt_slow_path(size >= mp->min_ext_size)) {
return nxt_mem_pool_ext(mp, size);
}
chunk = mp->current;
for ( ;; ) {
p = chunk->end - size;
if (nxt_fast_path(p >= chunk->free)) {
chunk->end = p;
return p;
}
chunk = nxt_mem_pool_next_chunk(mp, chunk);
if (nxt_slow_path(chunk == NULL)) {
return NULL;
}
}
}
/* An attempt to deallocate a large allocation outside pool. */
nxt_int_t
nxt_mem_free(nxt_mem_pool_t *mp, void *p)
{
nxt_mem_pool_ext_t *ext;
nxt_mem_pool_thread_assert_return(mp, NXT_DECLINED);
for (ext = mp->ext; ext != NULL; ext = ext->next) {
if (p == ext->data) {
nxt_free(ext->data);
ext->data = NULL;
return NXT_OK;
}
}
return NXT_DECLINED;
}
static void *
nxt_mem_pool_ext(nxt_mem_pool_t *mp, size_t size)
{
void *p;
nxt_mem_pool_ext_t *ext;
ext = nxt_mem_pool_align(mp, sizeof(void *), sizeof(nxt_mem_pool_ext_t));
if (nxt_fast_path(ext != NULL)) {
p = nxt_malloc(size);
if (nxt_fast_path(p != NULL)) {
ext->data = p;
ext->next = mp->ext;
mp->ext = ext;
return p;
}
}
return NULL;
}
static void *
nxt_mem_pool_align(nxt_mem_pool_t *mp, size_t alignment, size_t size)
{
u_char *p, *f;
nxt_mem_pool_chunk_t *chunk;
chunk = mp->current;
for ( ;; ) {
p = nxt_align_ptr(chunk->free, alignment);
f = p + size;
if (nxt_fast_path(f <= chunk->end)) {
chunk->free = f;
return p;
}
chunk = nxt_mem_pool_next_chunk(mp, chunk);
if (nxt_slow_path(chunk == NULL)) {
return NULL;
}
}
}
static nxt_mem_pool_chunk_t *
nxt_mem_pool_next_chunk(nxt_mem_pool_t *mp, nxt_mem_pool_chunk_t *chunk)
{
nxt_bool_t full;
full = (chunk->free == chunk->end || chunk->fails++ > 10);
chunk = chunk->next;
if (chunk == NULL) {
chunk = nxt_mem_pool_chunk(mp);
if (nxt_slow_path(chunk == NULL)) {
return NULL;
}
}
if (full) {
mp->current = chunk;
}
return chunk;
}
static nxt_mem_pool_chunk_t *
nxt_mem_pool_chunk(nxt_mem_pool_t *mp)
{
u_char *p;
size_t size;
nxt_mem_pool_chunk_t *ch, *chunk;
size = mp->chunk_size;
chunk = nxt_malloc(size);
if (nxt_fast_path(chunk != NULL)) {
nxt_malloc_usable_size(chunk, size);
p = (u_char *) chunk;
chunk->free = p + sizeof(nxt_mem_pool_chunk_t);
chunk->end = p + size;
chunk->next = NULL;
chunk->fails = 0;
for (ch = mp->current; ch->next; ch = ch->next) { /* void */ }
ch->next = chunk;
}
return chunk;
}
nxt_mem_pool_cleanup_t *
nxt_mem_pool_cleanup(nxt_mem_pool_t *mp, size_t size)
{
nxt_mem_pool_cleanup_t *mpcl;
nxt_mem_pool_thread_assert_return(mp, NULL);
mpcl = nxt_mem_pool_align(mp, sizeof(void *),
sizeof(nxt_mem_pool_cleanup_t));
if (nxt_fast_path(mpcl != NULL)) {
mpcl->handler = NULL;
mpcl->data = NULL;
if (size != 0) {
mpcl->data = nxt_mem_alloc(mp, size);
if (nxt_slow_path(mpcl->data == NULL)) {
return NULL;
}
}
mpcl->next = mp->cleanup;
mp->cleanup = mpcl;
nxt_thread_log_debug("mem pool cleanup add: %p", mpcl);
}
return mpcl;
}
/* Allocation of reusable object with specified size. */
void *
nxt_mem_cache_alloc0(nxt_mem_pool_t *mp, size_t size)
{
void **pp;
nxt_mem_pool_cache_t *cache;
nxt_mem_pool_thread_assert_return(mp, NULL);
for (cache = mp->cache; cache != NULL; cache = cache->next) {
if (cache->size == size && cache->nalloc == 0) {
if (cache->free != NULL) {
pp = cache->free;
cache->free = *pp;
return pp;
}
break;
}
}
return nxt_mem_alloc(mp, size);
}
void *
nxt_mem_cache_zalloc0(nxt_mem_pool_t *mp, size_t size)
{
void *p;
p = nxt_mem_cache_alloc0(mp, size);
if (nxt_fast_path(p != NULL)) {
nxt_memzero(p, size);
}
return p;
}
/* Deallocation of reusable object with specified size. */
void
nxt_mem_cache_free0(nxt_mem_pool_t *mp, void *p, size_t size)
{
void **pp;
nxt_mem_pool_cache_t *cache, **pcache;
nxt_mem_pool_thread_assert(mp);
pp = p;
pcache = &mp->cache;
for (cache = mp->cache; cache != NULL; cache = cache->next) {
if (cache->size == size && cache->nalloc == 0) {
*pp = cache->free;
cache->free = p;
return;
}
pcache = &cache->next;
}
/* Non-lvlhash caches are created only on return. */
cache = nxt_mem_pool_align(mp, sizeof(void *),
sizeof(nxt_mem_pool_cache_t));
if (nxt_fast_path(cache != NULL)) {
*pp = NULL;
cache->size = (uint32_t) size;
cache->nalloc = 0;
cache->free = p;
cache->next = NULL;
*pcache = cache;
}
}
/*
* lvlhsh requires allocations aligned to a size of the allocations.
* This is not issue for slab-like allocators, but glibc allocator may
* waste memory on such aligned allocations. So nxt_mem_lvlhsh_alloc()
* allocates memory in chunks specified by the "nalloc" parameter
* except the first allocation. The first lvlhsh allocation is a bucket
* allocation and it is enough for small hashes and for early stage
* of a hash. By default lvlhsh uses 128-bytes buckets and levels.
* This allows to search up to 10 entries in one memory access and
* up to 160 entries in two memory accesses on 64-bit platform.
* And on 32-bit platform up to 15 entries and up to 480 entries
* respectively.
*
* After the bucket will be filled up with 10 64-bit entries or 15
* 32-bit entries, lvlhsh will expand it to a level and content
* of the first bucket will spread to the level's new buckets.
* Number of the new buckets may be up to 11 on 64-bit or 16 on 32-bit
* platforms. It's better to allocate them together to eliminate
* wasting memory and CPU time.
*
* The "nalloc" should be 16 if bucket size is 128 bytes.
*/
/* Allocation of lvlhsh level or bucket with specified size. */
void *
nxt_mem_lvlhsh_alloc(void *ctx, size_t size, nxt_uint_t nalloc)
{
void *p, **pp;
nxt_mem_pool_t *mp;
nxt_mem_pool_cache_t *cache;
mp = ctx;
nxt_mem_pool_thread_assert_return(mp, NULL);
for (cache = mp->cache; cache != NULL; cache = cache->next) {
if (cache->size == size && cache->nalloc != 0) {
if (cache->free != NULL) {
pp = cache->free;
cache->free = *pp;
return pp;
}
return nxt_mem_lvlhsh_alloc_chunk(cache, size, nalloc);
}
}
cache = nxt_mem_pool_align(mp, sizeof(void *),
sizeof(nxt_mem_pool_cache_t));
if (nxt_fast_path(cache != NULL)) {
p = nxt_memalign(size, size);
if (nxt_fast_path(p != NULL)) {
cache->size = (uint32_t) size;
cache->nalloc = nalloc;
cache->free = NULL;
cache->next = mp->cache;
mp->cache = cache;
return p;
}
}
return NULL;
}
static void *
nxt_mem_lvlhsh_alloc_chunk(nxt_mem_pool_cache_t *cache, size_t size,
nxt_uint_t nalloc)
{
char *m, *p, *end;
void **pp;
size_t n;
n = (nalloc == 0) ? 1 : nalloc;
n *= size;
m = nxt_memalign(size, n);
if (nxt_fast_path(m != NULL)) {
pp = &cache->free;
end = m + n;
for (p = m + size; p < end; p = p + size) {
*pp = p;
pp = (void **) p;
}
*pp = NULL;
}
return m;
}
/* Deallocation of lvlhsh level or bucket with specified size. */
void
nxt_mem_lvlhsh_free(void *ctx, void *p, size_t size)
{
void **pp;
nxt_mem_pool_t *mp;
nxt_mem_pool_cache_t *cache;
mp = ctx;
nxt_mem_pool_thread_assert(mp);
pp = p;
for (cache = mp->cache; cache != NULL; cache = cache->next) {
if (cache->size == size && cache->nalloc != 0) {
*pp = cache->free;
cache->free = p;
return;
}
}
}