ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
debug/named.c
Go to the documentation of this file.
1
10
11/* Storage capacities in elements; text buffers include the terminating NUL. */
12#define DEBUG_NAMED_FINAL_BASE_NAME_BUFFER_SIZE 256
13
16#include "libplatform/rwlock.h"
18#include <ascii-chat/source_root.h>
19#include "libplatform/thread.h"
20#include <ascii-chat/uthash.h>
21#include "ascii-chat/log/log.h"
23#include <libplatform/lifecycle.h>
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <inttypes.h>
29
30// Hash table growth runs under entries_lock. Memory tracking looks up names
31// under the same lock, so registry metadata must use untracked allocations.
32#undef uthash_malloc
33#undef uthash_free
34#define uthash_malloc(sz) malloc(sz)
35#define uthash_free(ptr, sz) free(ptr)
36
37#define MAX_NAME_LEN 256
38#define DESCRIBE_BUFFER_SIZE 768
39
48static char *entry_strdup(const char *s) {
49 if (!s)
50 return NULL;
51 size_t len = strlen(s) + 1;
52 char *dup = malloc(len);
53 if (dup) {
54 asciichat_error_t strcpy_result = SAFE_STRCPY(dup, len, s);
55 if (strcpy_result != ASCIICHAT_OK) {
56 log_error("Failed to duplicate string: %s", asciichat_error_format(NULL, strcpy_result));
57 free(dup);
58 return NULL;
59 }
60 }
61 return dup;
62}
63
67typedef struct named_entry {
68 uintptr_t key;
70 char *name;
71 char *type;
73 char *file;
74 int line;
75 char *func;
76 UT_hash_handle hh; // uthash handle
78
86typedef struct {
87 named_entry_t *entries; // uthash hash table
88 rwlock_t entries_lock; // Protects hash table
89 lifecycle_t lifecycle;
91
92static named_registry_t g_named_registry = {
93 .entries = NULL,
94 .entries_lock = {0},
95 .lifecycle = LIFECYCLE_INIT,
96};
97
98// Writers publish a revision while holding entries_lock. Readers only load it;
99// their snapshots own all display strings and never retain object pointers.
100static _Atomic uint64_t g_metadata_revision = 1;
101static tls_key_t g_metadata_tls;
102static bool g_metadata_tls_ready;
103
111
123static _Thread_local named_metadata_cache_t *g_thread_metadata;
124
125static void metadata_cache_destroy(void *data) {
126 named_metadata_cache_t *cache = data;
127 if (cache) {
128 if (g_thread_metadata == cache)
129 g_thread_metadata = NULL;
130 UNTRACKED_FREE(cache->entries);
131 UNTRACKED_FREE(cache);
132 }
133}
134
135static int metadata_compare(const void *left, const void *right) {
136 uintptr_t a = ((const named_metadata_entry_t *)left)->key;
137 uintptr_t b = ((const named_metadata_entry_t *)right)->key;
138 return (a > b) - (a < b);
139}
140
141static bool metadata_copy_entry(const named_entry_t *entry, named_metadata_t *output) {
142 const char *name = entry->name ? entry->name : "?";
143 const char *type = entry->type ? entry->type : "object";
144 const char *format = entry->format_spec ? entry->format_spec : "0x%tx";
145 if (strlen(name) >= sizeof(output->name) || strlen(type) >= sizeof(output->type) ||
146 strlen(format) >= sizeof(output->format))
147 return false;
148 memcpy(output->name, name, strlen(name) + 1);
149 memcpy(output->type, type, strlen(type) + 1);
150 memcpy(output->format, format, strlen(format) + 1);
151 return true;
152}
153
154static named_metadata_cache_t *metadata_snapshot(void) {
155 if (!g_metadata_tls_ready || !lifecycle_is_initialized(&g_named_registry.lifecycle))
156 return NULL;
157 named_metadata_cache_t *cache = g_thread_metadata;
158 if (cache && cache->revision == atomic_load(&g_metadata_revision))
159 return cache;
160 if (!cache) {
161 cache = UNTRACKED_MALLOC(sizeof(*cache), named_metadata_cache_t *);
162 if (!cache)
163 return NULL;
164 memset(cache, 0, sizeof(*cache));
165 if (platform_tls_set(g_metadata_tls, cache) != 0) {
166 metadata_cache_destroy(cache);
167 return NULL;
168 }
169 g_thread_metadata = cache;
170 }
171 bool acquired = false;
172 if (rwlock_tryrdlock(&g_named_registry.entries_lock, &acquired) != ASCIICHAT_OK || !acquired)
173 return NULL;
174 size_t required = HASH_COUNT(g_named_registry.entries);
175 rwlock_rdunlock(&g_named_registry.entries_lock);
176 if (required > cache->capacity) {
177 if (required > SIZE_MAX / sizeof(*cache->entries))
178 return NULL;
179 named_metadata_entry_t *entries = UNTRACKED_MALLOC(required * sizeof(*entries), named_metadata_entry_t *);
180 if (!entries)
181 return NULL;
182 UNTRACKED_FREE(cache->entries);
183 cache->entries = entries;
184 cache->capacity = required;
185 }
186 if (rwlock_tryrdlock(&g_named_registry.entries_lock, &acquired) != ASCIICHAT_OK || !acquired)
187 return NULL;
188 if (HASH_COUNT(g_named_registry.entries) > cache->capacity) {
189 rwlock_rdunlock(&g_named_registry.entries_lock);
190 return NULL;
191 }
192 cache->count = 0;
193 for (named_entry_t *entry = g_named_registry.entries; entry; entry = entry->hh.next) {
194 // An initializer and owner can register the same address. Keep the latest
195 // hash entry, matching HASH_FIND, rather than returning an older alias.
196 named_entry_t *latest = NULL;
197 HASH_FIND(hh, g_named_registry.entries, &entry->key, sizeof(entry->key), latest);
198 if (latest != entry)
199 continue;
200 named_metadata_entry_t *copy = &cache->entries[cache->count++];
201 copy->key = entry->key;
202 copy->valid = metadata_copy_entry(entry, &copy->metadata);
203 }
204 cache->revision = atomic_load(&g_metadata_revision);
205 rwlock_rdunlock(&g_named_registry.entries_lock);
206 if (cache->count > 1)
207 qsort(cache->entries, cache->count, sizeof(*cache->entries), metadata_compare);
208 return cache;
209}
210
211bool named_metadata_copy(uintptr_t key, named_metadata_t *output) {
212 if (!output)
213 return false;
214 named_metadata_cache_t *cache = metadata_snapshot();
215 if (!cache) {
216 // Keep names available if TLS/allocation is unavailable. No borrowed
217 // strings escape this fallback either.
218 if (!lifecycle_is_initialized(&g_named_registry.lifecycle))
219 return false;
220 bool acquired = false;
221 if (rwlock_tryrdlock(&g_named_registry.entries_lock, &acquired) != ASCIICHAT_OK || !acquired)
222 return false;
223 named_entry_t *entry = NULL;
224 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(key), entry);
225 bool copied = entry && metadata_copy_entry(entry, output);
226 rwlock_rdunlock(&g_named_registry.entries_lock);
227 return copied;
228 }
229 named_metadata_entry_t lookup = {.key = key};
230 const named_metadata_entry_t *entry =
231 cache->count ? bsearch(&lookup, cache->entries, cache->count, sizeof(*cache->entries), metadata_compare) : NULL;
232 if (!entry || !entry->valid)
233 return false;
234 *output = entry->metadata;
235 return true;
236}
237
239 // Idempotent: if already initialized, return success
240 if (lifecycle_is_initialized(&g_named_registry.lifecycle)) {
241 return ASCIICHAT_OK;
242 }
243
244 if (!lifecycle_init(&g_named_registry.lifecycle, "named_registry")) {
245 return ASCIICHAT_OK;
246 }
247
248 if (rwlock_init(&g_named_registry.entries_lock, "named_registry_lock") != 0) {
249 lifecycle_shutdown(&g_named_registry.lifecycle);
250 return ASCIICHAT_OK; // Continue even if rwlock init fails
251 }
252
253 g_metadata_tls_ready = platform_tls_key_create(&g_metadata_tls, metadata_cache_destroy) == 0;
254 atomic_fetch_add(&g_metadata_revision, 1);
255 lifecycle_init_commit(&g_named_registry.lifecycle);
256 return ASCIICHAT_OK;
257}
258
259void named_destroy(void) {
260 if (!lifecycle_shutdown(&g_named_registry.lifecycle))
261 return;
262 rwlock_wrlock(&g_named_registry.entries_lock);
263 named_entry_t *entry, *next;
264 HASH_ITER(hh, g_named_registry.entries, entry, next) {
265 // Remove each hash node so uthash releases its bucket storage as well.
266 // Unregistration is disabled after lifecycle shutdown, including for the
267 // registry lock itself; all entries must be freed here.
268 HASH_DEL(g_named_registry.entries, entry);
269 free(entry->name);
270 free(entry->type);
271 free(entry->format_spec);
272 free(entry->file);
273 free(entry->func);
274 free(entry);
275 }
276 g_named_registry.entries_lock.name = NULL;
277 atomic_fetch_add(&g_metadata_revision, 1);
278 rwlock_wrunlock(&g_named_registry.entries_lock);
279 rwlock_destroy(&g_named_registry.entries_lock);
280 if (g_metadata_tls_ready) {
281 void *cache = g_thread_metadata;
282 platform_tls_set(g_metadata_tls, NULL);
283 metadata_cache_destroy(cache);
284 platform_tls_key_delete(g_metadata_tls);
285 g_metadata_tls_ready = false;
286 }
287}
288
295static uint64_t mutex_counter = 0;
296static uint64_t rwlock_counter = 0;
297static uint64_t cond_counter = 0;
298static uint64_t atomic_counter = 0;
299static _Atomic uint64_t named_generation = 0;
300
305static void named_entry_init(named_entry_t *entry, const char *type, const char *format_spec, const char *file,
306 int line, const char *func) {
307 entry->generation = ++named_generation;
308 entry->type = entry_strdup(type);
309 entry->format_spec = entry_strdup(format_spec);
310 entry->file = file ? entry_strdup(extract_project_relative_path(file, ASCIICHAT_OWNED_SOURCE_DIRS)) : NULL;
311 entry->line = line;
312 entry->func = func ? entry_strdup(func) : NULL;
313}
314
321static const char *named_lookup_name_unlocked(uintptr_t parent_key) {
322 if (parent_key == 0)
323 return NULL;
324 named_entry_t *entry = NULL;
325 HASH_FIND(hh, g_named_registry.entries, &parent_key, sizeof(uintptr_t), entry);
326 return entry ? entry->name : NULL;
327}
328
329const char *named_register(uintptr_t key, const char *base_name, const char *type, const char *format_spec,
330 const char *file, int line, const char *func, uintptr_t parent_key) {
331#ifdef NDEBUG
332 // In release builds, skip debug registry and return the base name
333 return base_name ? base_name : "?";
334#endif
335
336 if (!base_name || !type || !format_spec) {
337 return "?";
338 }
339
340 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
341 return base_name;
342 }
343
344 // If parent_key provided, look up parent name and create hierarchical name
345 char final_base_name[DEBUG_NAMED_FINAL_BASE_NAME_BUFFER_SIZE];
346 if (parent_key != 0) {
347 rwlock_rdlock(&g_named_registry.entries_lock);
348 const char *parent_name = named_lookup_name_unlocked(parent_key);
349 rwlock_rdunlock(&g_named_registry.entries_lock);
350
351 if (parent_name) {
352 snprintf(final_base_name, sizeof(final_base_name), "%s#%s", parent_name, base_name);
353 } else {
354 // Parent not found, fall back to base name
355 snprintf(final_base_name, sizeof(final_base_name), "%s", base_name);
356 }
357 } else {
358 snprintf(final_base_name, sizeof(final_base_name), "%s", base_name);
359 }
360
361 // Generate unique name using atomic counters (only for non-hierarchical names)
362 char name_buffer[DEBUG_NAMED_NAME_BUFFER_SIZE];
363 if (parent_key == 0) {
364 uint64_t counter = 0;
365 if (strcmp(type, "mutex") == 0) {
366 counter = __sync_fetch_and_add(&mutex_counter, 1);
367 } else if (strcmp(type, "rwlock") == 0) {
368 counter = __sync_fetch_and_add(&rwlock_counter, 1);
369 } else if (strcmp(type, "cond") == 0) {
370 counter = __sync_fetch_and_add(&cond_counter, 1);
371 } else if (strcmp(type, "atomic") == 0) {
372 counter = __sync_fetch_and_add(&atomic_counter, 1);
373 }
374 snprintf(name_buffer, sizeof(name_buffer), "%s.%" PRIu64, final_base_name, counter);
375 } else {
376 // Hierarchical names don't get counters - parent already has one
377 snprintf(name_buffer, sizeof(name_buffer), "%s", final_base_name);
378 }
379
380 // Allocate entry BEFORE acquiring lock (avoid long critical section)
381 named_entry_t *entry = malloc(sizeof(named_entry_t));
382 if (!entry)
383 return base_name;
384
385 entry->key = key;
386 entry->name = entry_strdup(name_buffer);
387 named_entry_init(entry, type, format_spec, file, line, func);
388
389 // Only hold lock for the hash table operation
390 log_dev("[NAMED_REGISTER_LOCK_1] About to acquire entries_lock for key=%p type=%s", (void *)key, type);
391 rwlock_wrlock(&g_named_registry.entries_lock);
392 HASH_ADD(hh, g_named_registry.entries, key, sizeof(uintptr_t), entry);
393 atomic_fetch_add(&g_metadata_revision, 1);
394 rwlock_wrunlock(&g_named_registry.entries_lock);
395 log_dev("[NAMED_REGISTER_LOCK_4] ✅ Released entries_lock");
396
397 return entry->name;
398}
399
400const char *named_register_fmt(uintptr_t key, const char *type, const char *format_spec, const char *file, int line,
401 const char *func, const char *fmt, ...) {
402 if (!type || !format_spec || !fmt) {
403 return "?";
404 }
405
406 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
407 return fmt;
408 }
409
410 // Format the name BEFORE acquiring lock
411 va_list args;
412 va_start(args, fmt);
413 char *full_name = NULL;
414 int ret = platform_vasprintf(&full_name, fmt, args);
415 va_end(args);
416
417 if (ret < 0) {
418 return "?";
419 }
420
421 // Allocate entry BEFORE acquiring lock
422 named_entry_t *entry = malloc(sizeof(named_entry_t));
423 if (!entry) {
424 free(full_name);
425 return "?";
426 }
427
428 // Initialize entry (name is pre-allocated as full_name)
429 entry->key = key;
430 entry->name = full_name;
431 named_entry_init(entry, type, format_spec, file, line, func);
432
433 // Only hold lock for the hash table operation
434 rwlock_wrlock(&g_named_registry.entries_lock);
435 HASH_ADD(hh, g_named_registry.entries, key, sizeof(uintptr_t), entry);
436 atomic_fetch_add(&g_metadata_revision, 1);
437 rwlock_wrunlock(&g_named_registry.entries_lock);
438
439 return entry->name;
440}
441
442void named_unregister(uintptr_t key) {
443 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
444 return;
445 }
446
447 rwlock_wrlock(&g_named_registry.entries_lock);
448 named_entry_t *entry = NULL;
449 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
450 bool changed = entry != NULL;
451 // A primitive may be registered again with a parent-qualified name. Retire
452 // every entry before its owner releases the object.
453 while (entry) {
454 HASH_DEL(g_named_registry.entries, entry);
455 free(entry->name);
456 if (entry->type)
457 free(entry->type);
458 if (entry->format_spec)
459 free(entry->format_spec);
460 if (entry->file)
461 free(entry->file);
462 if (entry->func)
463 free(entry->func);
464 free(entry);
465 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
466 }
467 if (changed)
468 atomic_fetch_add(&g_metadata_revision, 1);
469 rwlock_wrunlock(&g_named_registry.entries_lock);
470}
471
472const char *named_update_name(uintptr_t key, const char *new_base_name) {
473#ifndef NDEBUG
474 if (!new_base_name || !lifecycle_is_initialized(&g_named_registry.lifecycle))
475 return NULL;
476 char name[MAX_NAME_LEN];
477 snprintf(name, sizeof(name), "%s.%" PRIu64, new_base_name, ++named_generation);
478 char *replacement = entry_strdup(name);
479 if (!replacement)
480 return NULL;
481 rwlock_wrlock(&g_named_registry.entries_lock);
482 named_entry_t *entry = NULL;
483 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(key), entry);
484 const char *result = NULL;
485 if (entry) {
486 UNTRACKED_FREE(entry->name);
487 entry->name = replacement;
488 result = replacement;
489 atomic_fetch_add(&g_metadata_revision, 1);
490 } else {
491 UNTRACKED_FREE(replacement);
492 }
493 rwlock_wrunlock(&g_named_registry.entries_lock);
494 return result;
495#else
496 (void)key;
497 (void)new_base_name;
498 return NULL;
499#endif
500}
501
502const char *named_get(uintptr_t key) {
503 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
504 return NULL;
505 }
506
507 rwlock_rdlock(&g_named_registry.entries_lock);
508 named_entry_t *entry = NULL;
509 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
510 const char *result = entry ? entry->name : NULL;
511 rwlock_rdunlock(&g_named_registry.entries_lock);
512 return result;
513}
514
515bool named_registry_has_type(const char *type, size_t length) {
516 if (!type || !length)
517 return false;
518 named_metadata_cache_t *cache = metadata_snapshot();
519 if (!cache)
520 return false;
521 for (size_t i = 0; i < cache->count; ++i) {
522 const named_metadata_entry_t *entry = &cache->entries[i];
523 if (entry->valid && strlen(entry->metadata.type) == length && memcmp(entry->metadata.type, type, length) == 0)
524 return true;
525 }
526 return false;
527}
528
529const char *named_get_type(uintptr_t key) {
530 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
531 return NULL;
532 }
533
534 rwlock_rdlock(&g_named_registry.entries_lock);
535 named_entry_t *entry = NULL;
536 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
537 const char *result = entry ? entry->type : NULL;
538 rwlock_rdunlock(&g_named_registry.entries_lock);
539 return result;
540}
541
542const char *named_get_format_spec(uintptr_t key) {
543 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
544 return NULL;
545 }
546
547 rwlock_rdlock(&g_named_registry.entries_lock);
548 named_entry_t *entry = NULL;
549 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
550 const char *result = entry ? entry->format_spec : NULL;
551 rwlock_rdunlock(&g_named_registry.entries_lock);
552 return result;
553}
554
555const char *named_describe(uintptr_t key, const char *type_hint) {
556 if (!type_hint)
557 type_hint = "object";
558
559 static _Thread_local char buffer[DESCRIBE_BUFFER_SIZE];
560
561 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
562 snprintf(buffer, sizeof(buffer), "%s (0x%tx)", type_hint, (ptrdiff_t)key);
563 return buffer;
564 }
565
566 rwlock_rdlock(&g_named_registry.entries_lock);
567 named_entry_t *entry = NULL;
568 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
569 rwlock_rdunlock(&g_named_registry.entries_lock);
570
571 if (entry) {
572 const char *type = entry->type ? entry->type : type_hint;
573 const char *name = entry->name ? entry->name : "unknown";
574 if (entry->file && entry->func && entry->line > 0) {
575 snprintf(buffer, sizeof(buffer), "%s/%s (0x%tx) @ %s:%d:%s()", type, name, (ptrdiff_t)key, entry->file,
576 entry->line, entry->func);
577 } else {
578 snprintf(buffer, sizeof(buffer), "%s/%s (0x%tx)", type, name, (ptrdiff_t)key);
579 }
580 } else {
581 snprintf(buffer, sizeof(buffer), "%s (0x%tx)", type_hint ? type_hint : "unknown", (ptrdiff_t)key);
582 }
583
584 return buffer;
585}
586
587const char *named_describe_thread(void *thread) {
588 uintptr_t key = thread_to_key((platform_thread_t)thread);
589 return named_describe(key, "thread");
590}
591
592typedef struct {
593 uintptr_t key;
601
602asciichat_error_t named_registry_for_each(named_iter_callback_t callback, void *user_data, bool *completed) {
603 if (!callback || !completed)
604 return SET_ERRNO(ERROR_INVALID_PARAM, "Registry iteration requires a callback");
605 *completed = false;
606 // Logging also enumerates names during startup and teardown.
607 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
608 *completed = true;
609 return ASCIICHAT_OK;
610 }
611 bool acquired = false;
612 asciichat_error_t err = rwlock_tryrdlock(&g_named_registry.entries_lock, &acquired);
613 if (err != ASCIICHAT_OK || !acquired)
614 return err;
615 size_t capacity = HASH_COUNT(g_named_registry.entries);
616 rwlock_rdunlock(&g_named_registry.entries_lock);
617 if (!capacity) {
618 *completed = true;
619 return ASCIICHAT_OK;
620 }
621 if (capacity > SIZE_MAX / sizeof(named_iter_entry_t))
622 return SET_ERRNO(ERROR_MEMORY, "Named registry snapshot size overflow");
623 // Snapshot metadata must not re-enter the registry through memory tracking.
624 named_iter_entry_t *entries = UNTRACKED_MALLOC(capacity * sizeof(*entries), named_iter_entry_t *);
625 if (!entries)
626 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate named registry snapshot");
627 err = rwlock_tryrdlock(&g_named_registry.entries_lock, &acquired);
628 if (err != ASCIICHAT_OK || !acquired) {
629 UNTRACKED_FREE(entries);
630 return err;
631 }
632 if (HASH_COUNT(g_named_registry.entries) > capacity) {
633 rwlock_rdunlock(&g_named_registry.entries_lock);
634 UNTRACKED_FREE(entries);
635 return ASCIICHAT_OK;
636 }
637 size_t count = 0;
638 for (named_entry_t *e = g_named_registry.entries; e != NULL; e = e->hh.next) {
639 entries[count].key = e->key;
640 const char *src = e->name ? e->name : "?";
641 size_t src_len = strlen(src);
642 if (src_len >= MAX_NAME_LEN)
643 src_len = MAX_NAME_LEN - 1;
644 memcpy(entries[count].name, src, src_len);
645 entries[count].name[src_len] = '\0';
646 count++;
647 }
648 rwlock_rdunlock(&g_named_registry.entries_lock);
649
650 // Call callback outside the lock
651 for (size_t i = 0; i < count; i++) {
652 callback(entries[i].key, entries[i].name, user_data);
653 }
654 UNTRACKED_FREE(entries);
655 *completed = true;
656 return ASCIICHAT_OK;
657}
658
659asciichat_error_t named_registry_try_snapshot(size_t capacity, size_t *required, bool *completed,
660 named_snapshot_fn copy, void *data) {
661 if (!required || !completed || !copy)
662 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid protected registry snapshot");
663 *completed = false;
664 *required = 0;
665 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
666 *completed = true;
667 return ASCIICHAT_OK;
668 }
669 bool acquired = false;
670 asciichat_error_t result = rwlock_tryrdlock(&g_named_registry.entries_lock, &acquired);
671 if (result != ASCIICHAT_OK || !acquired)
672 return result;
673 *required = HASH_COUNT(g_named_registry.entries);
674 if (*required <= capacity) {
675 for (named_entry_t *e = g_named_registry.entries; e; e = e->hh.next)
676 copy(e->key, e->generation, e->name, e->type, e->file, e->line, data);
677 *completed = true;
678 }
679 rwlock_rdunlock(&g_named_registry.entries_lock);
680 return ASCIICHAT_OK;
681}
682
683bool named_registry_read(uintptr_t key, const char *type, void (*read_object)(uintptr_t, void *), void *user_data) {
684 if (!type || !read_object || !lifecycle_is_initialized(&g_named_registry.lifecycle))
685 return false;
686 bool acquired = false;
687 if (rwlock_tryrdlock(&g_named_registry.entries_lock, &acquired) != ASCIICHAT_OK || !acquired)
688 return false;
689 named_entry_t *entry = NULL;
690 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(key), entry);
691 bool found = entry && entry->type && strcmp(entry->type, type) == 0;
692 if (found)
693 read_object(key, user_data);
694 rwlock_rdunlock(&g_named_registry.entries_lock);
695 return found;
696}
697
698const char *named_search_by_type_id(const char *type, void *id) {
699 (void)type;
700 (void)id;
701 return NULL;
702}
703
707static inline uintptr_t encode_fd_key(int fd) {
708 return ((uintptr_t)-1 - (unsigned int)fd);
709}
710
714static inline uintptr_t encode_pkt_type_key(int pkt_type) {
715 return ((uintptr_t)-1 - 100000U - (unsigned int)pkt_type);
716}
717
719 return fd >= 0 && named_metadata_copy(encode_fd_key(fd), output);
720}
721
722bool named_packet_metadata_copy(int packet_type, named_metadata_t *output) {
723 return packet_type >= 0 && named_metadata_copy(encode_pkt_type_key(packet_type), output);
724}
725
726const char *named_register_fd(int fd, const char *name, const char *file, int line, const char *func) {
727 if (fd < 0) {
728 return "?";
729 }
730
731 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
732 return "?";
733 }
734
735 char name_buffer[DEBUG_NAMED_NAME_BUFFER_SIZE];
736 if (name) {
737 snprintf(name_buffer, sizeof(name_buffer), "%s", name);
738 } else {
739 snprintf(name_buffer, sizeof(name_buffer), "fd=%d", fd);
740 }
741
742 // Allocate entry BEFORE acquiring lock
743 named_entry_t *entry = malloc(sizeof(named_entry_t));
744 if (!entry)
745 return "?";
746
747 uintptr_t key = encode_fd_key(fd);
748 entry->key = key;
749 entry->name = entry_strdup(name_buffer);
750 named_entry_init(entry, "fd", "%d", file, line, func);
751
752 // Only hold lock for the hash table operation
753 rwlock_wrlock(&g_named_registry.entries_lock);
754 HASH_ADD(hh, g_named_registry.entries, key, sizeof(uintptr_t), entry);
755 atomic_fetch_add(&g_metadata_revision, 1);
756 rwlock_wrunlock(&g_named_registry.entries_lock);
757
758 return entry->name;
759}
760
761const char *named_get_fd(int fd) {
762 if (fd < 0)
763 return NULL;
764
765 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
766 return NULL;
767 }
768
769 uintptr_t key = encode_fd_key(fd);
770 rwlock_rdlock(&g_named_registry.entries_lock);
771 named_entry_t *entry = NULL;
772 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
773 const char *result = entry ? entry->name : NULL;
774 rwlock_rdunlock(&g_named_registry.entries_lock);
775 return result;
776}
777
778const char *named_get_fd_format_spec(int fd) {
779 if (fd < 0)
780 return NULL;
781
782 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
783 return NULL;
784 }
785
786 uintptr_t key = encode_fd_key(fd);
787 rwlock_rdlock(&g_named_registry.entries_lock);
788 named_entry_t *entry = NULL;
789 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
790 const char *result = entry ? entry->format_spec : NULL;
791 rwlock_rdunlock(&g_named_registry.entries_lock);
792 return result;
793}
794
795const char *named_register_packet_type(int pkt_type, const char *file, int line, const char *func) {
796 if (pkt_type < 0) {
797 return "?";
798 }
799
800 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
801 return "?";
802 }
803
804 char name_buffer[DEBUG_NAMED_NAME_BUFFER_SIZE];
805 snprintf(name_buffer, sizeof(name_buffer), "PACKET_TYPE=%d", pkt_type);
806
807 // Allocate entry BEFORE acquiring lock
808 named_entry_t *entry = malloc(sizeof(named_entry_t));
809 if (!entry)
810 return "?";
811
812 uintptr_t key = encode_pkt_type_key(pkt_type);
813 entry->key = key;
814 entry->name = entry_strdup(name_buffer);
815 named_entry_init(entry, "packet_type", "%d", file, line, func);
816
817 // Only hold lock for the hash table operation
818 rwlock_wrlock(&g_named_registry.entries_lock);
819 HASH_ADD(hh, g_named_registry.entries, key, sizeof(uintptr_t), entry);
820 atomic_fetch_add(&g_metadata_revision, 1);
821 rwlock_wrunlock(&g_named_registry.entries_lock);
822
823 return entry->name;
824}
825
826const char *named_get_packet_type(int pkt_type) {
827 if (pkt_type < 0)
828 return NULL;
829
830 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
831 return NULL;
832 }
833
834 uintptr_t key = encode_pkt_type_key(pkt_type);
835 rwlock_rdlock(&g_named_registry.entries_lock);
836 named_entry_t *entry = NULL;
837 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
838 const char *result = entry ? entry->name : NULL;
839 rwlock_rdunlock(&g_named_registry.entries_lock);
840 return result;
841}
842
843const char *named_get_packet_type_format_spec(int pkt_type) {
844 if (pkt_type < 0)
845 return NULL;
846
847 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
848 return NULL;
849 }
850
851 uintptr_t key = encode_pkt_type_key(pkt_type);
852 rwlock_rdlock(&g_named_registry.entries_lock);
853 named_entry_t *entry = NULL;
854 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
855 const char *result = entry ? entry->format_spec : NULL;
856 rwlock_rdunlock(&g_named_registry.entries_lock);
857 return result;
858}
859
861 // No-op
862}
863
865 if (!lifecycle_is_initialized(&g_named_registry.lifecycle))
866 return;
867 rwlock_rdlock(&g_named_registry.entries_lock);
868 size_t entries = HASH_COUNT(g_named_registry.entries);
869 size_t buckets = g_named_registry.entries ? g_named_registry.entries->hh.tbl->num_buckets : 0;
870 rwlock_rdunlock(&g_named_registry.entries_lock);
871 log_plain("Named registry: entries=%zu buckets=%zu", entries, buckets);
872}
action_args_t args
Definition actions.c:78
const char * asciichat_error_format(const char *message, asciichat_error_t code)
#define SET_ERRNO(code,...)
#define DESCRIBE_BUFFER_SIZE
Definition debug/named.c:38
struct named_entry named_entry_t
Registry entry for uthash.
#define DEBUG_NAMED_FINAL_BASE_NAME_BUFFER_SIZE
Definition debug/named.c:12
const char * named_search_by_type_id(const char *type, void *id)
#define MAX_NAME_LEN
Definition debug/named.c:37
Named object registry for debugging — log identifiable resource names.
#define UNTRACKED_FREE(ptr)
Definition common.h:414
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:486
unsigned long long uint64_t
Definition common.h:63
#define UNTRACKED_MALLOC(size, cast)
Definition common.h:405
void named_print_hash_stats(void)
const char * named_get_fd(int fd)
Look up a registered file descriptor.
bool named_registry_has_type(const char *type, size_t length)
const char * named_get_packet_type_format_spec(int pkt_type)
Get the format specifier for a registered packet type.
uintptr_t thread_to_key(platform_thread_t thread)
Convert a thread handle to a registry key.
bool named_packet_metadata_copy(int packet_type, named_metadata_t *output)
bool named_registry_read(uintptr_t key, const char *type, void(*read_object)(uintptr_t, void *), void *user_data)
void named_destroy(void)
Destroy the named object registry.
const char * named_describe(uintptr_t key, const char *type_hint)
Format a description string for logging.
void(* named_snapshot_fn)(uintptr_t key, uint64_t generation, const char *name, const char *type, const char *file, int line, void *data)
bool named_metadata_copy(uintptr_t key, named_metadata_t *output)
const char * named_get_packet_type(int pkt_type)
Look up a registered packet type.
const char * named_register_packet_type(int pkt_type, const char *file, int line, const char *func)
Register a packet type with namespace encoding.
const char * named_register_fd(int fd, const char *name, const char *file, int line, const char *func)
Register a file descriptor with namespace encoding.
const char * named_get_type(uintptr_t key)
Look up the registered type for a resource.
void named_unregister(uintptr_t key)
Unregister a resource by key.
void(* named_iter_callback_t)(uintptr_t key, const char *name, void *user_data)
Callback function for iterating registered entries.
const char * named_get_format_spec(uintptr_t key)
Get the format specifier for a registered named object.
asciichat_error_t named_registry_try_snapshot(size_t capacity, size_t *required, bool *completed, named_snapshot_fn copy, void *data)
void named_registry_register_packet_types(void)
Register all packet types from packet_type_t enum.
const char * named_get(uintptr_t key)
Look up the registered name for a resource.
const char * named_get_fd_format_spec(int fd)
Get the format specifier for a registered file descriptor.
bool named_fd_metadata_copy(int fd, named_metadata_t *output)
asciichat_error_t named_registry_for_each(named_iter_callback_t callback, void *user_data, bool *completed)
Iterate through all registered entries.
#define DEBUG_NAMED_NAME_BUFFER_SIZE
Definition debug/named.h:47
asciichat_error_t named_init(void)
Initialize the named object registry.
const char * named_register(uintptr_t key, const char *base_name, const char *type, const char *format_spec, const char *file, int line, const char *func, uintptr_t parent_key)
Register a resource with an auto-suffixed name, type, and location info.
const char * named_describe_thread(void *thread)
Describe a thread for logging (function wrapper).
const char * named_register_fmt(uintptr_t key, const char *type, const char *format_spec, const char *file, int line, const char *func, const char *fmt,...)
Register a resource with a formatted name, type, and location info (no auto-suffix).
const char * named_update_name(uintptr_t key, const char *new_base_name)
Update the registered name for a resource with a new base name.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_dev(...)
Log a DEV message (most verbose, development only).
Definition log/log.h:581
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_plain(...)
Plain logging - writes to both log file and stderr without timestamps or log levels.
Definition log/log.h:665
asciichat_error_t rwlock_tryrdlock(rwlock_t *lock, bool *acquired)
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds).
Definition rwlock.h:357
int rwlock_init(rwlock_t *lock, const char *name)
Initialize a read-write lock with a name.
int rwlock_destroy(rwlock_t *lock)
Destroy a read-write lock.
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds).
Definition rwlock.h:299
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds).
Definition rwlock.h:319
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds).
Definition rwlock.h:338
const char * extract_project_relative_path(const char *file, const char *const *owned_dirs)
Extract a path relative to the source root embedded by CMake.
📝 Logging API with multiple log levels and terminal output control
📂 Path Manipulation Utilities
🔤 String Manipulation and Shell Escaping Utilities
Registry entry for uthash.
Definition debug/named.c:67
char * format_spec
Definition debug/named.c:72
uint64_t generation
Definition debug/named.c:69
char * func
Definition debug/named.c:75
uintptr_t key
Definition debug/named.c:68
char * file
Definition debug/named.c:73
int line
Definition debug/named.c:74
UT_hash_handle hh
Definition debug/named.c:76
char * name
Definition debug/named.c:70
char * type
Definition debug/named.c:71
uintptr_t key
char name[256]
named_metadata_entry_t * entries
bool valid
uintptr_t key
named_metadata_t metadata
The useful part of named metadata is in its fields: together they describe what is pending,...
Global registry with uthash and rwlock.
Definition debug/named.c:86
rwlock_t entries_lock
Definition debug/named.c:88
lifecycle_t lifecycle
Definition debug/named.c:89
named_entry_t * entries
Definition debug/named.c:87
Read-write lock type (POSIX: pthread_rwlock_t with debug tracking).
Definition rwlock.h:63