PyCyBase 0.1.8.post4__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. cbase/__infra__.pxd +6 -0
  2. cbase/__init__.pxd +6 -0
  3. cbase/__init__.py +28 -0
  4. cbase/allocator_protocol/__infra__.pxd +58 -0
  5. cbase/allocator_protocol/__init__.pxd +58 -0
  6. cbase/allocator_protocol/__init__.py +45 -0
  7. cbase/allocator_protocol/c_allocator_protocol.c +31818 -0
  8. cbase/allocator_protocol/c_allocator_protocol.cp313-win_amd64.pyd +0 -0
  9. cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  10. cbase/allocator_protocol/c_allocator_protocol.pxd +64 -0
  11. cbase/allocator_protocol/c_allocator_protocol.pyi +144 -0
  12. cbase/allocator_protocol/c_allocator_protocol.pyx +209 -0
  13. cbase/allocator_protocol/c_heap_allocator.c +38144 -0
  14. cbase/allocator_protocol/c_heap_allocator.cp313-win_amd64.pyd +0 -0
  15. cbase/allocator_protocol/c_heap_allocator.h +444 -0
  16. cbase/allocator_protocol/c_heap_allocator.pxd +97 -0
  17. cbase/allocator_protocol/c_heap_allocator.pyi +161 -0
  18. cbase/allocator_protocol/c_heap_allocator.pyx +300 -0
  19. cbase/allocator_protocol/c_nt_shm_allocator.c +33399 -0
  20. cbase/allocator_protocol/c_nt_shm_allocator.cp313-win_amd64.pyd +0 -0
  21. cbase/allocator_protocol/c_nt_shm_allocator.h +996 -0
  22. cbase/allocator_protocol/c_nt_shm_allocator.pxd +102 -0
  23. cbase/allocator_protocol/c_nt_shm_allocator.pyx +223 -0
  24. cbase/allocator_protocol/c_shm_allocator.h +1168 -0
  25. cbase/allocator_protocol/c_shm_allocator.pxd +142 -0
  26. cbase/allocator_protocol/c_shm_allocator.pyi +299 -0
  27. cbase/allocator_protocol/c_shm_allocator.pyx +442 -0
  28. cbase/allocator_protocol/c_shm_comp.c +4643 -0
  29. cbase/allocator_protocol/c_shm_comp.cp313-win_amd64.pyd +0 -0
  30. cbase/allocator_protocol/c_shm_comp.h +49 -0
  31. cbase/allocator_protocol/c_shm_comp.pxd +38 -0
  32. cbase/allocator_protocol/c_shm_comp.pyx +1 -0
  33. cbase/backports/__infra__.pxd +17 -0
  34. cbase/backports/__init__.pxd +17 -0
  35. cbase/backports/__init__.py +9 -0
  36. cbase/backports/pydict.h +55 -0
  37. cbase/backports/pydict.pxd +5 -0
  38. cbase/backports/pylong.c +9946 -0
  39. cbase/backports/pylong.cp313-win_amd64.pyd +0 -0
  40. cbase/backports/pylong.h +79 -0
  41. cbase/backports/pylong.pxd +23 -0
  42. cbase/backports/pylong.pyx +61 -0
  43. cbase/bytemap/__infra__.pxd +42 -0
  44. cbase/bytemap/__init__.pxd +42 -0
  45. cbase/bytemap/__init__.py +21 -0
  46. cbase/bytemap/c_bytemap.c +52076 -0
  47. cbase/bytemap/c_bytemap.cp313-win_amd64.pyd +0 -0
  48. cbase/bytemap/c_bytemap.h +867 -0
  49. cbase/bytemap/c_bytemap.pxd +370 -0
  50. cbase/bytemap/c_bytemap.pyx +1758 -0
  51. cbase/bytemap/xxh3.h +55 -0
  52. cbase/bytemap/xxhash.c +42 -0
  53. cbase/bytemap/xxhash.h +7487 -0
  54. cbase/env.c +11109 -0
  55. cbase/env.cp313-win_amd64.pyd +0 -0
  56. cbase/env.pxd +8 -0
  57. cbase/env.pyi +48 -0
  58. cbase/env.pyx +46 -0
  59. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.c +31818 -0
  60. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  61. cbase/includes/cbase/allocator_protocol/c_heap_allocator.c +38144 -0
  62. cbase/includes/cbase/allocator_protocol/c_heap_allocator.h +444 -0
  63. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.c +33399 -0
  64. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.h +996 -0
  65. cbase/includes/cbase/allocator_protocol/c_shm_allocator.h +1168 -0
  66. cbase/includes/cbase/allocator_protocol/c_shm_comp.c +4643 -0
  67. cbase/includes/cbase/allocator_protocol/c_shm_comp.h +49 -0
  68. cbase/includes/cbase/backports/pydict.h +55 -0
  69. cbase/includes/cbase/backports/pylong.c +9946 -0
  70. cbase/includes/cbase/backports/pylong.h +79 -0
  71. cbase/includes/cbase/bytemap/c_bytemap.c +52076 -0
  72. cbase/includes/cbase/bytemap/c_bytemap.h +867 -0
  73. cbase/includes/cbase/bytemap/xxh3.h +55 -0
  74. cbase/includes/cbase/bytemap/xxhash.c +42 -0
  75. cbase/includes/cbase/bytemap/xxhash.h +7487 -0
  76. cbase/includes/cbase/env.c +11109 -0
  77. cbase/includes/cbase/int128.h +81 -0
  78. cbase/includes/cbase/intern_string/c_intern_string.c +28081 -0
  79. cbase/includes/cbase/intern_string/c_intern_string.h +614 -0
  80. cbase/includes/cbase/nt/pthread_nt_compat.h +102 -0
  81. cbase/includes/cbase/nt/regex_nt_compat.h +133 -0
  82. cbase/includes/cbase/nt/time_sched_nt_compat.h +32 -0
  83. cbase/int128.h +81 -0
  84. cbase/int128.pxd +15 -0
  85. cbase/intern_string/BENCHMARK.md +157 -0
  86. cbase/intern_string/__infra__.pxd +11 -0
  87. cbase/intern_string/__init__.pxd +11 -0
  88. cbase/intern_string/__init__.py +19 -0
  89. cbase/intern_string/c_intern_string.c +28081 -0
  90. cbase/intern_string/c_intern_string.cp313-win_amd64.pyd +0 -0
  91. cbase/intern_string/c_intern_string.h +614 -0
  92. cbase/intern_string/c_intern_string.pxd +118 -0
  93. cbase/intern_string/c_intern_string.pyi +376 -0
  94. cbase/intern_string/c_intern_string.pyx +765 -0
  95. cbase/nt/pthread_nt_compat.h +102 -0
  96. cbase/nt/regex_nt_compat.h +133 -0
  97. cbase/nt/time_sched_nt_compat.h +32 -0
  98. pycybase-0.1.8.post4.dist-info/METADATA +180 -0
  99. pycybase-0.1.8.post4.dist-info/RECORD +101 -0
  100. pycybase-0.1.8.post4.dist-info/WHEEL +5 -0
  101. pycybase-0.1.8.post4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1168 @@
