-
Notifications
You must be signed in to change notification settings - Fork 3
kithara bufpool
Contract reviewed from PR #341 and PR #428 (ring section), source revision f2a105155. This records that branch contract, not merged release status or new runtime validation. API and usage · All crates.
PoolRegion<S> owns one immutable schema and one shared RegionBudget.
Cloning the facade clones that owner handle; it does not create another pool or
counter. Composition roots declare concrete schemas with pool_schema! and
register every slot through the generated typestate builder. Missing,
duplicate, unknown, and unsupported registrations fail to compile.
Lower crates accept PoolRegion<S> with the narrow S: HasPool<K> bounds they
need. PoolAlias<Tag, K> names a distinct physical slot with an existing key's
element and guard policy; aliases do not share a free list or per-pool counter.
VecKey<T, N> and StringKey<N> are the sealed extension points for
crate-owned vector and UTF-8 aliases; downstream crates cannot implement raw
key plumbing or detach a slot from its region.
The public macro must expose construction plumbing to downstream expansion. Every slot therefore carries its region provenance, checked on every dispatch. Raw key operations additionally require an unforgeable crate-owned capability. Safe downstream code cannot detach a core or attach a slot to another region's reported budget.
With the test-utils feature, testing::TestPools provides the same byte and
sample capabilities as the application composition roots. Each test harness
builds one isolated region from that schema and passes the facade through the
subsystems it composes; tests do not declare narrower per-crate schemas or share
one process-global region.
The built-in byte key uses 32 shards and the sample key uses 8. Generic vector
and string keys state their shard count in the key type. Each shard is a bounded
crossbeam_queue::ArrayQueue; acquisition and return take no lock.
-
getprobes the calling thread's shard, up to four neighbouring shards, and the optional cold-start queue. A complete miss returns a fresh empty guard. - Checked growth reserves capacity against both hard budgets before allocation, reconciles allocator capacity, and rolls both counters back on failure. If a new reservation cannot fit, growth makes one bounded pass over every shard and then the cold-start queue for an already charged suitable allocation. A remaining pool-cap rejection evicts idle allocations from that slot; an overall-cap rejection evicts idle allocations across registered slots before one final admission attempt.
- Guard drop clears its vector or string, applies the configured trim or strict retained-capacity ceiling, and returns it to the home shard. A rejected return drops the allocation and releases its complete charge.
max_buffers is divided across shards and each shard is capped at 1024 retained
values. It must provide at least one slot per shard. initial_buffers and
initial_capacity allocate payloads during construction; any failure drops all
earlier slots and no region is published.
The region freezes a weak inventory of slot reclaimers only after the complete schema builds. The inventory cannot keep a slot alive, and pressure reclamation never reaches a checked-out guard.
OverallBudget is the hard cap shared by every slot in a region. A slot's
max_share is an additional hard ceiling, not a reservation or partition: two
slots may both use Percent::MAX and compete for the same peak capacity.
Accounting measures Vec capacity multiplied by element size and String
capacity in bytes. It deliberately does not claim to measure RSS, allocator
metadata, or the temporary old-plus-new allocation during transactional growth.
Every retained or checked-out pooled allocation remains charged until trimming
or dropping returns bytes.
Growth chooses one amortized target, clamped to the capacity currently available under both counters. This keeps incremental writes linear without an unchecked exact-fit fallback. A budget rejection may discard bounded idle retention and repeat admission once for each hard counter it encounters; a concurrent reservation may still make that attempt fail immediately. On any error, the original buffer and both counters stay unchanged.
ByteBuffer, SampleBuffer, and PooledVec dereference to slices, never raw
Vecs. Built-in capacity grows through ensure_len and
try_extend_from_slice; generic vectors use try_push and try_extend.
PooledString keeps UTF-8 validity in String and grows through
try_push_str. There is no public raw growth, extraction, attach, recycle,
collect, or pre-warm contract.
ByteBuffer::renew returns its held allocation to the slot and continues with
another empty guard, so a long-lived owner can release active capacity for
region-wide reclaim. ByteBuffer::normalize instead clears and normalizes the
held allocation in place. It applies the same retained-capacity policy as guard
return and lets long-lived scratch reuse one bounded allocation.
PoolRegion::ring::<K>(capacity) builds a cross-thread SPSC ring on one
pooled buffer through kithara-ring. The slots are acquired with
get_with_len, so they count against the slot's max_share and the region's
OverallBudget like any other checked-out guard, and acquisition fails with
the usual budget errors instead of allocating past them. The buffer returns to
its slot only when both halves have dropped. Zero capacity is refused with
PoolError::EmptyRing. Rings that move decoded PCM between threads (analysis
ingress, broadcast, live recording) take their slots this way; a heap-backed
ringbuf ring bypasses the budget.
stats() reports the shared region budget. pool_stats::<K>() reports reuse for
one registered generic slot; built-in hot-path keys compile out counter updates.
PoolConfigPatch is the second way into PoolConfig: a configuration document
types into it and apply writes only the fields the document names, leaving
the rest of the built policy standing. Every field is patchable — this config
holds only numbers and a Percent, no live handle a document could not name.
There is no patch type for a region. pool_schema! generates a region type per
consumer, so no shared type exists to derive on; the section naming a
particular application's pools belongs beside that application's schema
invocation: the region's byte budget, then one field per pool it declares.
Percent derives Ranged with a private field and bounds 0..=100.
checked and the generated Deserialize refuse invalid shares; document
errors name the offending key. The default is Percent::MAX. Region
construction receives a valid percentage, so pool_limit only computes its
byte ceiling.