nginx/nginx
Pool (ngx_pool_t)
Active contributors: Maxim Dounin, Sergey Kandaurov
What it is
A pool is a bump-allocator with cleanup callbacks. You allocate from it; you can't free individual allocations; when the pool is destroyed, every allocation goes away at once and registered cleanup handlers run. nginx uses pools everywhere — there's a per-cycle pool, a per-connection pool, a per-request pool, a per-subrequest pool, plus many short-lived helper pools. This is why nginx hardly ever calls malloc() or free() in the request hot path.
The pool allocator is one of the oldest pieces of code in the codebase — first commit August 6, 2002, and the API has been stable since.
Definition
src/core/ngx_palloc.h:
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};
typedef struct {
u_char *last; /* current bump pointer */
u_char *end; /* end of this block's storage */
ngx_pool_t *next; /* next block (when full) */
ngx_uint_t failed; /* allocation-failure counter */
} ngx_pool_data_t;
struct ngx_pool_s {
ngx_pool_data_t d; /* current block's data */
size_t max; /* "small" allocation threshold */
ngx_pool_t *current; /* first block with room left */
ngx_chain_t *chain; /* free chain links */
ngx_pool_large_t *large; /* oversized allocations chain */
ngx_pool_cleanup_t *cleanup; /* cleanup handler chain */
ngx_log_t *log;
};How it allocates
Two paths based on size:
- Small (≤
pool->max): bump-allocate fromd.last. If the current block doesn't have room, walk the linked list of blocks and try each. If all are full, allocate a new block and link it. - Large (>
pool->max): allocate viangx_alloc(an unwrappedmalloc) and link the result ontopool->largeso it can be freed when the pool is destroyed.
ngx_palloc(pool, size) aligns the returned pointer to NGX_ALIGNMENT (typically 16 on amd64). ngx_pnalloc(pool, size) skips alignment. ngx_pcalloc(pool, size) zeroes.
Lifecycle
ngx_pool_t *pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, log);
/* ... allocate from it ... */
ngx_destroy_pool(pool);ngx_create_pool(size, log) mallocs one block of size size. The pool struct itself sits at the start of the block, so the first allocation is just a bump from &pool[1]. ngx_destroy_pool(pool) runs cleanups (LIFO), frees pool->large, then frees each block.
Cleanups
A cleanup is a callback that fires when the pool is destroyed:
ngx_pool_cleanup_t *cln;
cln = ngx_pool_cleanup_add(pool, sizeof(my_state_t));
cln->handler = my_cleanup_handler;
cln->data = (my_state_t *) cln->data; /* allocated for you */ngx_pool_cleanup_add(pool, size) allocates sizeof(ngx_pool_cleanup_t) + size from the pool, links the node onto pool->cleanup, and returns the node. The data field is a pointer to the trailing bytes you can use for state.
Cleanups run in LIFO order. They're how nginx ensures that, e.g., file descriptors are closed, regex contexts are freed, locks are released, before the pool's memory is freed.
There are several pre-built cleanup helpers:
ngx_pool_cleanup_file— close a file descriptorngx_pool_cleanup_delete_file— close + unlink (for temp files)- Module-specific helpers in
src/core/ngx_open_file_cache.c, etc.
The four common pool lifetimes
| Pool | Lives for | Created in |
|---|---|---|
cycle->pool |
Until the cycle ends (next reload / shutdown) | ngx_init_cycle |
c->pool |
Until the connection closes | ngx_get_connection |
r->pool |
Until the request finalizes | ngx_http_create_request |
| Subrequest pool | Until the subrequest finalizes | ngx_http_subrequest |
A handful of one-off pools also exist for things like the temporary configuration parse pool (cf->temp_pool), the resolver query pool, and the upstream subrequest pool.
Sizes and tuning
NGX_DEFAULT_POOL_SIZE is 16 KB. Most pools start there. The size is the size of each block, not a total cap — pools grow by chaining new blocks. The size matters for:
- Bump efficiency: a too-small pool means more block allocations and more
failedincrements (which triggercurrentadvancement). - Memory footprint: a too-large pool wastes memory on tiny requests.
Directives that tune pool sizes:
client_header_buffer_size/large_client_header_buffers— initial request pool sizingclient_body_buffer_size— body poolconnection_pool_size—c->poolrequest_pool_size—r->pool
Defaults are sensible; tuning is rarely necessary.
Why not just malloc?
Three reasons:
- Speed. A bump allocator is one increment + one bounds check.
mallocis a hash table lookup or a free-list walk plus locking. - Simplicity for callers. "Allocate, never free" matches the request-scoped lifetime of most allocations. No leak chasing.
- Cleanup batching. All cleanup handlers run together at pool destruction, which is the right granularity for things like "close all the files this request opened."
The cost is fragmentation across the per-pool blocks, and an inability to free individual allocations. nginx's allocation patterns make both of those non-issues.
Pitfalls
- Don't allocate from
c->poolfor state that should outlive the connection. Use a shared zone instead. - Don't pass pool pointers between threads. Pools are not thread-safe.
- Don't allocate from
r->poolafterngx_http_finalize_requesthas been called. The pool may already be destroyed (or about to be). Subrequest pools are tied to the subrequest's finalize, not the parent's. ngx_pcallocallocates and zeros, so don'tmemsetafter.
Cross-references
- systems/core — pool, slab, shared zones overview
- primitives/cycle —
cycle->pool - primitives/connection —
c->pool - primitives/request —
r->pool - primitives/buffer — buffers come from pools
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.