Open-Source Wikis

/

nginx

/

Primitives

/

Buffer and chain (`ngx_buf_t`, `ngx_chain_t`)

nginx/nginx

Buffer and chain (ngx_buf_t, ngx_chain_t)

Active contributors: Maxim Dounin, Sergey Kandaurov

What they are

A buffer is a span of bytes plus metadata about where those bytes live, what they mean, and what should happen after they're sent. A chain is a linked-list link of buffers. Output flowing through nginx travels as a ngx_chain_t * — the filter chain takes a chain in, transforms it, and passes a chain out. This is the universal data-movement primitive.

Definition

src/core/ngx_buf.h:

struct ngx_buf_s {
    u_char          *pos;         /* current read position */
    u_char          *last;        /* one past the last valid byte */
    off_t            file_pos;
    off_t            file_last;

    u_char          *start;       /* allocation start */
    u_char          *end;         /* allocation end */
    ngx_buf_tag_t    tag;         /* who allocated me (for buffer reuse) */
    ngx_file_t      *file;
    ngx_buf_t       *shadow;

    /* the buffer can be in memory */
    unsigned         temporary:1; /* writable scratch */
    unsigned         memory:1;    /* read-only memory (e.g., a literal) */
    unsigned         mmap:1;      /* mmap'd region */
    unsigned         recycled:1;  /* buffer can be reused after sending */
    unsigned         in_file:1;   /* read from file at file_pos..file_last */
    unsigned         flush:1;     /* flush after this buffer */
    unsigned         sync:1;      /* sync buffer */
    unsigned         last_buf:1;  /* last buffer in this stream */
    unsigned         last_in_chain:1;

    unsigned         last_shadow:1;
    unsigned         temp_file:1;

    int              num;
};

struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

Buffer flavors

A single ngx_buf_t can represent any of:

  • In-memory, read-only — memory=1 (a config-time string literal, an HTTP status line)
  • In-memory, scratch — temporary=1 (allocated for header serialization)
  • mmap'd file regionmmap=1 (rare; used by some cache paths)
  • File-backedin_file=1 with file, file_pos, file_last (a static file being served)
  • In-memory + in-file — both bits set; the in-memory part is the file's currently-buffered chunk

The send-chain machinery handles each correctly: in-memory buffers get writev'd; file buffers get sendfile'd; mmap'd is treated like memory.

What the flags mean

Flag Semantics
last_buf The terminal buffer of the response. After sending this, finalize.
last_in_chain The terminal buffer of the current chain (subrequest output, partial flush). Not necessarily the last buffer of the response.
flush After sending, flush the socket. Used for SSE / progressive responses.
sync Empty buffer used purely as a marker — no bytes to send, but signals state to the next filter.
recycled After being sent, the buffer's storage is reusable (return to a free chain pool).
temp_file The file is a temp file owned by nginx (created by client_body_temp_path etc.). Delete on cleanup.

Reading buffers

A buffer with content has bytes at pos..last. After consuming n bytes, the consumer advances b->pos += n. When b->pos == b->last, the buffer is exhausted.

Filters that consume a chain typically loop:

for (cl = in; cl; cl = cl->next) {
    n = ngx_buf_size(cl->buf);
    /* ... do stuff with cl->buf->pos[0..n] ... */
}

ngx_buf_size(b) is (b->in_file ? (b->file_last - b->file_pos) : (b->last - b->pos)) — works whichever the buffer is.

Writing buffers

To produce output:

  1. Allocate a buffer (typically from r->pool or c->pool):

    ngx_buf_t  *b = ngx_create_temp_buf(pool, size);

    This sets b->start = b->pos = b->last = pool_alloc; b->end = ... + size; b->temporary = 1.

  2. Write bytes:

    ngx_memcpy(b->last, src, n);
    b->last += n;
  3. Wrap in a chain:

    ngx_chain_t  *cl = ngx_alloc_chain_link(pool);
    cl->buf  = b;
    cl->next = NULL;
  4. Pass it down: return ngx_http_output_filter(r, cl);.

For static file responses: build a buffer with in_file = 1, file = ..., file_pos = 0, file_last = stat.st_size, last_buf = 1. The send-chain machinery will sendfile() it.

Chain pools

Allocating a fresh chain link per buffer is wasteful in tight loops. Modules use a per-pool free list:

  • pool->chain is a linked list of unused ngx_chain_t links.
  • ngx_alloc_chain_link(pool) pops from the free list or allocates fresh.
  • ngx_free_chain(pool, cl) pushes back to the free list.

This is a small optimization but matters for filters that build many short chains per request.

The output filter chain

When a content handler produces a chain, it calls ngx_http_output_filter(r, in). That kicks off the body filter chain (see systems/http/index). Each filter takes a chain, possibly transforms it (ngx_http_gzip_body_filter compresses, ngx_http_chunked_body_filter adds chunked framing), and passes the result downward. The chain links may be rewritten — a single input buffer might fan out into multiple output buffers.

The bottom of the chain is ngx_http_write_filter, which packs as many bytes as possible into a writev/sendfile call and stores leftover in r->out for the next pass.

Buffer "tags"

b->tag is a void pointer used to identify "who owns this buffer." When a filter wants to recycle buffers, it walks the output r->out chain and reuses any buffer with its own tag. This avoids cross-module buffer corruption — if module A's buffer is sitting in module B's pending queue, B knows not to recycle it.

Convention: use the module's address as the tag, e.g., b->tag = (ngx_buf_tag_t) &ngx_http_my_module;.

Subrequest interaction

The postpone filter is the trickiest user of chains. A subrequest's output chain gets queued onto the parent's r->postponed list rather than going directly to the wire. When the subrequest finishes, the parent walks postponed in order and emits the correct interleaved output. See systems/http/request-lifecycle.

Pitfalls

  • Mutating b->pos advances the read cursor, not the buffer's identity. Don't change b->start after creation.
  • Setting last_buf on a non-final buffer terminates the response. A misplaced flag will silently truncate a body.
  • Don't share temporary buffers across requests — they live in the request pool and die with the request.
  • b->mmap = 1 is rare; most static-file paths go through in_file + sendfile, not mmap.

Cross-references

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

Buffer and chain (`ngx_buf_t`, `ngx_chain_t`) – nginx wiki | Factory