ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
buffer.c
Go to the documentation of this file.
1#include <ascii-chat/buffer.h>
2#include <ascii-chat/common.h>
3#include <libplatform/atomic.h>
4#include <libplatform/mutex.h>
5#include <libplatform/lifecycle.h>
8#include <string.h>
9#include <stdint.h>
10
11#if defined(__has_feature)
12#if __has_feature(address_sanitizer)
13#include <sanitizer/asan_interface.h>
14#define BUFFER_POISON(p, n) __asan_poison_memory_region((p), (n))
15#define BUFFER_UNPOISON(p, n) __asan_unpoison_memory_region((p), (n))
16#endif
17#endif
18#ifndef BUFFER_POISON
19#define BUFFER_POISON(p, n) ((void)0)
20#define BUFFER_UNPOISON(p, n) ((void)0)
21#endif
22
23/* Power-of-two bins through 4 MiB; larger payloads are budgeted but not cached. */
24#define BUFFER_BINS 17
25#define BUFFER_MIN_CAPACITY 64u
26#define BUFFER_MAX_CACHED ((size_t)4 * 1024 * 1024)
27
43
59
60static atomic_t process_bytes;
61static buffer_domain_t *default_domain;
62static buffer_domain_t *control_domain;
63static lifecycle_t default_lifecycle = LIFECYCLE_INIT;
64
65static bool process_reserve(size_t bytes, bool control) {
66 uint64_t current = atomic_load_u64(&process_bytes);
68 while (current <= limit && bytes <= limit - current) {
69 if (atomic_cas_u64(&process_bytes, &current, current + bytes))
70 return true;
71 }
72 return false;
73}
74
76 return (size_t)atomic_load_u64(&process_bytes);
77}
78
80 if (!budget || budget->bytes || !bytes)
81 return SET_ERRNO(ERROR_INVALID_PARAM, "Expected an empty buffer budget lease");
82 if (!process_reserve(bytes, false))
83 return SET_ERRNO(ERROR_MEMORY, "Process buffer budget exhausted reserving platform storage");
84 budget->bytes = bytes;
85 return ASCIICHAT_OK;
86}
87
89 if (budget && budget->bytes) {
90 atomic_fetch_sub_u64(&process_bytes, budget->bytes);
91 budget->bytes = 0;
92 }
93}
94
95/* The back pointer lives immediately before the aligned payload. It is never
96 * inspected for foreign allocations; those use their original deallocator. */
97static buffer_header_t *header(const void *data) {
98 return data ? ((buffer_header_t *const *)data)[-1] : NULL;
99}
100
101static void dispose(buffer_domain_t *domain, buffer_header_t *node) {
102 domain->stats.reserved_bytes -= node->charge;
103 atomic_fetch_sub_u64(&process_bytes, node->charge);
104 void *allocation = node->allocation;
105 SAFE_FREE(allocation);
106}
107
108static void trim_locked(buffer_domain_t *domain, bool all) {
109 uint64_t now = time_get_ns();
110 for (unsigned i = 0; i < BUFFER_BINS; i++) {
111 buffer_header_t **link = &domain->bins[i];
112 while (*link) {
113 buffer_header_t *node = *link;
114 if (all || now - node->returned_at >= domain->config.idle_ns) {
115 *link = node->next;
116 domain->stats.cached_bytes -= node->charge;
117 dispose(domain, node);
118 } else {
119 link = &node->next;
120 }
121 }
122 }
123}
124
126 if (!out)
127 return SET_ERRNO(ERROR_INVALID_PARAM, "Buffer domain output is NULL");
128 *out = NULL;
129 buffer_domain_config_t defaults = {BUFFER_DOMAIN_LIMIT, 32 * 1024 * 1024, 5000000000ULL, false};
130 if (!process_reserve(sizeof(buffer_domain_t), config && config->control))
131 return SET_ERRNO(ERROR_MEMORY, "Process buffer budget exhausted creating domain");
132 buffer_domain_t *domain = ALLOC_CALLOC(1, sizeof(*domain));
133 if (!domain) {
134 atomic_fetch_sub_u64(&process_bytes, sizeof(*domain));
135 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate buffer domain");
136 }
137 domain->config = config ? *config : defaults;
138 if (!domain->config.limit_bytes)
140 if (mutex_init(&domain->mutex,
141 domain->config.control ? "reserved_control_buffer_pool" : "shared_media_buffer_pool") != 0) {
142 atomic_fetch_sub_u64(&process_bytes, sizeof(*domain));
143 SAFE_FREE(domain);
144 return SET_ERRNO(ERROR_THREAD, "Cannot initialize buffer domain mutex");
145 }
146 *out = domain;
147 return ASCIICHAT_OK;
148}
149
150static void domain_dispose(buffer_domain_t *domain) {
151 mutex_destroy(&domain->mutex);
152 atomic_fetch_sub_u64(&process_bytes, sizeof(*domain));
153 SAFE_FREE(domain);
154}
155
157 if (!slot || !*slot)
158 return;
159 buffer_domain_t *domain = *slot;
160 *slot = NULL;
161 mutex_lock(&domain->mutex);
162 domain->closing = true;
163 trim_locked(domain, true);
164 bool done = domain->stats.outstanding == 0;
165 mutex_unlock(&domain->mutex);
166 if (done)
167 domain_dispose(domain);
168}
169
171 if (!domain)
172 return;
173 mutex_lock(&domain->mutex);
174 trim_locked(domain, false);
175 mutex_unlock(&domain->mutex);
176}
177
178asciichat_error_t buffer_acquire(buffer_domain_t *domain, size_t size, size_t alignment, void **out) {
179 if (!out)
180 return SET_ERRNO(ERROR_INVALID_PARAM, "Buffer output is NULL");
181 *out = NULL;
182 if (!alignment)
183 alignment = BUFFER_DEFAULT_ALIGNMENT;
184 if (alignment < sizeof(void *) || (alignment & (alignment - 1)) || alignment > 4096)
185 return SET_ERRNO(ERROR_INVALID_PARAM, "Buffer alignment must be a power of two between pointer size and 4096");
186 if (!domain)
187 domain = buffer_default_domain();
188 if (!domain)
189 return SET_ERRNO(ERROR_INVALID_STATE, "Buffer system is unavailable");
190
191 size_t capacity = BUFFER_MIN_CAPACITY;
192 unsigned bin = 0;
193 while (capacity < size && bin + 1 < BUFFER_BINS) {
194 capacity *= 2;
195 bin++;
196 }
197 if (capacity < size)
198 capacity = size;
199 size_t overhead = sizeof(buffer_header_t) + sizeof(buffer_header_t *) + alignment - 1;
200 if (capacity > SIZE_MAX - overhead)
201 return SET_ERRNO(ERROR_INVALID_PARAM, "Buffer capacity overflow");
202 size_t charge = capacity + overhead;
203
204 mutex_lock(&domain->mutex);
205 if (domain->closing) {
206 mutex_unlock(&domain->mutex);
207 return SET_ERRNO(ERROR_INVALID_STATE, "Buffer domain is closing");
208 }
209 buffer_header_t **link = &domain->bins[bin];
210 buffer_header_t *node = NULL;
211 if (capacity <= BUFFER_MAX_CACHED) {
212 while (*link) {
213 if ((*link)->alignment >= alignment) {
214 node = *link;
215 *link = node->next;
216 domain->stats.cached_bytes -= node->charge;
217 domain->stats.hits++;
218 break;
219 }
220 link = &(*link)->next;
221 }
222 }
223 if (!node) {
224 if (charge > domain->config.limit_bytes || domain->stats.reserved_bytes > domain->config.limit_bytes - charge) {
225 /* A miss may reclaim idle capacity, but a release never scans a pool. */
226 trim_locked(domain, true);
227 }
228 bool fits =
229 charge <= domain->config.limit_bytes && domain->stats.reserved_bytes <= domain->config.limit_bytes - charge;
230 bool reserved = fits && process_reserve(charge, domain->config.control);
231 if (fits && !reserved && domain->stats.cached_bytes) {
232 trim_locked(domain, true);
233 reserved = process_reserve(charge, domain->config.control);
234 }
235 if (!reserved) {
236 domain->stats.failures++;
237 mutex_unlock(&domain->mutex);
238 return SET_ERRNO(ERROR_MEMORY, "Buffer budget exhausted requesting %zu bytes", size);
239 }
240 node = ALLOC_MALLOC(charge);
241 if (!node) {
242 atomic_fetch_sub_u64(&process_bytes, charge);
243 domain->stats.failures++;
244 mutex_unlock(&domain->mutex);
245 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate %zu buffer bytes", charge);
246 }
247 memset(node, 0, sizeof(*node));
248 node->allocation = node;
249 uintptr_t start = (uintptr_t)(node + 1) + sizeof(buffer_header_t *);
250 node->data = (void *)((start + alignment - 1) & ~(uintptr_t)(alignment - 1));
251 ((buffer_header_t **)node->data)[-1] = node;
252 node->domain = domain;
253 node->capacity = capacity;
254 node->charge = charge;
255 node->alignment = alignment;
256 node->bin = bin;
257 domain->stats.reserved_bytes += charge;
258 domain->stats.allocations++;
259 }
260 node->next = NULL;
261 BUFFER_UNPOISON(node->data, node->capacity);
262 atomic_store_u64(&node->references, 1);
263 node->length = size;
264 domain->stats.outstanding++;
265 domain->stats.live_bytes += node->charge;
266 domain->stats.requested_bytes += size;
267 if (domain->stats.reserved_bytes > domain->stats.peak_bytes)
268 domain->stats.peak_bytes = domain->stats.reserved_bytes;
269 *out = node->data;
270 mutex_unlock(&domain->mutex);
271 return ASCIICHAT_OK;
272}
273
274void *buffer_alloc(buffer_domain_t *domain, size_t size) {
275 void *data = NULL;
276 (void)buffer_acquire(domain, size, BUFFER_DEFAULT_ALIGNMENT, &data);
277 return data;
278}
279
280size_t buffer_capacity(const void *data) {
281 return data ? header(data)->capacity : 0;
282}
283size_t buffer_size(const void *data) {
284 return data ? header(data)->length : 0;
285}
286
287asciichat_error_t buffer_set_size(void *data, size_t size) {
288 if (!data || size > buffer_capacity(data))
289 return SET_ERRNO(ERROR_INVALID_PARAM, "Buffer length exceeds capacity");
290 buffer_header_t *node = header(data);
291 buffer_domain_t *domain = node->domain;
292 mutex_lock(&domain->mutex);
293 if (atomic_load_u64(&node->references) != 1) {
294 mutex_unlock(&domain->mutex);
295 return SET_ERRNO(ERROR_INVALID_STATE, "Cannot resize shared buffer");
296 }
297 domain->stats.requested_bytes -= node->length;
298 node->length = size;
299 domain->stats.requested_bytes += size;
300 mutex_unlock(&domain->mutex);
301 return ASCIICHAT_OK;
302}
303
304const void *buffer_share(const void *data) {
305 if (!data)
306 return NULL;
307 buffer_header_t *node = header(data);
308 uint64_t references = atomic_load_u64(&node->references);
309 while (references && references != SIZE_MAX) {
310 if (atomic_cas_u64(&node->references, &references, references + 1))
311 return data;
312 }
313 SET_ERRNO(ERROR_INVALID_STATE, "Invalid buffer reference count");
314 return NULL;
315}
316
317bool buffer_is_unique(const void *data) {
318 return data && atomic_load_u64(&header(data)->references) == 1;
319}
320
321void buffer_unref(const void *data) {
322 if (!data)
323 return;
324 buffer_header_t *node = header(data);
325 buffer_domain_t *domain = node->domain;
326 uint64_t references = atomic_load_u64(&node->references);
327 while (references && !atomic_cas_u64(&node->references, &references, references - 1)) {
328 }
329 if (!references) {
330 SET_ERRNO(ERROR_INVALID_STATE, "Buffer has already been released");
331 return;
332 }
333 if (references > 1) {
334 return;
335 }
336 mutex_lock(&domain->mutex);
337 domain->stats.outstanding--;
338 domain->stats.live_bytes -= node->charge;
339 domain->stats.requested_bytes -= node->length;
340 bool done = domain->closing && !domain->stats.outstanding;
341 if (!domain->closing && node->capacity <= BUFFER_MAX_CACHED && node->charge <= domain->config.cache_bytes &&
342 domain->stats.cached_bytes <= domain->config.cache_bytes - node->charge) {
343 node->returned_at = time_get_ns();
344 BUFFER_POISON(node->data, node->capacity);
345 node->next = domain->bins[node->bin];
346 domain->bins[node->bin] = node;
347 domain->stats.cached_bytes += node->charge;
348 } else {
349 dispose(domain, node);
350 }
351 mutex_unlock(&domain->mutex);
352 if (done)
353 domain_dispose(domain);
354}
355
357 if (!workspace)
358 return SET_ERRNO(ERROR_INVALID_PARAM, "Workspace is NULL");
359 if (workspace->length > buffer_capacity(workspace->data))
360 return SET_ERRNO(ERROR_INVALID_STATE, "Workspace length exceeds its storage");
361 if (capacity <= buffer_capacity(workspace->data))
362 return ASCIICHAT_OK;
363 void *next = NULL;
364 asciichat_error_t result = buffer_acquire(domain, capacity, BUFFER_DEFAULT_ALIGNMENT, &next);
365 if (result != ASCIICHAT_OK)
366 return result;
367 if (workspace->data && workspace->length)
368 memcpy(next, workspace->data, workspace->length);
369 buffer_release(workspace->data);
370 workspace->data = next;
371 return ASCIICHAT_OK;
372}
373
375 if (workspace) {
376 buffer_release(workspace->data);
377 workspace->length = 0;
378 }
379}
380
382 if (!out)
383 return;
384 memset(out, 0, sizeof(*out));
385 if (!domain)
386 return;
387 mutex_lock(&domain->mutex);
388 *out = domain->stats;
389 mutex_unlock(&domain->mutex);
390}
391
392void buffer_domain_log_stats(buffer_domain_t *domain, const char *name) {
393 buffer_stats_t stats;
394 buffer_domain_stats(domain, &stats);
395 log_debug("Buffers %s: reserved=%zu live=%zu requested=%zu cached=%zu outstanding=%zu hits=%llu failures=%llu",
396 name ? name : "default", stats.reserved_bytes, stats.live_bytes, stats.requested_bytes, stats.cached_bytes,
397 stats.outstanding, (unsigned long long)stats.hits, (unsigned long long)stats.failures);
398}
399
401 for (;;) {
402 if (lifecycle_init_once(&default_lifecycle))
403 break;
404 if (lifecycle_is_initialized(&default_lifecycle) || lifecycle_is_dead(&default_lifecycle))
405 return;
406 // Another caller must finish publishing both domains before allocations use them.
407 platform_sleep_us(100);
408 }
409 if (buffer_domain_create(NULL, &default_domain) != ASCIICHAT_OK) {
410 lifecycle_init_abort(&default_lifecycle);
411 return;
412 }
413 buffer_domain_config_t control = {BUFFER_CONTROL_RESERVE, 1024 * 1024, 5000000000ULL, true};
414 if (buffer_domain_create(&control, &control_domain) != ASCIICHAT_OK) {
415 buffer_domain_close(&default_domain);
416 lifecycle_init_abort(&default_lifecycle);
417 return;
418 }
419 NAMED_REGISTER_ATOMIC(&process_bytes, "buffer.process_reserved_bytes", NULL);
420 lifecycle_init_commit(&default_lifecycle);
421}
422
425 return default_domain;
426}
427
429 if (lifecycle_shutdown(&default_lifecycle)) {
430 NAMED_UNREGISTER(&process_bytes);
431 buffer_domain_close(&default_domain);
432 buffer_domain_close(&control_domain);
433 }
434}
435
438 return control_domain;
439}
#define SET_ERRNO(code,...)
size_t buffer_capacity(const void *data)
Definition buffer.c:280
bool buffer_is_unique(const void *data)
Definition buffer.c:317
#define BUFFER_MIN_CAPACITY
Definition buffer.c:25
void buffer_domain_log_stats(buffer_domain_t *domain, const char *name)
Definition buffer.c:392
asciichat_error_t buffer_workspace_reserve(buffer_workspace_t *workspace, buffer_domain_t *domain, size_t capacity)
Definition buffer.c:356
void buffer_domain_stats(buffer_domain_t *domain, buffer_stats_t *out)
Definition buffer.c:381
void buffer_system_cleanup(void)
Definition buffer.c:428
void buffer_domain_close(buffer_domain_t **slot)
Definition buffer.c:156
asciichat_error_t buffer_set_size(void *data, size_t size)
Definition buffer.c:287
void * buffer_alloc(buffer_domain_t *domain, size_t size)
Definition buffer.c:274
#define BUFFER_POISON(p, n)
Definition buffer.c:19
void buffer_budget_release(buffer_budget_t *budget)
Definition buffer.c:88
buffer_domain_t * buffer_default_domain(void)
Definition buffer.c:423
size_t buffer_process_reserved(void)
Definition buffer.c:75
void buffer_domain_trim(buffer_domain_t *domain)
Definition buffer.c:170
#define BUFFER_BINS
Definition buffer.c:24
#define BUFFER_MAX_CACHED
Definition buffer.c:26
void buffer_unref(const void *data)
Definition buffer.c:321
struct buffer_header buffer_header_t
void buffer_workspace_destroy(buffer_workspace_t *workspace)
Definition buffer.c:374
buffer_domain_t * buffer_control_domain(void)
Definition buffer.c:436
#define BUFFER_UNPOISON(p, n)
Definition buffer.c:20
asciichat_error_t buffer_domain_create(const buffer_domain_config_t *config, buffer_domain_t **out)
Definition buffer.c:125
void buffer_system_init(void)
Definition buffer.c:400
const void * buffer_share(const void *data)
Definition buffer.c:304
asciichat_error_t buffer_budget_reserve(buffer_budget_t *budget, size_t bytes)
Definition buffer.c:79
asciichat_error_t buffer_acquire(buffer_domain_t *domain, size_t size, size_t alignment, void **out)
Definition buffer.c:178
#define BUFFER_DOMAIN_LIMIT
Definition buffer.h:29
struct buffer_domain buffer_domain_t
Definition buffer.h:24
#define BUFFER_DEFAULT_ALIGNMENT
Definition buffer.h:26
#define buffer_release(data)
Definition buffer.h:91
#define BUFFER_PROCESS_LIMIT
Definition buffer.h:27
#define BUFFER_CONTROL_RESERVE
Definition buffer.h:28
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging — log identifiable resource names.
int buffer_size
Size of circular buffer.
Definition grep.c:68
#define SAFE_FREE(ptr)
Definition common.h:377
#define ALLOC_CALLOC(count, size)
Definition common.h:254
#define ALLOC_MALLOC(size)
Definition common.h:253
unsigned long long uint64_t
Definition common.h:63
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
@ ERROR_THREAD
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
static bool size_t gotel_query_t * out
Definition queries.h:8
🔤 String Manipulation and Shell Escaping Utilities
Owns the temporary storage used by buffer budget. The data pointers and size or cursor fields describ...
Definition budget.h:8
size_t bytes
Definition budget.h:9
The useful part of buffer header is in next, domain, allocation, data: together they describe what is...
Definition buffer.c:52
buffer_stats_t stats
Definition buffer.c:55
bool closing
Definition buffer.c:57
mutex_t mutex
Definition buffer.c:53
buffer_header_t * bins[17]
Definition buffer.c:56
buffer_domain_config_t config
Definition buffer.c:54
size_t capacity
Definition buffer.c:35
void * allocation
Definition buffer.c:33
size_t charge
Definition buffer.c:37
struct buffer_header * next
Definition buffer.c:31
uint64_t returned_at
Definition buffer.c:40
buffer_domain_t * domain
Definition buffer.c:32
unsigned bin
Definition buffer.c:41
size_t alignment
Definition buffer.c:38
size_t length
Definition buffer.c:36
void * data
Definition buffer.c:34
atomic_t references
Definition buffer.c:39
size_t live_bytes
Definition buffer.h:42
uint64_t hits
Definition buffer.h:47
size_t outstanding
Definition buffer.h:46
size_t reserved_bytes
Definition buffer.h:41
size_t peak_bytes
Definition buffer.h:45
size_t requested_bytes
Definition buffer.h:43
size_t cached_bytes
Definition buffer.h:44
uint64_t failures
Definition buffer.h:49
uint64_t allocations
Definition buffer.h:48
Owns the temporary storage used by buffer workspace. The data pointers and size or cursor fields desc...
Definition buffer.h:102
⏱️ High-precision timing utilities using sokol_time.h and uthash