CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
Buffer Pool

Functions

void bufpoolInit (BufPool *pool, size_t bufsz, uint32 initial, uint32 max)
 
Buffer bufpoolGet (BufPool *pool)
 
void bufpoolPut (BufPool *pool, Buffer *buf)
 
uint32 bufpoolInUse (BufPool *pool)
 
void bufpoolCollect (BufPool *pool)
 
void bufpoolDestroy (BufPool *pool)
 

Detailed Description

A thread-safe freelist of fixed-size buffers, for code paths that need to acquire and release buffers continuously and cannot afford to allocate on every operation.

The motivating case is asynchronous I/O. Completion-based APIs require the caller to own a receive buffer for the entire duration of an operation, so a busy socket needs a supply of them at all times; allocating and freeing per packet puts the allocator on the hot path and fragments the heap. A pool turns that into a pop and a push on a lock-free queue, with zero allocation in steady state.

The pool grows on demand up to a configured cap and never grows past it. Once the cap is reached and no buffers are free, bufpoolGet() returns NULL and the caller decides what to do. This is deliberate: an unbounded pool converts a transient overload into memory exhaustion, and does it fastest under exactly the flood that is least deserving of the help. max * bufsz is the pool's entire memory ceiling and should be a number that can be stated up front.

Callers must handle a NULL return. What that means is protocol-dependent – a datagram receiver can simply drop the packet and count it, while a stream receiver must instead stop posting receives and let the transport's own flow control apply the backpressure.

Note
All operations are thread-safe. Buffers may be acquired on one thread and released on another.

Example:

BufPool pool;
bufpoolInit(&pool, 2048, 64, 4096); // 2KB buffers, 64 preallocated, 4096 max
Buffer buf = bufpoolGet(&pool);
if (!buf) {
++dropped; // at the cap; shed load rather than allocate
} else {
// ... use buf ...
bufpoolPut(&pool, &buf); // buf is NULL after this
}
void bufpoolPut(BufPool *pool, Buffer *buf)
Buffer bufpoolGet(BufPool *pool)
void bufpoolInit(BufPool *pool, size_t bufsz, uint32 initial, uint32 max)
void bufpoolDestroy(BufPool *pool)

Function Documentation

◆ bufpoolCollect()

void bufpoolCollect ( BufPool *  pool)

Run a garbage-collection pass on the pool's internal freelist.

The freelist is a dynamic queue that grows on demand up to the cap and reclaims that growth only lazily, when this runs. Call it periodically from a thread that is otherwise idle so the pool can shrink back after a burst rather than holding its high-water footprint forever. It is non-blocking: if another thread is already collecting, or there is nothing to reclaim, it returns at once. Purely an optimization – a pool that never collects still works correctly.

Parameters
poolPointer to the pool

◆ bufpoolDestroy()

void bufpoolDestroy ( BufPool *  pool)

Destroy a buffer pool and free all pooled buffers.

All buffers must have been returned first. Any that are still checked out are leaked, since the pool has no way to reach them.

Parameters
poolPointer to the pool to destroy

◆ bufpoolGet()

Buffer bufpoolGet ( BufPool *  pool)

Acquire a buffer from the pool.

Returns a buffer of the pool's configured size with len reset to 0. Reuses a free buffer if one is available, otherwise allocates a new one, unless doing so would exceed the cap.

Parameters
poolPointer to the pool
Returns
A buffer, or NULL if the pool is at its cap with none free
Note
The returned buffer must not be resized. Return it with bufpoolPut(); destroying it with bufDestroy() will permanently shrink the pool's effective capacity.

◆ bufpoolInit()

void bufpoolInit ( BufPool *  pool,
size_t  bufsz,
uint32  initial,
uint32  max 
)

Initialize a buffer pool.

Preallocates initial buffers so that a burst of traffic immediately after startup does not pay for allocation. The pool will grow beyond that on demand, but never past max.

Sizing guidance: the pool needs to cover the in-flight window – roughly the number of outstanding operations plus whatever is queued awaiting processing – with headroom. Under-sizing shows up as failed bufpoolGet() calls, which is why the caller should always count them. Over-sizing costs only address space.

Parameters
poolPointer to the pool to initialize
bufszSize in bytes of each buffer in the pool
initialNumber of buffers to preallocate
maxMaximum number of live buffers, or 0 for no limit

◆ bufpoolInUse()

uint32 bufpoolInUse ( BufPool *  pool)

Get the number of buffers currently checked out of the pool.

Intended for diagnostics. The value is a snapshot and may be stale the moment it is read.

Parameters
poolPointer to the pool
Returns
Approximate number of buffers currently in use

◆ bufpoolPut()

void bufpoolPut ( BufPool *  pool,
Buffer buf 
)

Return a buffer to the pool.

Ownership transfers back to the pool and the caller's pointer is set to NULL. If the freelist cannot accept the buffer, it is destroyed instead; this is always safe, it only means the pool will allocate again later.

Parameters
poolPointer to the pool
bufPointer to the buffer to return (set to NULL on return)