|
CX Framework
Cross-platform C utility framework
|
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) |
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.
Example:
| 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.
| pool | Pointer to the pool |
| 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.
| pool | Pointer to the pool to destroy |
| 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.
| pool | Pointer to the pool |
| 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.
| pool | Pointer to the pool to initialize |
| bufsz | Size in bytes of each buffer in the pool |
| initial | Number of buffers to preallocate |
| max | Maximum number of live buffers, or 0 for no limit |
| 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.
| pool | Pointer to the pool |
| 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.
| pool | Pointer to the pool |
| buf | Pointer to the buffer to return (set to NULL on return) |