1
+ #ifndef _GNU_SOURCE
2
+ #define _GNU_SOURCE
3
+ #endif
4
+ #ifndef AP_SHM_C_SHM_ALLOCATOR_H
5
+ #define AP_SHM_C_SHM_ALLOCATOR_H
6
+
7
+ #include <dirent.h>
8
+ #include <errno.h>
9
+ #include <fcntl.h>
10
+ #include <limits.h>
11
+ #include <pthread.h>
12
+ #include <signal.h>
13
+ #include <stddef.h>
14
+ #include <stdint.h>
15
+ #include <stdio.h>
16
+ #include <stdlib.h>
17
+ #include <string.h>
18
+ #include <sys/mman.h>
19
+ #include <sys/stat.h>
20
+ #include <unistd.h>
21
+
22
+ // Configuration
23
+
24
+ #ifndef AP_SHM_AUTOPAGE_CAPACITY
25
+ #define AP_SHM_AUTOPAGE_CAPACITY (64 * 1024) /* 64 KiB */
26
+ #endif
27
+
28
+ #ifndef AP_SHM_AUTOPAGE_CAPACITY_MAX
29
+ #define AP_SHM_AUTOPAGE_CAPACITY_MAX (16 * 1024 * 1024) /* 16 MiB */
30
+ #endif
31
+
32
+ #ifndef AP_SHM_AUTOPAGE_ALIGNMENT
33
+ #define AP_SHM_AUTOPAGE_ALIGNMENT (4 * 1024) /* 4 KiB */
34
+ #endif
35
+
36
+ #ifndef AP_SHM_ALLOCATOR_PREFIX
37
+ #define AP_SHM_ALLOCATOR_PREFIX "/c_cbase_shm"
38
+ #endif
39
+
40
+ #ifndef AP_SHM_NAME_LEN
41
+ #define AP_SHM_NAME_LEN 256
42
+ #endif
43
+
44
+ #ifndef AP_SHM_PREFIX_MAX
45
+ #define AP_SHM_PREFIX_MAX 64
46
+ #endif
47
+
48
+ #ifndef AP_SHM_ALLOCATOR_DEFAULT_REGION_SIZE
49
+ #define AP_SHM_ALLOCATOR_DEFAULT_REGION_SIZE (128ULL << 30) /* 128 GiB */
50
+ #endif
51
+
52
+ typedef struct shm_page {
53
+ size_t capacity; // total capacity, excluding metadata
54
+ size_t occupied; // bytes occupied, excluding metadata
55
+ size_t offset;
56
+ struct shm_allocator* allocator;
57
+ struct shm_memory_block* allocated;
58
+ char shm_name[AP_SHM_NAME_LEN];
59
+ char prev_name[AP_SHM_NAME_LEN];
60
+ } shm_page;
61
+
62
+ typedef struct shm_page_ctx {
63
+ shm_page* shm_page;
64
+ int shm_fd;
65
+ char* buffer;
66
+ struct shm_page_ctx* prev;
67
+ } shm_page_ctx;
68
+
69
+ typedef struct shm_memory_block {
70
+ size_t capacity;
71
+ size_t size;
72
+ struct shm_memory_block* next_free;
73
+ struct shm_memory_block* next_allocated;
74
+ shm_page* parent_page;
75
+ char buffer[];
76
+ } shm_memory_block;
77
+
78
+ typedef struct shm_allocator {
79
+ char shm_name[AP_SHM_NAME_LEN];
80
+ size_t pid;
81
+ pthread_mutex_t lock;
82
+ uintptr_t region;
83
+ size_t region_size;
84
+ size_t mapped_size;
85
+ char active_page[AP_SHM_NAME_LEN];
86
+ size_t mapped_pages;
87
+ shm_memory_block* free_list;
88
+ size_t autopage_capacity;
89
+ size_t autopage_capacity_max;
90
+ size_t autopage_alignment;
91
+ char shm_prefix[AP_SHM_PREFIX_MAX];
92
+ } shm_allocator;
93
+
94
+ typedef struct shm_allocator_ctx {
95
+ shm_allocator* shm_allocator;
96
+ int shm_fd;
97
+ shm_page_ctx* active_page;
98
+ } shm_allocator_ctx;
99
+
100
+ // ========== Forward Declarations (public API) ==========
101
+
102
+ /**
103
+ * @brief Round up size to allocator->autopage_alignment.
104
+ * @param size Requested size in bytes.
105
+ * @return Rounded-up size.
106
+ */
107
+ static size_t c_shm_page_roundup(shm_allocator* allocator, size_t size);
108
+
109
+ /**
110
+ * @brief Round up size to pointer alignment.
111
+ * @param size Requested size in bytes.
112
+ * @return Rounded-up size.
113
+ */
114
+ static size_t c_shm_block_roundup(size_t size);
115
+
116
+ /**
117
+ * @brief Scan /dev/shm for an entry matching prefix.
118
+ * @param prefix Prefix to match (may include leading '/').
119
+ * @param out Output buffer to receive the found name (with leading '/').
120
+ * @return 0 on success, -1 with errno set otherwise.
121
+ */
122
+ static inline int c_shm_scan(const char* prefix, char* out);
123
+
124
+ /**
125
+ * @brief Allocate a new shared-memory page metadata + fd (not yet mapped).
126
+ * @param allocator Allocator metadata (shared) or NULL.
127
+ * @param page_capacity Total bytes including metadata.
128
+ * @return Page context or NULL on failure.
129
+ */
130
+ static inline shm_page_ctx* c_shm_page_new(shm_allocator* allocator, size_t page_capacity);
131
+
132
+ /**
133
+ * @brief Map a page into the allocator's reserved region at the next offset.
134
+ * @param allocator Allocator metadata (shared).
135
+ * @param page_ctx Page context to map.
136
+ * @return 0 on success, -1 on error.
137
+ */
138
+ static inline int c_shm_page_map(shm_allocator* allocator, shm_page_ctx* page_ctx);
139
+
140
+ /**
141
+ * @brief Reclaim freed blocks of a given page back to unallocated state, with best effort.
142
+ * @param allocator Allocator metadata (shared).
143
+ * @param page_ctx Page context to reclaim from.
144
+ */
145
+ static inline void c_shm_page_reclaim(shm_allocator* allocator, shm_page_ctx* page_ctx);
146
+
147
+ /**
148
+ * @brief Extend allocator with a new page, optionally locking.
149
+ * @param ctx Allocator context.
150
+ * @param capacity Payload bytes requested (0 for auto sizing).
151
+ * @param lock Optional mutex to lock during extend.
152
+ * @return New active page context or NULL on error.
153
+ */
154
+ static inline shm_page_ctx* c_shm_allocator_extend(shm_allocator_ctx* ctx, size_t capacity, pthread_mutex_t* lock);
155
+
156
+ /**
157
+ * @brief Create a new allocator and reserve address space.
158
+ * @param region_size Virtual region size to reserve (0 for default).
159
+ * @param shm_prefix Custom SHM name prefix (NULL for compile-time default).
160
+ * @return Allocator context or NULL on failure.
161
+ */
162
+ static inline shm_allocator_ctx* c_shm_allocator_new(size_t region_size, const char* shm_prefix);
163
+
164
+ /**
165
+ * @brief Tear down allocator, unmapping pages and unlinking SHM objects.
166
+ * @param ctx Allocator context; NULL-safe.
167
+ */
168
+ static inline void c_shm_allocator_free(shm_allocator_ctx* ctx);
169
+
170
+ /**
171
+ * @brief Allocate zeroed memory from shared pages.
172
+ * @param ctx Allocator context.
173
+ * @param size Requested size.
174
+ * @param lock Optional mutex controlling allocation.
175
+ * @return Pointer to zeroed memory or NULL on error.
176
+ */
177
+ static inline void* c_shm_calloc(shm_allocator_ctx* ctx, size_t size, pthread_mutex_t* lock);
178
+
179
+ /**
180
+ * @brief Allocate memory with optional free-list reuse and page scanning.
181
+ * @param ctx Allocator context.
182
+ * @param size Requested size.
183
+ * @param scan_all_pages Non-zero scans older pages before extending.
184
+ * @param lock Optional mutex controlling allocation.
185
+ * @return Pointer to allocated memory or NULL on error.
186
+ */
187
+ static inline void* c_shm_request(shm_allocator_ctx* ctx, size_t size, int scan_all_pages, pthread_mutex_t* lock);
188
+
189
+ /**
190
+ * @brief Return a previously allocated block to the allocator free list.
191
+ * @param ptr User pointer obtained from request/calloc.
192
+ * @param lock Optional mutex controlling free.
193
+ */
194
+ static inline void c_shm_free(void* ptr, pthread_mutex_t* lock);
195
+
196
+ /**
197
+ * @brief Best-effort reclaim of freed blocks across all pages owned by ctx.
198
+ * @param ctx Allocator context.
199
+ * @param lock Optional mutex controlling reclaim.
200
+ */
201
+ static inline void c_shm_reclaim(shm_allocator_ctx* ctx, pthread_mutex_t* lock);
202
+
203
+ /**
204
+ * @brief Scan for an allocator SHM name (appends "_ac" to prefix).
205
+ * @param shm_prefix Prefix to scan for (NULL for compile-time default).
206
+ * @param out Output buffer for found name.
207
+ * @return 0 on success, -1 on failure.
208
+ */
209
+ static inline int c_shm_scan_allocator(const char* shm_prefix, char* out);
210
+
211
+ /**
212
+ * @brief Scan for a page SHM name (appends "_pg" to prefix).
213
+ * @param shm_prefix Prefix to scan for (NULL for compile-time default).
214
+ * @param out Output buffer for found name.
215
+ * @return 0 on success, -1 on failure.
216
+ */
217
+ static inline int c_shm_scan_page(const char* shm_prefix, char* out);
218
+
219
+ /**
220
+ * @brief Extract pid embedded in allocator/page SHM name.
221
+ *
222
+ * The name format is {prefix}_{pid_hex}_{suffix}. This function locates
223
+ * the pid by scanning from the right — it is prefix-agnostic.
224
+ *
225
+ * @param shm_name SHM object name (with or without leading '/').
226
+ * @return pid on success, -1 with errno=EINVAL on parse failure.
227
+ */
228
+ static inline pid_t c_shm_pid(const char* shm_name);
229
+
230
+ /**
231
+ * @brief Find and map a dangling allocator (whose creator pid is gone).
232
+ * @param shm_prefix Prefix to scan for (NULL for compile-time default).
233
+ * @param shm_name Output buffer to receive the dangling allocator name.
234
+ * @return Mapped allocator pointer or NULL with errno set.
235
+ */
236
+ static inline shm_allocator* c_shm_allocator_dangling(const char* shm_prefix, char* shm_name);
237
+
238
+ /**
239
+ * @brief Unlink all dangling allocator/page SHM objects.
240
+ * @param shm_prefix Prefix to scan for (NULL for compile-time defaults).
241
+ */
242
+ static inline void c_shm_clear_dangling(const char* shm_prefix);
243
+
244
+ // ========== Utility Functions ==========
245
+
246
+ static const size_t c_shm_page_overhead = (sizeof(shm_page) + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
247
+ static const size_t c_shm_block_overhead = (sizeof(shm_memory_block) + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
248
+
249
+ static inline size_t c_shm_page_roundup(shm_allocator* allocator, size_t size) {
250
+ return (size + allocator->autopage_alignment - 1) & ~(allocator->autopage_alignment - 1);
251
+ }
252
+
253
+ static inline size_t c_shm_block_roundup(size_t size) {
254
+ return (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
255
+ }
256
+
257
+ static inline void c_shm_allocator_name(const void* region, const char* shm_prefix, char* out) {
258
+ pid_t pid = getpid();
259
+ // The shm name should be in format of {prefix}_ac_{pid_hex}_{region_hex}
260
+ snprintf(out, AP_SHM_NAME_LEN, "%s_ac_%lx_%lx", shm_prefix, (long) pid, (unsigned long) (uintptr_t) region);
261
+ }
262
+
263
+ static inline void c_shm_page_name(shm_allocator* allocator, char* out) {
264
+ pid_t pid = getpid();
265
+ size_t page_idx = allocator->mapped_pages;
266
+ // The shm name should be in format of {prefix}_pg_{pid_hex}_{region_hex}_{page_idx_hex}
267
+ snprintf(out, AP_SHM_NAME_LEN, "%s_pg_%lx_%lx_%zx", allocator->shm_prefix, (long) pid, (unsigned long) allocator->region, page_idx);
268
+ }
269
+
270
+ static inline int c_shm_scan(const char* prefix, char* out) {
271
+ if (!prefix || !out) {
272
+ errno = EINVAL;
273
+ return -1;
274
+ }
275
+
276
+ if (prefix[0] == '/') {
277
+ prefix += 1;
278
+ }
279
+
280
+ DIR* dir = opendir("/dev/shm");
281
+ if (!dir) {
282
+ return -1;
283
+ }
284
+
285
+ size_t prefix_len = strlen(prefix);
286
+ struct dirent* ent = NULL;
287
+ int found = 0;
288
+
289
+ while ((ent = readdir(dir)) != NULL) {
290
+ if (ent->d_name[0] == '.') {
291
+ continue;
292
+ }
293
+ if (strncmp(ent->d_name, prefix, prefix_len) != 0) {
294
+ continue;
295
+ }
296
+
297
+ size_t name_len = strnlen(ent->d_name, AP_SHM_NAME_LEN - 1);
298
+ if (name_len + 2 <= AP_SHM_NAME_LEN) {
299
+ out[0] = '/';
300
+ memcpy(out + 1, ent->d_name, name_len);
301
+ out[1 + name_len] = '\0';
302
+ found = 1;
303
+ break;
304
+ }
305
+ }
306
+
307
+ closedir(dir);
308
+
309
+ if (!found) {
310
+ errno = ENOENT;
311
+ return -1;
312
+ }
313
+
314
+ return 0;
315
+ }
316
+
317
+ static inline shm_page_ctx* c_shm_page_new(shm_allocator* allocator, size_t page_capacity) {
318
+ shm_page_ctx* ctx = (shm_page_ctx*) calloc(1, sizeof(shm_page_ctx));
319
+ if (!ctx) {
320
+ return NULL;
321
+ }
322
+
323
+ shm_page* meta = (shm_page*) calloc(1, c_shm_page_overhead);
324
+ if (!meta) {
325
+ free(ctx);
326
+ return NULL;
327
+ }
328
+ ctx->shm_page = meta;
329
+
330
+ meta->capacity = page_capacity;
331
+ meta->occupied = c_shm_page_overhead;
332
+ meta->offset = 0;
333
+
334
+ c_shm_page_name(allocator, meta->shm_name);
335
+
336
+ int fd = shm_open(meta->shm_name, O_CREAT | O_RDWR | O_EXCL, 0600);
337
+ if (fd == -1) {
338
+ free(meta);
339
+ free(ctx);
340
+ return NULL;
341
+ }
342
+
343
+ if (ftruncate(fd, page_capacity) != 0) {
344
+ close(fd);
345
+ shm_unlink(meta->shm_name);
346
+ free(meta);
347
+ free(ctx);
348
+ return NULL;
349
+ }
350
+
351
+ ctx->shm_page = meta;
352
+ ctx->shm_fd = fd;
353
+ ctx->buffer = NULL;
354
+ ctx->prev = NULL;
355
+
356
+ return ctx;
357
+ }
358
+
359
+ static inline int c_shm_page_map(shm_allocator* allocator, shm_page_ctx* page_ctx) {
360
+ if (!allocator || !page_ctx || !page_ctx->shm_page || page_ctx->shm_fd < 0) {
361
+ errno = EINVAL;
362
+ return -1;
363
+ }
364
+
365
+ size_t page_capacity = page_ctx->shm_page->capacity;
366
+ size_t offset = allocator->mapped_size;
367
+
368
+ if (offset + page_capacity > allocator->region_size) {
369
+ errno = ENOMEM;
370
+ return -1;
371
+ }
372
+
373
+ /* compute target address from integer region base */
374
+ void* target_addr = (void*) ((char*) (uintptr_t) allocator->region + offset);
375
+ void* mapped = mmap(target_addr, page_capacity, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, page_ctx->shm_fd, 0);
376
+ if (mapped == MAP_FAILED) {
377
+ return -1;
378
+ }
379
+
380
+ // Step 0: Point to metadata at start of page
381
+ shm_page* page_meta = (shm_page*) mapped;
382
+ memcpy(page_meta, page_ctx->shm_page, c_shm_page_overhead);
383
+ free(page_ctx->shm_page);
384
+ page_ctx->shm_page = page_meta;
385
+ page_ctx->buffer = (char*) mapped; // occupied already included the overhead, so the buffer should start at mapped
386
+
387
+ // Step 1: Set correct offset
388
+ page_meta->offset = offset;
389
+ page_meta->allocator = allocator;
390
+
391
+ // Step 2: Set prev_name = current active_page (enables backward traversal)
392
+ if (allocator->mapped_pages == 0) {
393
+ page_meta->prev_name[0] = '\0';
394
+ }
395
+ else {
396
+ memcpy(page_meta->prev_name, allocator->active_page, AP_SHM_NAME_LEN);
397
+ page_meta->prev_name[AP_SHM_NAME_LEN - 1] = '\0';
398
+ }
399
+
400
+ // Step 4: Update allocator state
401
+ allocator->mapped_size += page_capacity;
402
+ memcpy(allocator->active_page, page_meta->shm_name, AP_SHM_NAME_LEN);
403
+ allocator->active_page[AP_SHM_NAME_LEN - 1] = '\0';
404
+ allocator->mapped_pages++;
405
+
406
+ return 0;
407
+ }
408
+
409
+ static inline void c_shm_page_reclaim(shm_allocator* allocator, shm_page_ctx* page_ctx) {
410
+ if (!allocator || !page_ctx || !page_ctx->shm_page) {
411
+ errno = EINVAL;
412
+ return;
413
+ }
414
+
415
+ shm_page* page = page_ctx->shm_page;
416
+
417
+ // Best-effort: walk from newest; stop at first live block.
418
+ shm_memory_block** prevp = &page->allocated;
419
+ while (*prevp) {
420
+ shm_memory_block* block = *prevp;
421
+ if (block->size != 0) {
422
+ break; // first live block reached; older blocks retained
423
+ }
424
+
425
+ // Remove from page allocation list
426
+ *prevp = block->next_allocated;
427
+ block->next_allocated = NULL;
428
+
429
+ // Remove from allocator free list (single occurrence)
430
+ shm_memory_block** free_prev = &allocator->free_list;
431
+ while (*free_prev && *free_prev != block) {
432
+ free_prev = &(*free_prev)->next_free;
433
+ }
434
+ if (*free_prev == block) {
435
+ *free_prev = block->next_free;
436
+ }
437
+ block->next_free = NULL;
438
+
439
+ // Return occupied size back to page
440
+ size_t cap_total = block->capacity + c_shm_block_overhead;
441
+ if (page->occupied >= cap_total) {
442
+ page->occupied -= cap_total;
443
+ }
444
+ }
445
+ }
446
+
447
+ // ========== Public API Functions ==========
448
+
449
+ static inline shm_page_ctx* c_shm_allocator_extend(shm_allocator_ctx* ctx, size_t capacity, pthread_mutex_t* lock) {
450
+ if (!ctx || !ctx->shm_allocator) {
451
+ errno = EINVAL;
452
+ return NULL;
453
+ }
454
+
455
+ shm_allocator* allocator = ctx->shm_allocator;
456
+ uint8_t locked = 0;
457
+
458
+ if (lock) {
459
+ int ret = pthread_mutex_lock(lock);
460
+ if (ret != 0) {
461
+ errno = ret;
462
+ return NULL;
463
+ }
464
+ locked = 1;
465
+ }
466
+
467
+ // Step 1: Determine new page capacity
468
+ if (capacity == 0) {
469
+ if (!ctx->active_page) {
470
+ capacity = allocator->autopage_capacity;
471
+ }
472
+ else {
473
+ size_t prev_cap = ctx->active_page->shm_page->capacity;
474
+ capacity = prev_cap * 2;
475
+ if (capacity < allocator->autopage_capacity) {
476
+ capacity = allocator->autopage_capacity;
477
+ }
478
+ else if (capacity > allocator->autopage_capacity_max) {
479
+ capacity = allocator->autopage_capacity_max;
480
+ }
481
+ }
482
+ }
483
+
484
+ size_t aligned_capacity = c_shm_page_roundup(allocator, capacity);
485
+
486
+ if (aligned_capacity + allocator->mapped_size > allocator->region_size) {
487
+ if (locked) pthread_mutex_unlock(lock);
488
+ errno = ENOMEM;
489
+ return NULL;
490
+ }
491
+
492
+ // Step 2: Create new page
493
+ shm_page_ctx* page_ctx = c_shm_page_new(allocator, aligned_capacity);
494
+ if (!page_ctx) {
495
+ if (locked) pthread_mutex_unlock(lock);
496
+ return NULL;
497
+ }
498
+
499
+ // Step 3: Map new page into allocator region
500
+ if (c_shm_page_map(allocator, page_ctx) != 0) {
501
+ shm_unlink(page_ctx->shm_page->shm_name);
502
+ close(page_ctx->shm_fd);
503
+ free(page_ctx->shm_page); // temp heap metadata
504
+ free(page_ctx);
505
+ if (locked) pthread_mutex_unlock(lock);
506
+ return NULL;
507
+ }
508
+
509
+ // Step 4: Update context active_page
510
+ page_ctx->prev = ctx->active_page;
511
+ ctx->active_page = page_ctx;
512
+
513
+ // Unlock if we locked here
514
+ if (locked) pthread_mutex_unlock(lock);
515
+ return page_ctx;
516
+ }
517
+
518
+ static inline shm_allocator_ctx* c_shm_allocator_new(size_t region_size, const char* shm_prefix) {
519
+ shm_allocator_ctx* ctx = (shm_allocator_ctx*) calloc(1, sizeof(shm_allocator_ctx));
520
+ if (!ctx) {
521
+ return NULL;
522
+ }
523
+
524
+ if (!shm_prefix) {
525
+ shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
526
+ }
527
+
528
+ size_t prefix_len = strnlen(shm_prefix, AP_SHM_PREFIX_MAX);
529
+ if (prefix_len >= AP_SHM_PREFIX_MAX) {
530
+ fprintf(stderr, "c_shm_allocator_new: shm_prefix exceeds AP_SHM_PREFIX_MAX (%zu)\n", (size_t) AP_SHM_PREFIX_MAX);
531
+ abort();
532
+ }
533
+
534
+ if (region_size == 0) {
535
+ region_size = AP_SHM_ALLOCATOR_DEFAULT_REGION_SIZE; // 128 GiB
536
+ }
537
+
538
+ // Step 1: Reserve virtual address space for the 128 GiB page region
539
+ void* virtual_region = mmap(
540
+ NULL, region_size,
541
+ PROT_NONE,
542
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE,
543
+ -1, 0
544
+ );
545
+ if (virtual_region == MAP_FAILED) {
546
+ free(ctx);
547
+ return NULL;
548
+ }
549
+
550
+ // Step 2: Create SHM object for allocator metadata
551
+ char meta_shm_name[AP_SHM_NAME_LEN];
552
+ c_shm_allocator_name(virtual_region, shm_prefix, meta_shm_name);
553
+
554
+ int meta_fd = shm_open(meta_shm_name, O_CREAT | O_RDWR | O_EXCL, 0600);
555
+ if (meta_fd == -1) {
556
+ munmap(virtual_region, region_size);
557
+ free(ctx);
558
+ return NULL;
559
+ }
560
+
561
+ if (ftruncate(meta_fd, sizeof(shm_allocator)) != 0) {
562
+ close(meta_fd);
563
+ shm_unlink(meta_shm_name);
564
+ munmap(virtual_region, region_size);
565
+ free(ctx);
566
+ return NULL;
567
+ }
568
+
569
+ // Step 3: Map allocator metadata into memory
570
+ shm_allocator* meta = (shm_allocator*) mmap(NULL, sizeof(shm_allocator), PROT_READ | PROT_WRITE, MAP_SHARED, meta_fd, 0);
571
+ if (meta == MAP_FAILED) {
572
+ close(meta_fd);
573
+ shm_unlink(meta_shm_name);
574
+ munmap(virtual_region, region_size);
575
+ free(ctx);
576
+ return NULL;
577
+ }
578
+
579
+ // Step 4: Initialize metadata IN SHARED MEMORY
580
+ // Must zero first (mmap doesn't guarantee zeroed memory for ftruncate'd SHM)
581
+ memset(meta, 0, sizeof(shm_allocator));
582
+
583
+ size_t name_len = strnlen(meta_shm_name, AP_SHM_NAME_LEN - 1);
584
+ memcpy(meta->shm_name, meta_shm_name, name_len);
585
+ meta->shm_name[name_len] = '\0';
586
+ meta->pid = (size_t) getpid();
587
+ meta->region = (uintptr_t) virtual_region;
588
+ meta->region_size = region_size;
589
+ meta->mapped_size = 0;
590
+ meta->mapped_pages = 0;
591
+ meta->active_page[0] = '\0';
592
+ meta->autopage_capacity = AP_SHM_AUTOPAGE_CAPACITY;
593
+ meta->autopage_capacity_max = AP_SHM_AUTOPAGE_CAPACITY_MAX;
594
+ meta->autopage_alignment = AP_SHM_AUTOPAGE_ALIGNMENT;
595
+
596
+ memcpy(meta->shm_prefix, shm_prefix, prefix_len);
597
+ meta->shm_prefix[prefix_len] = '\0';
598
+
599
+ // Step 5: Initialize mutex in shared memory with PTHREAD_PROCESS_SHARED
600
+ pthread_mutexattr_t mattr;
601
+ if (pthread_mutexattr_init(&mattr) != 0) {
602
+ goto cleanup;
603
+ }
604
+ if (pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED) != 0) {
605
+ pthread_mutexattr_destroy(&mattr);
606
+ goto cleanup;
607
+ }
608
+ if (pthread_mutex_init(&meta->lock, &mattr) != 0) {
609
+ pthread_mutexattr_destroy(&mattr);
610
+ goto cleanup;
611
+ }
612
+ pthread_mutexattr_destroy(&mattr);
613
+
614
+ // Step 6: Fill context
615
+ ctx->shm_allocator = meta;
616
+ ctx->shm_fd = meta_fd;
617
+ ctx->active_page = NULL;
618
+
619
+ return ctx;
620
+
621
+ cleanup:
622
+ munmap(virtual_region, region_size);
623
+ munmap(meta, sizeof(shm_allocator));
624
+ close(meta_fd);
625
+ shm_unlink(meta_shm_name);
626
+ free(ctx);
627
+ return NULL;
628
+ }
629
+
630
+ static inline void c_shm_allocator_free(shm_allocator_ctx* ctx) {
631
+ if (!ctx) {
632
+ return;
633
+ }
634
+
635
+ char shm_name[AP_SHM_NAME_LEN];
636
+
637
+ // Step 1: Unmap all pages
638
+ shm_page_ctx* page_ctx = ctx->active_page;
639
+ while (page_ctx) {
640
+ shm_page_ctx* prev = page_ctx->prev;
641
+ shm_page* page_meta = page_ctx->shm_page;
642
+
643
+ strncpy(shm_name, page_meta->shm_name, sizeof(shm_name) - 1);
644
+ shm_name[sizeof(shm_name) - 1] = '\0';
645
+
646
+ if (page_meta) {
647
+ munmap((void*) page_meta, page_meta->capacity);
648
+ shm_unlink(shm_name);
649
+ }
650
+
651
+ if (page_ctx->shm_fd >= 0) {
652
+ close(page_ctx->shm_fd);
653
+ }
654
+
655
+ free(page_ctx);
656
+ page_ctx = prev;
657
+ }
658
+
659
+ // Step 2: Unmap allocator region
660
+ shm_allocator* allocator = ctx->shm_allocator;
661
+ if (allocator) {
662
+ // Step 2.1: Save shm_name before unmapping
663
+ strncpy(shm_name, allocator->shm_name, sizeof(shm_name) - 1);
664
+ shm_name[sizeof(shm_name) - 1] = '\0';
665
+
666
+ // Step 2.2: Destroy mutex
667
+ pthread_mutex_destroy(&allocator->lock);
668
+
669
+ // Step 2.3: Unmap region
670
+ if (allocator->region) {
671
+ munmap((void*) allocator->region, allocator->region_size);
672
+ }
673
+
674
+ // Step 2.4: Unmap allocator shm
675
+ munmap((void*) allocator, sizeof(shm_allocator));
676
+
677
+ // Step 2.5: Unlink allocator SHM
678
+ shm_unlink(shm_name);
679
+ }
680
+
681
+ // Step 3: Close allocator SHM fd
682
+ if (ctx->shm_fd >= 0) {
683
+ close(ctx->shm_fd);
684
+ }
685
+
686
+ // Step 4: Free context
687
+ free(ctx);
688
+ }
689
+
690
+ static inline void* c_shm_calloc(shm_allocator_ctx* ctx, size_t size, pthread_mutex_t* lock) {
691
+ if (!ctx || !ctx->shm_allocator || size == 0) {
692
+ errno = EINVAL;
693
+ return NULL;
694
+ }
695
+
696
+ size_t cap_net = c_shm_block_roundup(size);
697
+ // the overhead is already aligned due to struct padding
698
+ size_t cap_total = cap_net + c_shm_block_overhead;
699
+ shm_allocator* allocator = ctx->shm_allocator;
700
+
701
+ // Step 1: Lock allocator
702
+ // Locking strategy:
703
+ // - If lock is NULL, caller is responsible for locking, calloc and extend will not be locked
704
+ // - If lock is allocator's builtin lock, calloc will be locked, extend will not be locked to void double lock
705
+ // - If lock is a different lock, calloc will use the provided lock, extend will also use the builtin lock
706
+
707
+ uint8_t locked = 0;
708
+ pthread_mutex_t* builtin_lock = &allocator->lock;
709
+ pthread_mutex_t* child_lock = &allocator->lock;
710
+ if (lock) {
711
+ if (lock == builtin_lock) {
712
+ child_lock = NULL; // avoid double locking
713
+ }
714
+ int ret = pthread_mutex_lock(lock);
715
+ if (ret != 0) {
716
+ errno = ret;
717
+ return NULL;
718
+ }
719
+ locked = 1;
720
+ }
721
+ else {
722
+ child_lock = NULL; // caller is responsible for locking
723
+ }
724
+
725
+ // Step 2: Extend the allocator if there is no active_page or insufficient space
726
+ shm_page_ctx* page_ctx = ctx->active_page;
727
+ if (!page_ctx) {
728
+ // Step 2.1: Roundup to nearest allocator->autopage_capacity * 2^n, for the first page
729
+ size_t target_cap = allocator->autopage_capacity;
730
+ while (target_cap < cap_total + c_shm_page_overhead) {
731
+ target_cap *= 2;
732
+ }
733
+
734
+ page_ctx = c_shm_allocator_extend(ctx, target_cap, child_lock);
735
+ if (!page_ctx) {
736
+ if (locked) pthread_mutex_unlock(lock);
737
+ return NULL;
738
+ }
739
+ }
740
+
741
+ shm_page* page_meta = page_ctx->shm_page;
742
+
743
+ // Step 2: Extend the allocator if there is no active_page or insufficient space
744
+ if (page_meta->occupied + cap_total > page_meta->capacity) {
745
+ // Step 2.1: Determine new page capacity
746
+ size_t target_cap = page_meta->capacity;
747
+
748
+ if (target_cap < allocator->autopage_capacity) {
749
+ target_cap = allocator->autopage_capacity;
750
+ }
751
+ else if (target_cap < allocator->autopage_capacity_max) {
752
+ target_cap *= 2;
753
+ }
754
+
755
+ // Step 2.1: Roundup to nearest allocator->autopage_capacity * 2^n, for the first page
756
+ while (target_cap < cap_total + c_shm_page_overhead) {
757
+ target_cap *= 2;
758
+ }
759
+ page_ctx = c_shm_allocator_extend(ctx, target_cap, child_lock);
760
+ if (!page_ctx) {
761
+ if (locked) pthread_mutex_unlock(lock);
762
+ return NULL;
763
+ }
764
+
765
+ page_meta = page_ctx->shm_page;
766
+ }
767
+
768
+ // Step 3: Allocate memory from active page
769
+ size_t offset = page_meta->occupied;
770
+ shm_memory_block* block = (shm_memory_block*) (page_ctx->buffer + offset);
771
+ block->capacity = cap_net;
772
+ block->size = size;
773
+ block->next_free = NULL;
774
+
775
+ // Step 4: Link fresh block to its page (calloc path is always fresh)
776
+ block->parent_page = page_meta;
777
+ block->next_allocated = page_meta->allocated;
778
+ page_meta->allocated = block;
779
+
780
+ page_meta->occupied += cap_total;
781
+
782
+ // Step 5: Unlock allocator
783
+ if (locked) pthread_mutex_unlock(lock);
784
+
785
+ memset(block + 1, 0, cap_net);
786
+ return (void*) block->buffer;
787
+ }
788
+
789
+ static inline void* c_shm_request(shm_allocator_ctx* ctx, size_t size, int scan_all_pages, pthread_mutex_t* lock) {
790
+ if (!ctx || !ctx->shm_allocator || size == 0) {
791
+ errno = EINVAL;
792
+ return NULL;
793
+ }
794
+
795
+ size_t cap_net = c_shm_block_roundup(size);
796
+ size_t cap_total = cap_net + c_shm_block_overhead;
797
+ shm_allocator* allocator = ctx->shm_allocator;
798
+
799
+ uint8_t locked = 0;
800
+ pthread_mutex_t* builtin_lock = &allocator->lock;
801
+ pthread_mutex_t* child_lock = &allocator->lock;
802
+ if (lock) {
803
+ if (lock == builtin_lock) {
804
+ child_lock = NULL;
805
+ }
806
+ int ret = pthread_mutex_lock(lock);
807
+ if (ret != 0) {
808
+ errno = ret;
809
+ return NULL;
810
+ }
811
+ locked = 1;
812
+ }
813
+ else {
814
+ child_lock = NULL;
815
+ }
816
+
817
+ // Step 1: Try allocator-level free list reuse
818
+ shm_memory_block** prevp = &allocator->free_list;
819
+ shm_memory_block* free_blk = allocator->free_list;
820
+ while (free_blk) {
821
+ if (free_blk->capacity >= cap_net) {
822
+ *prevp = free_blk->next_free;
823
+ free_blk->next_free = NULL;
824
+ free_blk->size = size;
825
+ if (locked) pthread_mutex_unlock(lock);
826
+ memset(free_blk + 1, 0, cap_net);
827
+ return (void*) free_blk->buffer;
828
+ }
829
+ prevp = &free_blk->next_free;
830
+ free_blk = free_blk->next_free;
831
+ }
832
+
833
+ // Step 2: Locate a page with enough space (optionally scan all pages)
834
+ shm_page_ctx* target_ctx = NULL;
835
+ if (scan_all_pages) {
836
+ shm_page_ctx* iter = ctx->active_page;
837
+ while (iter) {
838
+ shm_page* meta = iter->shm_page;
839
+ if (meta && meta->occupied + cap_total <= meta->capacity) {
840
+ target_ctx = iter;
841
+ break;
842
+ }
843
+ iter = iter->prev;
844
+ }
845
+ }
846
+ else if (ctx->active_page) {
847
+ shm_page* meta = ctx->active_page->shm_page;
848
+ if (meta && meta->occupied + cap_total <= meta->capacity) {
849
+ target_ctx = ctx->active_page;
850
+ }
851
+ }
852
+
853
+ // Step 3: Extend allocator if no existing page fits
854
+ if (!target_ctx) {
855
+ shm_page_ctx* current = ctx->active_page;
856
+ size_t target_cap;
857
+
858
+ if (!current) {
859
+ target_cap = allocator->autopage_capacity;
860
+ while (target_cap < cap_total + c_shm_page_overhead) {
861
+ target_cap *= 2;
862
+ }
863
+ }
864
+ else {
865
+ size_t prev_cap = current->shm_page->capacity;
866
+ size_t new_cap = prev_cap;
867
+
868
+ if (new_cap < allocator->autopage_capacity) {
869
+ new_cap = allocator->autopage_capacity;
870
+ }
871
+ else if (new_cap < allocator->autopage_capacity_max) {
872
+ new_cap *= 2;
873
+ }
874
+
875
+ while (new_cap < cap_total + c_shm_page_overhead) {
876
+ new_cap *= 2;
877
+ }
878
+ target_cap = new_cap;
879
+ }
880
+
881
+ target_ctx = c_shm_allocator_extend(ctx, target_cap, child_lock);
882
+ if (!target_ctx) {
883
+ if (locked) pthread_mutex_unlock(lock);
884
+ return NULL;
885
+ }
886
+ }
887
+
888
+ // Step 4: Allocate from the selected page
889
+ shm_page* page_meta = target_ctx->shm_page;
890
+ size_t offset = page_meta->occupied;
891
+ shm_memory_block* block = (shm_memory_block*) (target_ctx->buffer + offset);
892
+ block->capacity = cap_net;
893
+ block->size = size;
894
+ block->next_free = NULL;
895
+
896
+ // Step 5: Link block to its page for potential recycling scans
897
+ block->parent_page = page_meta;
898
+ block->next_allocated = page_meta->allocated;
899
+ page_meta->allocated = block;
900
+
901
+ page_meta->occupied += cap_total;
902
+
903
+ if (locked) pthread_mutex_unlock(lock);
904
+
905
+ memset(block + 1, 0, cap_net);
906
+ return (void*) block->buffer;
907
+ }
908
+
909
+ static inline void c_shm_free(void* ptr, pthread_mutex_t* lock) {
910
+ if (!ptr) {
911
+ errno = EINVAL;
912
+ return;
913
+ }
914
+
915
+ shm_memory_block* block = (shm_memory_block*) ((char*) ptr - c_shm_block_overhead);
916
+ shm_page* page = block->parent_page;
917
+ if (!page || !page->allocator) {
918
+ errno = EINVAL;
919
+ return;
920
+ }
921
+
922
+ shm_allocator* allocator = page->allocator;
923
+
924
+ uint8_t locked = 0;
925
+ if (lock) {
926
+ int ret = pthread_mutex_lock(lock);
927
+ if (ret != 0) {
928
+ errno = ret;
929
+ return;
930
+ }
931
+ locked = 1;
932
+ }
933
+
934
+ block->size = 0;
935
+ block->next_free = allocator->free_list;
936
+ allocator->free_list = block;
937
+
938
+ if (locked) pthread_mutex_unlock(lock);
939
+ }
940
+
941
+ static inline void c_shm_reclaim(shm_allocator_ctx* ctx, pthread_mutex_t* lock) {
942
+ if (!ctx || !ctx->shm_allocator) {
943
+ errno = EINVAL;
944
+ return;
945
+ }
946
+
947
+ uint8_t locked = 0;
948
+ if (lock) {
949
+ int ret = pthread_mutex_lock(lock);
950
+ if (ret != 0) {
951
+ errno = ret;
952
+ return;
953
+ }
954
+ locked = 1;
955
+ }
956
+
957
+ shm_allocator* allocator = ctx->shm_allocator;
958
+ shm_page_ctx* page_ctx = ctx->active_page;
959
+ while (page_ctx) {
960
+ c_shm_page_reclaim(allocator, page_ctx);
961
+ page_ctx = page_ctx->prev;
962
+ }
963
+
964
+ if (locked) pthread_mutex_unlock(lock);
965
+ }
966
+
967
+ // ========== Internal SHM Management API ==========
968
+
969
+ static inline int c_shm_scan_allocator(const char* shm_prefix, char* out) {
970
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
971
+ char scan_prefix[AP_SHM_PREFIX_MAX + 4]; // +4 for "_ac\0"
972
+ snprintf(scan_prefix, sizeof(scan_prefix), "%s_ac", shm_prefix);
973
+ return c_shm_scan(scan_prefix, out);
974
+ }
975
+
976
+ static inline int c_shm_scan_page(const char* shm_prefix, char* out) {
977
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
978
+ char scan_prefix[AP_SHM_PREFIX_MAX + 4]; // +4 for "_pg\0"
979
+ snprintf(scan_prefix, sizeof(scan_prefix), "%s_pg", shm_prefix);
980
+ return c_shm_scan(scan_prefix, out);
981
+ }
982
+
983
+ static inline pid_t c_shm_pid(const char* shm_name) {
984
+ if (!shm_name) {
985
+ errno = EINVAL;
986
+ return -1;
987
+ }
988
+
989
+ const char* base = shm_name;
990
+ if (base[0] == '/') {
991
+ base++;
992
+ }
993
+
994
+ // Name format: {prefix}_{pid_hex}_{suffix}
995
+ // Walk from the right: find last '_' (suffix separator),
996
+ // then the '_' before it (pid separator).
997
+ const char* suffix_us = strrchr(base, '_');
998
+ if (!suffix_us) {
999
+ errno = EINVAL;
1000
+ return -1;
1001
+ }
1002
+
1003
+ // Find the underscore that starts the pid segment (rightmost before suffix)
1004
+ const char* pid_us = NULL;
1005
+ for (const char* p = base; p < suffix_us; p++) {
1006
+ if (*p == '_') pid_us = p;
1007
+ }
1008
+ if (!pid_us) {
1009
+ errno = EINVAL;
1010
+ return -1;
1011
+ }
1012
+
1013
+ const char* pid_start = pid_us + 1;
1014
+ size_t pid_len = (size_t) (suffix_us - pid_start);
1015
+ if (pid_len == 0 || pid_len >= 32) {
1016
+ errno = EINVAL;
1017
+ return -1;
1018
+ }
1019
+
1020
+ char pidbuf[32];
1021
+ memcpy(pidbuf, pid_start, pid_len);
1022
+ pidbuf[pid_len] = '\0';
1023
+
1024
+ errno = 0;
1025
+ char* endptr = NULL;
1026
+ unsigned long v = strtoul(pidbuf, &endptr, 16);
1027
+ if (errno != 0 || endptr == pidbuf || *endptr != '\0') {
1028
+ errno = EINVAL;
1029
+ return -1;
1030
+ }
1031
+
1032
+ if (v > (unsigned long) INT_MAX) {
1033
+ errno = EINVAL;
1034
+ return -1;
1035
+ }
1036
+
1037
+ return (pid_t) v;
1038
+ }
1039
+
1040
+ static inline shm_allocator* c_shm_allocator_dangling(const char* shm_prefix, char* shm_name) {
1041
+ if (!shm_name) {
1042
+ errno = EINVAL;
1043
+ return NULL;
1044
+ }
1045
+
1046
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
1047
+
1048
+ char scan_prefix[AP_SHM_PREFIX_MAX + 4];
1049
+ snprintf(scan_prefix, sizeof(scan_prefix), "%s_ac", shm_prefix);
1050
+
1051
+ const char* fs_prefix = scan_prefix;
1052
+ if (fs_prefix[0] == '/') fs_prefix += 1;
1053
+ size_t prefix_len = strlen(fs_prefix);
1054
+
1055
+ DIR* dir = opendir("/dev/shm");
1056
+ if (!dir) {
1057
+ return NULL;
1058
+ }
1059
+
1060
+ struct dirent* ent = NULL;
1061
+ shm_allocator* mapped = NULL;
1062
+ char candidate[AP_SHM_NAME_LEN];
1063
+
1064
+ while ((ent = readdir(dir)) != NULL) {
1065
+ if (ent->d_name[0] == '.') continue;
1066
+ if (strncmp(ent->d_name, fs_prefix, prefix_len) != 0) continue;
1067
+
1068
+ size_t name_len = strnlen(ent->d_name, AP_SHM_NAME_LEN - 1);
1069
+ if (name_len + 2 > AP_SHM_NAME_LEN) {
1070
+ continue; // would truncate; skip this entry
1071
+ }
1072
+ candidate[0] = '/';
1073
+ memcpy(candidate + 1, ent->d_name, name_len);
1074
+ candidate[1 + name_len] = '\0';
1075
+
1076
+ pid_t pid = c_shm_pid(candidate);
1077
+ if (pid <= 0) continue;
1078
+
1079
+ if (kill(pid, 0) == 0 || errno != ESRCH) {
1080
+ errno = 0; // either alive or another error; treat as not dangling
1081
+ continue;
1082
+ }
1083
+
1084
+ int fd = shm_open(candidate, O_RDWR, 0);
1085
+ if (fd == -1) {
1086
+ continue;
1087
+ }
1088
+
1089
+ struct stat st;
1090
+ if (fstat(fd, &st) != 0) {
1091
+ close(fd);
1092
+ continue;
1093
+ }
1094
+
1095
+ void* map = mmap(NULL, (size_t) st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1096
+ close(fd);
1097
+ if (map == MAP_FAILED) {
1098
+ continue;
1099
+ }
1100
+
1101
+ strncpy(shm_name, candidate, AP_SHM_NAME_LEN - 1);
1102
+ shm_name[AP_SHM_NAME_LEN - 1] = '\0';
1103
+ mapped = (shm_allocator*) map;
1104
+ break;
1105
+ }
1106
+
1107
+ closedir(dir);
1108
+
1109
+ if (!mapped) {
1110
+ errno = ENOENT;
1111
+ }
1112
+
1113
+ return mapped;
1114
+ }
1115
+
1116
+ static inline void c_shm_clear_dangling(const char* shm_prefix) {
1117
+ DIR* dir = opendir("/dev/shm");
1118
+ if (!dir) {
1119
+ return;
1120
+ }
1121
+
1122
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
1123
+
1124
+ const char* suffixes[] = {"_ac", "_pg"};
1125
+ size_t suffix_count = 2;
1126
+
1127
+ struct dirent* ent = NULL;
1128
+ while ((ent = readdir(dir)) != NULL) {
1129
+ if (ent->d_name[0] == '.') continue;
1130
+
1131
+ int matched = 0;
1132
+ for (size_t i = 0; i < suffix_count; ++i) {
1133
+ char full_prefix[AP_SHM_PREFIX_MAX + 4];
1134
+ snprintf(full_prefix, sizeof(full_prefix), "%s%s", shm_prefix, suffixes[i]);
1135
+ const char* p = full_prefix;
1136
+ if (p[0] == '/') p += 1;
1137
+ size_t len = strlen(p);
1138
+ if (strncmp(ent->d_name, p, len) == 0) {
1139
+ matched = 1;
1140
+ break;
1141
+ }
1142
+ }
1143
+ if (!matched) continue;
1144
+
1145
+ char candidate[AP_SHM_NAME_LEN];
1146
+ size_t name_len = strnlen(ent->d_name, AP_SHM_NAME_LEN - 1);
1147
+ if (name_len + 2 > AP_SHM_NAME_LEN) {
1148
+ continue; // would truncate; skip this entry
1149
+ }
1150
+ candidate[0] = '/';
1151
+ memcpy(candidate + 1, ent->d_name, name_len);
1152
+ candidate[1 + name_len] = '\0';
1153
+
1154
+ pid_t pid = c_shm_pid(candidate);
1155
+ if (pid <= 0) {
1156
+ continue;
1157
+ }
1158
+
1159
+ errno = 0;
1160
+ if (kill(pid, 0) == -1 && errno == ESRCH) {
1161
+ shm_unlink(candidate); // best effort
1162
+ }
1163
+ }
1164
+
1165
+ closedir(dir);
1166
+ }
1167
+
1168
+ #endif // AP_SHM_C_SHM_ALLOCATOR_H