Open-Source Wikis

/

nginx

/

Primitives

/

Pool (`ngx_pool_t`)

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 from d.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 via ngx_alloc (an unwrapped malloc) and link the result onto pool->large so 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 descriptor
  • ngx_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 failed increments (which trigger current advancement).
  • 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 sizing
  • client_body_buffer_size — body pool
  • connection_pool_sizec->pool
  • request_pool_sizer->pool

Defaults are sensible; tuning is rarely necessary.

Why not just malloc?

Three reasons:

  1. Speed. A bump allocator is one increment + one bounds check. malloc is a hash table lookup or a free-list walk plus locking.
  2. Simplicity for callers. "Allocate, never free" matches the request-scoped lifetime of most allocations. No leak chasing.
  3. 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->pool for 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->pool after ngx_http_finalize_request has 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_pcalloc allocates and zeros, so don't memset after.

Cross-references

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Pool (`ngx_pool_t`) – nginx wiki | Factory