ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
debug/named.c
Go to the documentation of this file.
1
14#include <ascii-chat/uthash.h>
15#include "ascii-chat/log/log.h"
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <inttypes.h>
23
24// Hash table growth runs under entries_lock. Memory tracking looks up names
25// under the same lock, so registry metadata must use untracked allocations.
26#undef uthash_malloc
27#undef uthash_free
28#define uthash_malloc(sz) malloc(sz)
29#define uthash_free(ptr, sz) free(ptr)
30
31#define MAX_NAME_LEN 256
32#define DESCRIBE_BUFFER_SIZE 768
33
40static char *entry_strdup(const char *s) {
41 if (!s)
42 return NULL;
43 size_t len = strlen(s) + 1;
44 char *dup = malloc(len);
45 if (dup) {
46 asciichat_error_t strcpy_result = SAFE_STRCPY(dup, len, s);
47 if (strcpy_result != ASCIICHAT_OK) {
48 log_error("Failed to duplicate string: %s", asciichat_error_string(strcpy_result));
49 free(dup);
50 return NULL;
51 }
52 }
53 return dup;
54}
55
59typedef struct named_entry {
60 uintptr_t key;
61 char *name;
62 char *type;
64 char *file;
65 int line;
66 char *func;
67 UT_hash_handle hh; // uthash handle
69
77typedef struct {
78 named_entry_t *entries; // uthash hash table
79 rwlock_t entries_lock; // Protects hash table
82
83static named_registry_t g_named_registry = {
84 .entries = NULL,
85 .entries_lock = {0},
86 .lifecycle = LIFECYCLE_INIT,
87};
88
90 // Idempotent: if already initialized, return success
91 if (lifecycle_is_initialized(&g_named_registry.lifecycle)) {
92 return ASCIICHAT_OK;
93 }
94
95 if (!lifecycle_init(&g_named_registry.lifecycle, "named_registry")) {
96 return ASCIICHAT_OK;
97 }
98
99 if (rwlock_init(&g_named_registry.entries_lock, "named_registry_lock") != 0) {
100 lifecycle_shutdown(&g_named_registry.lifecycle);
101 return ASCIICHAT_OK; // Continue even if rwlock init fails
102 }
103
104 lifecycle_init_commit(&g_named_registry.lifecycle);
105 return ASCIICHAT_OK;
106}
107
108void named_destroy(void) {
109 // Always attempt to shut down the lifecycle, but always cleanup entries
110 lifecycle_shutdown(&g_named_registry.lifecycle);
111
112 rwlock_wrlock(&g_named_registry.entries_lock);
113
114 // Find the registry's own rwlock entry (allocated during named_init bootstrap)
115 // This entry will be unregistered by rwlock_destroy, so we must not free it here
116 uintptr_t registry_lock_key = (uintptr_t)(const void *)&g_named_registry.entries_lock;
117 named_entry_t *registry_lock_entry = NULL;
118 HASH_FIND(hh, g_named_registry.entries, &registry_lock_key, sizeof(uintptr_t), registry_lock_entry);
119
120 for (named_entry_t *e = g_named_registry.entries; e != NULL;) {
121 named_entry_t *next = e->hh.next;
122
123 // Skip the registry lock entry - it will be cleaned up by rwlock_destroy
124 // which calls NAMED_UNREGISTER after we release the lock
125 if (e == registry_lock_entry) {
126 e = next;
127 continue;
128 }
129
130 // Free all other entries
131 free(e->name);
132 if (e->type)
133 free(e->type);
134 if (e->format_spec)
135 free(e->format_spec);
136 if (e->file)
137 free(e->file);
138 if (e->func)
139 free(e->func);
140 free(e);
141 e = next;
142 }
143
144 rwlock_wrunlock(&g_named_registry.entries_lock);
145
146 // rwlock_destroy will call NAMED_UNREGISTER on the lock entry, which will:
147 // 1. Acquire the lock (now safe since we released it)
148 // 2. Find and remove the lock entry from the hash
149 // 3. Free the lock entry's fields
150 rwlock_destroy(&g_named_registry.entries_lock);
151}
152
156static uint64_t mutex_counter = 0;
157static uint64_t rwlock_counter = 0;
158static uint64_t cond_counter = 0;
159static uint64_t atomic_counter = 0;
160
165static void named_entry_init(named_entry_t *entry, const char *type, const char *format_spec, const char *file,
166 int line, const char *func) {
167 entry->type = entry_strdup(type);
168 entry->format_spec = entry_strdup(format_spec);
169 entry->file = file ? entry_strdup(extract_project_relative_path(file)) : NULL;
170 entry->line = line;
171 entry->func = func ? entry_strdup(func) : NULL;
172}
173
180static const char *named_lookup_name_unlocked(uintptr_t parent_key) {
181 if (parent_key == 0)
182 return NULL;
183 named_entry_t *entry = NULL;
184 HASH_FIND(hh, g_named_registry.entries, &parent_key, sizeof(uintptr_t), entry);
185 return entry ? entry->name : NULL;
186}
187
188const char *named_register(uintptr_t key, const char *base_name, const char *type, const char *format_spec,
189 const char *file, int line, const char *func, uintptr_t parent_key) {
190#ifdef NDEBUG
191 // In release builds, skip debug registry and return the base name
192 return base_name ? base_name : "?";
193#endif
194
195 if (!base_name || !type || !format_spec) {
196 return "?";
197 }
198
199 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
200 return base_name;
201 }
202
203 // If parent_key provided, look up parent name and create hierarchical name
204 char final_base_name[256];
205 if (parent_key != 0) {
206 rwlock_rdlock(&g_named_registry.entries_lock);
207 const char *parent_name = named_lookup_name_unlocked(parent_key);
208 rwlock_rdunlock(&g_named_registry.entries_lock);
209
210 if (parent_name) {
211 snprintf(final_base_name, sizeof(final_base_name), "%s#%s", parent_name, base_name);
212 } else {
213 // Parent not found, fall back to base name
214 snprintf(final_base_name, sizeof(final_base_name), "%s", base_name);
215 }
216 } else {
217 snprintf(final_base_name, sizeof(final_base_name), "%s", base_name);
218 }
219
220 // Generate unique name using atomic counters (only for non-hierarchical names)
221 char name_buffer[256];
222 if (parent_key == 0) {
223 uint64_t counter = 0;
224 if (strcmp(type, "mutex") == 0) {
225 counter = __sync_fetch_and_add(&mutex_counter, 1);
226 } else if (strcmp(type, "rwlock") == 0) {
227 counter = __sync_fetch_and_add(&rwlock_counter, 1);
228 } else if (strcmp(type, "cond") == 0) {
229 counter = __sync_fetch_and_add(&cond_counter, 1);
230 } else if (strcmp(type, "atomic") == 0) {
231 counter = __sync_fetch_and_add(&atomic_counter, 1);
232 }
233 snprintf(name_buffer, sizeof(name_buffer), "%s.%" PRIu64, final_base_name, counter);
234 } else {
235 // Hierarchical names don't get counters - parent already has one
236 snprintf(name_buffer, sizeof(name_buffer), "%s", final_base_name);
237 }
238
239 // Allocate entry BEFORE acquiring lock (avoid long critical section)
240 named_entry_t *entry = malloc(sizeof(named_entry_t));
241 if (!entry)
242 return base_name;
243
244 entry->key = key;
245 entry->name = entry_strdup(name_buffer);
246 named_entry_init(entry, type, format_spec, file, line, func);
247
248 // Only hold lock for the hash table operation
249 log_dev("[NAMED_REGISTER_LOCK_1] About to acquire entries_lock for key=%p type=%s", (void *)key, type);
250 rwlock_wrlock(&g_named_registry.entries_lock);
251 HASH_ADD(hh, g_named_registry.entries, key, sizeof(uintptr_t), entry);
252 rwlock_wrunlock(&g_named_registry.entries_lock);
253 log_dev("[NAMED_REGISTER_LOCK_4] ✅ Released entries_lock");
254
255 return entry->name;
256}
257
258const char *named_register_fmt(uintptr_t key, const char *type, const char *format_spec, const char *file, int line,
259 const char *func, const char *fmt, ...) {
260 if (!type || !format_spec || !fmt) {
261 return "?";
262 }
263
264 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
265 return fmt;
266 }
267
268 // Format the name BEFORE acquiring lock
269 va_list args;
270 va_start(args, fmt);
271 char *full_name = NULL;
272 int ret = platform_vasprintf(&full_name, fmt, args);
273 va_end(args);
274
275 if (ret < 0) {
276 return "?";
277 }
278
279 // Allocate entry BEFORE acquiring lock
280 named_entry_t *entry = malloc(sizeof(named_entry_t));
281 if (!entry) {
282 free(full_name);
283 return "?";
284 }
285
286 // Initialize entry (name is pre-allocated as full_name)
287 entry->key = key;
288 entry->name = full_name;
289 named_entry_init(entry, type, format_spec, file, line, func);
290
291 // Only hold lock for the hash table operation
292 rwlock_wrlock(&g_named_registry.entries_lock);
293 HASH_ADD(hh, g_named_registry.entries, key, sizeof(uintptr_t), entry);
294 rwlock_wrunlock(&g_named_registry.entries_lock);
295
296 return entry->name;
297}
298
299void named_unregister(uintptr_t key) {
300 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
301 return;
302 }
303
304 rwlock_wrlock(&g_named_registry.entries_lock);
305 named_entry_t *entry = NULL;
306 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
307 if (entry) {
308 HASH_DEL(g_named_registry.entries, entry);
309 free(entry->name);
310 if (entry->type)
311 free(entry->type);
312 if (entry->format_spec)
313 free(entry->format_spec);
314 if (entry->file)
315 free(entry->file);
316 if (entry->func)
317 free(entry->func);
318 free(entry);
319 }
320 rwlock_wrunlock(&g_named_registry.entries_lock);
321}
322
323const char *named_update_name(uintptr_t key, const char *new_base_name) {
324 (void)key;
325 (void)new_base_name;
326 return NULL;
327}
328
329const char *named_get(uintptr_t key) {
330 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
331 return NULL;
332 }
333
334 rwlock_rdlock(&g_named_registry.entries_lock);
335 named_entry_t *entry = NULL;
336 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
337 const char *result = entry ? entry->name : NULL;
338 rwlock_rdunlock(&g_named_registry.entries_lock);
339 return result;
340}
341
342const char *named_get_type(uintptr_t key) {
343 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
344 return NULL;
345 }
346
347 rwlock_rdlock(&g_named_registry.entries_lock);
348 named_entry_t *entry = NULL;
349 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
350 const char *result = entry ? entry->type : NULL;
351 rwlock_rdunlock(&g_named_registry.entries_lock);
352 return result;
353}
354
355const char *named_get_format_spec(uintptr_t key) {
356 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
357 return NULL;
358 }
359
360 rwlock_rdlock(&g_named_registry.entries_lock);
361 named_entry_t *entry = NULL;
362 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
363 const char *result = entry ? entry->format_spec : NULL;
364 rwlock_rdunlock(&g_named_registry.entries_lock);
365 return result;
366}
367
368const char *named_describe(uintptr_t key, const char *type_hint) {
369 if (!type_hint)
370 type_hint = "object";
371
372 static _Thread_local char buffer[DESCRIBE_BUFFER_SIZE];
373
374 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
375 snprintf(buffer, sizeof(buffer), "%s (0x%tx)", type_hint, (ptrdiff_t)key);
376 return buffer;
377 }
378
379 rwlock_rdlock(&g_named_registry.entries_lock);
380 named_entry_t *entry = NULL;
381 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
382 rwlock_rdunlock(&g_named_registry.entries_lock);
383
384 if (entry) {
385 const char *type = entry->type ? entry->type : type_hint;
386 const char *name = entry->name ? entry->name : "unknown";
387 if (entry->file && entry->func && entry->line > 0) {
388 snprintf(buffer, sizeof(buffer), "%s/%s (0x%tx) @ %s:%d:%s()", type, name, (ptrdiff_t)key, entry->file,
389 entry->line, entry->func);
390 } else {
391 snprintf(buffer, sizeof(buffer), "%s/%s (0x%tx)", type, name, (ptrdiff_t)key);
392 }
393 } else {
394 snprintf(buffer, sizeof(buffer), "%s (0x%tx)", type_hint ? type_hint : "unknown", (ptrdiff_t)key);
395 }
396
397 return buffer;
398}
399
400const char *named_describe_thread(void *thread) {
401 uintptr_t key = asciichat_thread_to_key((asciichat_thread_t)thread);
402 return named_describe(key, "thread");
403}
404
405typedef struct {
406 uintptr_t key;
407 char name[MAX_NAME_LEN];
409
410void named_registry_for_each(named_iter_callback_t callback, void *user_data) {
411 if (!callback || !lifecycle_is_initialized(&g_named_registry.lifecycle)) {
412 return;
413 }
414
415 // Snapshot entries while holding read lock
416 named_iter_entry_t entries[256];
417 int count = 0;
418
419 rwlock_rdlock(&g_named_registry.entries_lock);
420 for (named_entry_t *e = g_named_registry.entries; e != NULL && count < 256; e = e->hh.next) {
421 entries[count].key = e->key;
422 const char *src = e->name ? e->name : "?";
423 size_t src_len = strlen(src);
424 if (src_len >= MAX_NAME_LEN)
425 src_len = MAX_NAME_LEN - 1;
426 memcpy(entries[count].name, src, src_len);
427 entries[count].name[src_len] = '\0';
428 count++;
429 }
430 rwlock_rdunlock(&g_named_registry.entries_lock);
431
432 // Call callback outside the lock
433 for (int i = 0; i < count; i++) {
434 callback(entries[i].key, entries[i].name, user_data);
435 }
436}
437
438const char *named_search_by_type_id(const char *type, void *id) {
439 (void)type;
440 (void)id;
441 return NULL;
442}
443
447static inline uintptr_t encode_fd_key(int fd) {
448 return ((uintptr_t)-1 - (unsigned int)fd);
449}
450
454static inline uintptr_t encode_pkt_type_key(int pkt_type) {
455 return ((uintptr_t)-1 - 100000U - (unsigned int)pkt_type);
456}
457
458const char *named_register_fd(int fd, const char *name, const char *file, int line, const char *func) {
459 if (fd < 0) {
460 return "?";
461 }
462
463 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
464 return "?";
465 }
466
467 char name_buffer[256];
468 if (name) {
469 snprintf(name_buffer, sizeof(name_buffer), "%s", name);
470 } else {
471 snprintf(name_buffer, sizeof(name_buffer), "fd=%d", fd);
472 }
473
474 // Allocate entry BEFORE acquiring lock
475 named_entry_t *entry = malloc(sizeof(named_entry_t));
476 if (!entry)
477 return "?";
478
479 uintptr_t key = encode_fd_key(fd);
480 entry->key = key;
481 entry->name = entry_strdup(name_buffer);
482 named_entry_init(entry, "fd", "%d", file, line, func);
483
484 // Only hold lock for the hash table operation
485 rwlock_wrlock(&g_named_registry.entries_lock);
486 HASH_ADD(hh, g_named_registry.entries, key, sizeof(uintptr_t), entry);
487 rwlock_wrunlock(&g_named_registry.entries_lock);
488
489 return entry->name;
490}
491
492const char *named_get_fd(int fd) {
493 if (fd < 0)
494 return NULL;
495
496 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
497 return NULL;
498 }
499
500 uintptr_t key = encode_fd_key(fd);
501 rwlock_rdlock(&g_named_registry.entries_lock);
502 named_entry_t *entry = NULL;
503 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
504 const char *result = entry ? entry->name : NULL;
505 rwlock_rdunlock(&g_named_registry.entries_lock);
506 return result;
507}
508
509const char *named_get_fd_format_spec(int fd) {
510 if (fd < 0)
511 return NULL;
512
513 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
514 return NULL;
515 }
516
517 uintptr_t key = encode_fd_key(fd);
518 rwlock_rdlock(&g_named_registry.entries_lock);
519 named_entry_t *entry = NULL;
520 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
521 const char *result = entry ? entry->format_spec : NULL;
522 rwlock_rdunlock(&g_named_registry.entries_lock);
523 return result;
524}
525
526const char *named_register_packet_type(int pkt_type, const char *file, int line, const char *func) {
527 if (pkt_type < 0) {
528 return "?";
529 }
530
531 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
532 return "?";
533 }
534
535 char name_buffer[256];
536 snprintf(name_buffer, sizeof(name_buffer), "PACKET_TYPE=%d", pkt_type);
537
538 // Allocate entry BEFORE acquiring lock
539 named_entry_t *entry = malloc(sizeof(named_entry_t));
540 if (!entry)
541 return "?";
542
543 uintptr_t key = encode_pkt_type_key(pkt_type);
544 entry->key = key;
545 entry->name = entry_strdup(name_buffer);
546 named_entry_init(entry, "packet_type", "%d", file, line, func);
547
548 // Only hold lock for the hash table operation
549 rwlock_wrlock(&g_named_registry.entries_lock);
550 HASH_ADD(hh, g_named_registry.entries, key, sizeof(uintptr_t), entry);
551 rwlock_wrunlock(&g_named_registry.entries_lock);
552
553 return entry->name;
554}
555
556const char *named_get_packet_type(int pkt_type) {
557 if (pkt_type < 0)
558 return NULL;
559
560 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
561 return NULL;
562 }
563
564 uintptr_t key = encode_pkt_type_key(pkt_type);
565 rwlock_rdlock(&g_named_registry.entries_lock);
566 named_entry_t *entry = NULL;
567 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
568 const char *result = entry ? entry->name : NULL;
569 rwlock_rdunlock(&g_named_registry.entries_lock);
570 return result;
571}
572
573const char *named_get_packet_type_format_spec(int pkt_type) {
574 if (pkt_type < 0)
575 return NULL;
576
577 if (!lifecycle_is_initialized(&g_named_registry.lifecycle)) {
578 return NULL;
579 }
580
581 uintptr_t key = encode_pkt_type_key(pkt_type);
582 rwlock_rdlock(&g_named_registry.entries_lock);
583 named_entry_t *entry = NULL;
584 HASH_FIND(hh, g_named_registry.entries, &key, sizeof(uintptr_t), entry);
585 const char *result = entry ? entry->format_spec : NULL;
586 rwlock_rdunlock(&g_named_registry.entries_lock);
587 return result;
588}
589
591 // No-op
592}
#define DESCRIBE_BUFFER_SIZE
Definition debug/named.c:32
struct named_entry named_entry_t
Registry entry for uthash.
const char * named_search_by_type_id(const char *type, void *id)
#define MAX_NAME_LEN
Definition debug/named.c:31
Named object registry for debugging — log identifiable resource names.
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:471
unsigned long long uint64_t
Definition common.h:59
const char * named_get_fd(int fd)
Look up a registered file descriptor.
const char * named_get_packet_type_format_spec(int pkt_type)
Get the format specifier for a registered packet type.
void named_registry_for_each(named_iter_callback_t callback, void *user_data)
Iterate through all registered entries.
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.
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.
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.
asciichat_error_t named_init(void)
Initialize the named object registry.
Definition debug/named.c:89
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.
uintptr_t asciichat_thread_to_key(asciichat_thread_t thread)
Convert a thread handle to a registry key.
Definition threading.c:87
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:349
int rwlock_init(rwlock_t *rwlock, const char *name)
Initialize a read-write lock with a name.
Definition threading.c:65
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:294
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:313
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:331
int platform_vasprintf(char **strp, const char *format, va_list ap)
Allocate formatted string with va_list (vasprintf replacement)
const char * extract_project_relative_path(const char *file)
Extract relative path from an absolute path.
Definition path.c:456
void * asciichat_thread_t
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
void lifecycle_init_commit(lifecycle_t *lc)
Definition lifecycle.c:90
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108
Lock-free module lifecycle state machine using pure stdatomic.
#define LIFECYCLE_INIT
Static initializer for module-global lifecycle variables (no sync primitive)
Definition lifecycle.h:68
📝 Logging API with multiple log levels and terminal output control
action_args_t args
📂 Path Manipulation Utilities
Cross-platform string operations.
Cross-platform read-write lock interface for ascii-chat.
Registry entry for uthash.
Definition debug/named.c:59
char * format_spec
Definition debug/named.c:63
char * func
Definition debug/named.c:66
uintptr_t key
Definition debug/named.c:60
char * file
Definition debug/named.c:64
int line
Definition debug/named.c:65
UT_hash_handle hh
Definition debug/named.c:67
char * name
Definition debug/named.c:61
char * type
Definition debug/named.c:62
uintptr_t key
char name[256]
Global registry with uthash and rwlock.
Definition debug/named.c:77
rwlock_t entries_lock
Definition debug/named.c:79
lifecycle_t lifecycle
Definition debug/named.c:80
named_entry_t * entries
Definition debug/named.c:78
Read-write lock type (POSIX: pthread_rwlock_t with debug tracking)
Definition rwlock.h:61
🔤 String Manipulation and Shell Escaping Utilities