PyCyBase 0.1.10__cp315-cp315-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 (105) hide show
  1. cbase/__infra__.pxd +6 -0
  2. cbase/__init__.pxd +6 -0
  3. cbase/__init__.py +31 -0
  4. cbase/allocator_protocol/__infra__.pxd +65 -0
  5. cbase/allocator_protocol/__init__.pxd +65 -0
  6. cbase/allocator_protocol/__init__.py +45 -0
  7. cbase/allocator_protocol/c_allocator_protocol.c +31874 -0
  8. cbase/allocator_protocol/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  9. cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  10. cbase/allocator_protocol/c_allocator_protocol.pxd +66 -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 +38225 -0
  14. cbase/allocator_protocol/c_heap_allocator.cp315-win_amd64.pyd +0 -0
  15. cbase/allocator_protocol/c_heap_allocator.h +554 -0
  16. cbase/allocator_protocol/c_heap_allocator.pxd +105 -0
  17. cbase/allocator_protocol/c_heap_allocator.pyi +161 -0
  18. cbase/allocator_protocol/c_heap_allocator.pyx +303 -0
  19. cbase/allocator_protocol/c_nt_shm_allocator.c +33455 -0
  20. cbase/allocator_protocol/c_nt_shm_allocator.cp315-win_amd64.pyd +0 -0
  21. cbase/allocator_protocol/c_nt_shm_allocator.h +1105 -0
  22. cbase/allocator_protocol/c_nt_shm_allocator.pxd +110 -0
  23. cbase/allocator_protocol/c_nt_shm_allocator.pyx +223 -0
  24. cbase/allocator_protocol/c_shm_allocator.h +1270 -0
  25. cbase/allocator_protocol/c_shm_allocator.pxd +150 -0
  26. cbase/allocator_protocol/c_shm_allocator.pyi +299 -0
  27. cbase/allocator_protocol/c_shm_allocator.pyx +445 -0
  28. cbase/allocator_protocol/c_shm_comp.c +4654 -0
  29. cbase/allocator_protocol/c_shm_comp.cp315-win_amd64.pyd +0 -0
  30. cbase/allocator_protocol/c_shm_comp.h +49 -0
  31. cbase/allocator_protocol/c_shm_comp.pxd +46 -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 +9965 -0
  39. cbase/backports/pylong.cp315-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 +52151 -0
  47. cbase/bytemap/c_bytemap.cp315-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 +1760 -0
  51. cbase/bytemap/xxh3.h +55 -0
  52. cbase/bytemap/xxhash.c +42 -0
  53. cbase/bytemap/xxhash.h +7487 -0
  54. cbase/config_view.c +7253 -0
  55. cbase/config_view.cp315-win_amd64.pyd +0 -0
  56. cbase/config_view.pyx +85 -0
  57. cbase/env.c +11120 -0
  58. cbase/env.cp315-win_amd64.pyd +0 -0
  59. cbase/env.pxd +8 -0
  60. cbase/env.pyi +48 -0
  61. cbase/env.pyx +46 -0
  62. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.c +31874 -0
  63. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  64. cbase/includes/cbase/allocator_protocol/c_heap_allocator.c +38225 -0
  65. cbase/includes/cbase/allocator_protocol/c_heap_allocator.h +554 -0
  66. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.c +33455 -0
  67. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.h +1105 -0
  68. cbase/includes/cbase/allocator_protocol/c_shm_allocator.h +1270 -0
  69. cbase/includes/cbase/allocator_protocol/c_shm_comp.c +4654 -0
  70. cbase/includes/cbase/allocator_protocol/c_shm_comp.h +49 -0
  71. cbase/includes/cbase/backports/pydict.h +55 -0
  72. cbase/includes/cbase/backports/pylong.c +9965 -0
  73. cbase/includes/cbase/backports/pylong.h +79 -0
  74. cbase/includes/cbase/bytemap/c_bytemap.c +52151 -0
  75. cbase/includes/cbase/bytemap/c_bytemap.h +867 -0
  76. cbase/includes/cbase/bytemap/xxh3.h +55 -0
  77. cbase/includes/cbase/bytemap/xxhash.c +42 -0
  78. cbase/includes/cbase/bytemap/xxhash.h +7487 -0
  79. cbase/includes/cbase/config_view.c +7253 -0
  80. cbase/includes/cbase/env.c +11120 -0
  81. cbase/includes/cbase/int128.h +81 -0
  82. cbase/includes/cbase/intern_string/c_intern_string.c +28138 -0
  83. cbase/includes/cbase/intern_string/c_intern_string.h +614 -0
  84. cbase/includes/cbase/nt/pthread_nt_compat.h +102 -0
  85. cbase/includes/cbase/nt/regex_nt_compat.h +133 -0
  86. cbase/includes/cbase/nt/time_sched_nt_compat.h +32 -0
  87. cbase/int128.h +81 -0
  88. cbase/int128.pxd +15 -0
  89. cbase/intern_string/BENCHMARK.md +157 -0
  90. cbase/intern_string/__infra__.pxd +13 -0
  91. cbase/intern_string/__init__.pxd +13 -0
  92. cbase/intern_string/__init__.py +19 -0
  93. cbase/intern_string/c_intern_string.c +28138 -0
  94. cbase/intern_string/c_intern_string.cp315-win_amd64.pyd +0 -0
  95. cbase/intern_string/c_intern_string.h +614 -0
  96. cbase/intern_string/c_intern_string.pxd +121 -0
  97. cbase/intern_string/c_intern_string.pyi +376 -0
  98. cbase/intern_string/c_intern_string.pyx +765 -0
  99. cbase/nt/pthread_nt_compat.h +102 -0
  100. cbase/nt/regex_nt_compat.h +133 -0
  101. cbase/nt/time_sched_nt_compat.h +32 -0
  102. pycybase-0.1.10.dist-info/METADATA +190 -0
  103. pycybase-0.1.10.dist-info/RECORD +105 -0
  104. pycybase-0.1.10.dist-info/WHEEL +5 -0
  105. pycybase-0.1.10.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1105 @@
1
+ #ifndef AP_SHM_C_NT_SHM_ALLOCATOR_H
2
+ #define AP_SHM_C_NT_SHM_ALLOCATOR_H
3
+
4
+ #ifndef _WIN32
5
+ #error "c_nt_shm_allocator.h is intended for Windows builds only"
6
+ #endif
7
+
8
+ #ifndef WIN32_LEAN_AND_MEAN
9
+ #define WIN32_LEAN_AND_MEAN
10
+ #endif
11
+
12
+ #include <errno.h>
13
+ #include <stddef.h>
14
+ #include <stdint.h>
15
+ #include <stdio.h>
16
+ #include <stdlib.h>
17
+ #include <string.h>
18
+ #include <windows.h>
19
+
20
+ #include "cbase/nt/pthread_nt_compat.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
+ /* Size-binned free lists: exact 8-byte-granular bins for capacities up to
53
+ * AP_SHM_EXACT_BIN_COUNT * 8 bytes, pow2-class bins above. */
54
+ #ifndef AP_SHM_EXACT_BIN_COUNT
55
+ #define AP_SHM_EXACT_BIN_COUNT 8192 /* capacity <= 64 KiB gets an exact bin */
56
+ #endif
57
+
58
+ #ifndef AP_SHM_LARGE_BIN_COUNT
59
+ #define AP_SHM_LARGE_BIN_COUNT 9 /* pow2-class bins for capacity > 64 KiB */
60
+ #endif
61
+
62
+ #define AP_SHM_BIN_COUNT (AP_SHM_EXACT_BIN_COUNT + AP_SHM_LARGE_BIN_COUNT + 1)
63
+
64
+ /* Miss-path page scaling cap: page sizes double from the current page up
65
+ * to this cap; a request larger than the cap forces a bigger page.
66
+ * Used only when AP_SHM_PAGE_FIT_TO_REQUEST is 0. */
67
+ #ifndef AP_SHM_PAGE_EXTEND_MAX
68
+ #define AP_SHM_PAGE_EXTEND_MAX (128 * 1024 * 1024) /* 128 MiB */
69
+ #endif
70
+
71
+ /* Miss-path page policy: 0 (default) = capped power-of-2 page scaling
72
+ * (see AP_SHM_PAGE_EXTEND_MAX); 1 = fit the page to the request (floor
73
+ * = autopage_capacity) so rare bin misses never extend oversized pages. */
74
+ #ifndef AP_SHM_PAGE_FIT_TO_REQUEST
75
+ #define AP_SHM_PAGE_FIT_TO_REQUEST 0
76
+ #endif
77
+
78
+ /* On an exact-bin miss, probe up to this many LARGER exact bins before
79
+ * the page path. A slightly larger block satisfies the request; on free
80
+ * it returns to its own bin (recycling unaffected), and the fragmentation
81
+ * is bounded by the probe range. 0 disables the probe. */
82
+ #ifndef AP_SHM_EXACT_BIN_PROBE_COUNT
83
+ #define AP_SHM_EXACT_BIN_PROBE_COUNT 2
84
+ #endif
85
+
86
+ // ========== Structs ==========
87
+
88
+ typedef struct nt_shm_page {
89
+ size_t capacity;
90
+ size_t occupied;
91
+ size_t offset;
92
+ struct nt_shm_allocator* allocator;
93
+ struct nt_shm_memory_block* allocated;
94
+ char shm_name[AP_SHM_NAME_LEN];
95
+ char prev_name[AP_SHM_NAME_LEN];
96
+ } nt_shm_page;
97
+
98
+ typedef struct nt_shm_page_ctx {
99
+ nt_shm_page* shm_page;
100
+ HANDLE handle;
101
+ char* buffer;
102
+ struct nt_shm_page_ctx* prev;
103
+ } nt_shm_page_ctx;
104
+
105
+ typedef struct nt_shm_memory_block {
106
+ size_t capacity;
107
+ size_t size;
108
+ struct nt_shm_memory_block* next_free;
109
+ struct nt_shm_memory_block* next_allocated;
110
+ nt_shm_page* parent_page;
111
+ char buffer[];
112
+ } nt_shm_memory_block;
113
+
114
+ typedef struct nt_shm_allocator {
115
+ char shm_name[AP_SHM_NAME_LEN];
116
+ size_t pid;
117
+ char lock_name[AP_SHM_NAME_LEN];
118
+ size_t mapped_size;
119
+ char active_page[AP_SHM_NAME_LEN];
120
+ size_t mapped_pages;
121
+ size_t autopage_capacity;
122
+ size_t autopage_capacity_max;
123
+ size_t autopage_alignment;
124
+ nt_shm_memory_block* bins[AP_SHM_BIN_COUNT];
125
+ char shm_prefix[AP_SHM_PREFIX_MAX];
126
+ pthread_mutex_t lock; // dummy; present for c_allocator_protocol.h compat
127
+ } nt_shm_allocator;
128
+
129
+ typedef struct nt_shm_allocator_ctx {
130
+ nt_shm_allocator* shm_allocator;
131
+ HANDLE shm_handle;
132
+ HANDLE lock_handle;
133
+ nt_shm_page_ctx* active_page;
134
+ } nt_shm_allocator_ctx;
135
+
136
+ // ========== Internal Helpers ==========
137
+
138
+ static const size_t c_nt_shm_page_overhead = (sizeof(nt_shm_page) + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
139
+ static const size_t c_nt_shm_block_overhead = (sizeof(nt_shm_memory_block) + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
140
+
141
+ /**
142
+ * @brief Convert a UTF-8 name to a wide-char name with "Global\\" prefix.
143
+ * @param utf8_name UTF-8 input name (e.g. "/c_cbase_shm_ac_1234_7f...").
144
+ * @param wide_out Output buffer for wide-char name (must be at least AP_SHM_NAME_LEN WCHARs).
145
+ * @return Number of WCHARs written (excluding null terminator), or 0 on error.
146
+ */
147
+ static inline int c_nt_shm_to_wide(const char* utf8_name, wchar_t* wide_out) {
148
+ if (!utf8_name || !wide_out) return 0;
149
+
150
+ // Skip leading '/' in POSIX-style names
151
+ const char* src = utf8_name;
152
+ if (src[0] == '/') src++;
153
+
154
+ // Prepend "Global\\"
155
+ wchar_t* dst = wide_out;
156
+ wcscpy(dst, L"Global\\");
157
+ dst += 7;
158
+
159
+ // Convert ASCII to wide chars
160
+ int count = 7;
161
+ while (*src && count < AP_SHM_NAME_LEN - 1) {
162
+ *dst++ = (wchar_t) (unsigned char) *src++;
163
+ count++;
164
+ }
165
+ *dst = L'\0';
166
+ return count;
167
+ }
168
+
169
+ /**
170
+ * @brief Lock the allocator via the cached named mutex handle.
171
+ */
172
+ static inline int c_nt_shm_lock(nt_shm_allocator_ctx* ctx) {
173
+ if (!ctx || !ctx->lock_handle) return -1;
174
+ DWORD result = WaitForSingleObject(ctx->lock_handle, INFINITE);
175
+ return (result == WAIT_OBJECT_0) ? 0 : -1;
176
+ }
177
+
178
+ /**
179
+ * @brief Unlock the allocator via the cached named mutex handle.
180
+ */
181
+ static inline void c_nt_shm_unlock(nt_shm_allocator_ctx* ctx) {
182
+ if (ctx && ctx->lock_handle) {
183
+ ReleaseMutex(ctx->lock_handle);
184
+ }
185
+ }
186
+
187
+ // ========== Utility Functions ==========
188
+
189
+ static inline size_t c_nt_shm_page_roundup(nt_shm_allocator* allocator, size_t size) {
190
+ return (size + allocator->autopage_alignment - 1) & ~(allocator->autopage_alignment - 1);
191
+ }
192
+
193
+ static inline size_t c_nt_shm_block_roundup(size_t size) {
194
+ return (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
195
+ }
196
+
197
+ /* ceil(log2(v)) for 0 < v <= UINT32_MAX.
198
+ * 32-bit is sufficient: the bin structure caps block capacities well below
199
+ * 4 GiB, so a narrower parameter type truthfully advertises the domain. */
200
+ static inline size_t c_nt_shm_block_ceil_log2(uint32_t v) {
201
+ size_t floor;
202
+ #if defined(_MSC_VER)
203
+ unsigned long idx = 0;
204
+ _BitScanReverse(&idx, (unsigned long) v);
205
+ floor = (size_t) idx;
206
+ #else
207
+ floor = (size_t) (31u - __builtin_clz((unsigned int) v));
208
+ #endif
209
+ return floor + (v != ((size_t) 1u << floor));
210
+ }
211
+
212
+ /* Size bin for a block capacity (always a multiple of 8).
213
+ * Exact bins: index = capacity / 8 (1..AP_SHM_EXACT_BIN_COUNT), so any
214
+ * block in bin(cap_net) has capacity == cap_net and always fits.
215
+ * Larger capacities: pow2-class bins keyed by ceil(log2(capacity))
216
+ * (indices AP_SHM_EXACT_BIN_COUNT+1..); the last bin is the overflow
217
+ * class. In-bin first-fit is used there (blocks can be up to one class
218
+ * smaller than the request). */
219
+ static inline size_t c_nt_shm_block_bin(size_t capacity) {
220
+ if (capacity <= (size_t) AP_SHM_EXACT_BIN_COUNT * 8u) {
221
+ return capacity >> 3;
222
+ }
223
+ size_t idx = (size_t) AP_SHM_EXACT_BIN_COUNT + (c_nt_shm_block_ceil_log2(capacity) - 16u);
224
+ if (idx > (size_t) AP_SHM_EXACT_BIN_COUNT + AP_SHM_LARGE_BIN_COUNT) {
225
+ idx = (size_t) AP_SHM_EXACT_BIN_COUNT + AP_SHM_LARGE_BIN_COUNT;
226
+ }
227
+ return idx;
228
+ }
229
+
230
+ static inline void c_nt_shm_allocator_name(const char* shm_prefix, char* out) {
231
+ DWORD pid = GetCurrentProcessId();
232
+ snprintf(out, AP_SHM_NAME_LEN, "%s_ac_%lx", shm_prefix, (unsigned long) pid);
233
+ }
234
+
235
+ static inline void c_nt_shm_page_name(nt_shm_allocator* allocator, char* out) {
236
+ DWORD pid = GetCurrentProcessId();
237
+ size_t page_idx = allocator->mapped_pages;
238
+ snprintf(out, AP_SHM_NAME_LEN, "%s_pg_%lx_%lx_%zx", allocator->shm_prefix, (unsigned long) pid, (unsigned long) (uintptr_t) allocator, page_idx);
239
+ }
240
+
241
+ static inline void c_nt_shm_mutex_name(const char* shm_prefix, char* out) {
242
+ DWORD pid = GetCurrentProcessId();
243
+ snprintf(out, AP_SHM_NAME_LEN, "%s_mtx_%lx", shm_prefix, (unsigned long) pid);
244
+ }
245
+
246
+ // ========== Scan / Dangling ==========
247
+
248
+ /**
249
+ * @brief Try to open a file mapping with the given name pattern.
250
+ *
251
+ * Iterates through candidate PIDs to find a matching file mapping.
252
+ * Uses the naming convention: {prefix}_{suffix}_{pid}
253
+ *
254
+ * @param prefix UTF-8 prefix (e.g. "/c_cbase_shm_ac").
255
+ * @param out Output buffer to receive the found UTF-8 name.
256
+ * @param out_len Size of output buffer.
257
+ * @return 0 on success, -1 with errno set otherwise.
258
+ */
259
+ static inline int c_nt_shm_scan(const char* prefix, const char* suffix, char* out, size_t out_len) {
260
+ if (!prefix || !out || !out_len) {
261
+ errno = EINVAL;
262
+ return -1;
263
+ }
264
+
265
+ // Skip leading '/'
266
+ const char* pfx = prefix;
267
+ if (pfx[0] == '/') pfx++;
268
+
269
+ // The full prefix is {prefix}_{suffix}
270
+ char full_prefix[AP_SHM_NAME_LEN];
271
+ if (suffix) {
272
+ snprintf(full_prefix, sizeof(full_prefix), "%s_%s", pfx, suffix);
273
+ }
274
+ else {
275
+ strncpy(full_prefix, pfx, sizeof(full_prefix) - 1);
276
+ full_prefix[sizeof(full_prefix) - 1] = '\0';
277
+ }
278
+
279
+ // Build wide prefix: "Global\\{full_prefix}_"
280
+ wchar_t wide_prefix[AP_SHM_NAME_LEN];
281
+ int prefix_len = swprintf(wide_prefix, AP_SHM_NAME_LEN, L"Global\\%hs_", full_prefix);
282
+ if (prefix_len < 0) {
283
+ errno = EINVAL;
284
+ return -1;
285
+ }
286
+
287
+ // Try current process and a range of nearby PIDs
288
+ DWORD current_pid = GetCurrentProcessId();
289
+ for (DWORD offset = 0; offset < 65536; offset++) {
290
+ DWORD try_pid = (current_pid + offset) % 65536;
291
+ if (try_pid == 0) continue;
292
+
293
+ wchar_t wide_name[AP_SHM_NAME_LEN];
294
+ swprintf(wide_name, AP_SHM_NAME_LEN, L"Global\\%hs_%lx", full_prefix, (unsigned long) try_pid);
295
+
296
+ HANDLE h = OpenFileMappingW(FILE_MAP_READ, FALSE, wide_name);
297
+ if (h != NULL) {
298
+ CloseHandle(h);
299
+ // Convert back to UTF-8
300
+ int ret = WideCharToMultiByte(CP_UTF8, 0, wide_name, -1, out, (int) out_len, NULL, NULL);
301
+ if (ret > 0) {
302
+ return 0;
303
+ }
304
+ }
305
+ }
306
+
307
+ errno = ENOENT;
308
+ return -1;
309
+ }
310
+
311
+ /**
312
+ * @brief Scan for an allocator SHM name (appends "_ac" to prefix).
313
+ */
314
+ static inline int c_nt_shm_scan_allocator(const char* shm_prefix, char* out) {
315
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
316
+ return c_nt_shm_scan(shm_prefix, "ac", out, AP_SHM_NAME_LEN);
317
+ }
318
+
319
+ /**
320
+ * @brief Scan for a page SHM name (appends "_pg" to prefix).
321
+ */
322
+ static inline int c_nt_shm_scan_page(const char* shm_prefix, char* out) {
323
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
324
+ return c_nt_shm_scan(shm_prefix, "pg", out, AP_SHM_NAME_LEN);
325
+ }
326
+
327
+ /**
328
+ * @brief Extract pid embedded in allocator/page SHM name.
329
+ */
330
+ static inline DWORD c_nt_shm_pid(const char* shm_name) {
331
+ if (!shm_name) {
332
+ errno = EINVAL;
333
+ return 0;
334
+ }
335
+
336
+ const char* base = shm_name;
337
+ // Skip "Global\" prefix if present
338
+ if (strncmp(base, "Global\\", 7) == 0) base += 7;
339
+ if (base[0] == '/') base++;
340
+
341
+ // Name format: {prefix}_{pid_hex}_{suffix} or {prefix}_{pid_hex}
342
+ const char* suffix_us = strrchr(base, '_');
343
+ if (!suffix_us) {
344
+ errno = EINVAL;
345
+ return 0;
346
+ }
347
+
348
+ const char* pid_us = NULL;
349
+ for (const char* p = base; p < suffix_us; p++) {
350
+ if (*p == '_') pid_us = p;
351
+ }
352
+ if (!pid_us) {
353
+ errno = EINVAL;
354
+ return 0;
355
+ }
356
+
357
+ const char* pid_start = pid_us + 1;
358
+ size_t pid_len = (size_t) (suffix_us - pid_start);
359
+ if (pid_len == 0 || pid_len >= 32) {
360
+ errno = EINVAL;
361
+ return 0;
362
+ }
363
+
364
+ char pidbuf[32];
365
+ memcpy(pidbuf, pid_start, pid_len);
366
+ pidbuf[pid_len] = '\0';
367
+
368
+ errno = 0;
369
+ char* endptr = NULL;
370
+ unsigned long v = strtoul(pidbuf, &endptr, 16);
371
+ if (errno != 0 || endptr == pidbuf || *endptr != '\0') {
372
+ errno = EINVAL;
373
+ return 0;
374
+ }
375
+
376
+ return (DWORD) v;
377
+ }
378
+
379
+ /**
380
+ * @brief Check if a process is still running.
381
+ */
382
+ static inline int c_nt_shm_process_alive(DWORD pid) {
383
+ if (pid == 0) return 0;
384
+ HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
385
+ if (!h) return 0;
386
+ DWORD exit_code;
387
+ BOOL ok = GetExitCodeProcess(h, &exit_code);
388
+ CloseHandle(h);
389
+ return ok && exit_code == STILL_ACTIVE;
390
+ }
391
+
392
+ /**
393
+ * @brief Find and map a dangling allocator (whose creator pid is gone).
394
+ */
395
+ static inline nt_shm_allocator* c_nt_shm_allocator_dangling(const char* shm_prefix, char* shm_name) {
396
+ if (!shm_name) {
397
+ errno = EINVAL;
398
+ return NULL;
399
+ }
400
+
401
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
402
+
403
+ // Build wide prefix: "Global\\{shm_prefix}_ac_"
404
+ const char* pfx = shm_prefix;
405
+ if (pfx[0] == '/') pfx++;
406
+
407
+ wchar_t wide_prefix[AP_SHM_NAME_LEN];
408
+ swprintf(wide_prefix, AP_SHM_NAME_LEN, L"Global\\%hs_ac_", pfx);
409
+
410
+ // Scan PIDs
411
+ DWORD current_pid = GetCurrentProcessId();
412
+ for (DWORD offset = 0; offset < 65536; offset++) {
413
+ DWORD try_pid = (current_pid + offset) % 65536;
414
+ if (try_pid == 0) continue;
415
+
416
+ wchar_t wide_name[AP_SHM_NAME_LEN];
417
+ swprintf(wide_name, AP_SHM_NAME_LEN, L"Global\\%hs_ac_%lx", pfx, (unsigned long) try_pid);
418
+
419
+ if (c_nt_shm_process_alive(try_pid)) continue;
420
+
421
+ HANDLE h = OpenFileMappingW(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, wide_name);
422
+ if (!h) continue;
423
+
424
+ void* map = MapViewOfFile(h, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(nt_shm_allocator));
425
+ if (!map) {
426
+ CloseHandle(h);
427
+ continue;
428
+ }
429
+
430
+ // Convert wide name back to UTF-8 for output
431
+ WideCharToMultiByte(CP_UTF8, 0, wide_name, -1, shm_name, AP_SHM_NAME_LEN, NULL, NULL);
432
+ return (nt_shm_allocator*) map;
433
+ }
434
+
435
+ errno = ENOENT;
436
+ return NULL;
437
+ }
438
+
439
+ /**
440
+ * @brief Unlink all dangling allocator/page SHM objects.
441
+ *
442
+ * On Windows, file mappings are destroyed when all handles are closed.
443
+ * This function attempts to open and close any dangling objects, but
444
+ * the actual cleanup happens when the last handle is closed.
445
+ */
446
+ static inline void c_nt_shm_clear_dangling(const char* shm_prefix) {
447
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
448
+
449
+ const char* pfx = shm_prefix;
450
+ if (pfx[0] == '/') pfx++;
451
+
452
+ const char* suffixes[] = {"ac", "pg"};
453
+ size_t suffix_count = 2;
454
+
455
+ for (DWORD offset = 0; offset < 65536; offset++) {
456
+ DWORD try_pid = (GetCurrentProcessId() + offset) % 65536;
457
+ if (try_pid == 0) continue;
458
+
459
+ if (c_nt_shm_process_alive(try_pid)) continue;
460
+
461
+ for (size_t i = 0; i < suffix_count; i++) {
462
+ wchar_t wide_name[AP_SHM_NAME_LEN];
463
+ swprintf(wide_name, AP_SHM_NAME_LEN, L"Global\\%hs_%hs_%lx", pfx, suffixes[i], (unsigned long) try_pid);
464
+
465
+ HANDLE h = OpenFileMappingW(FILE_MAP_READ, FALSE, wide_name);
466
+ if (h) CloseHandle(h); // Closing our handle helps destroy the mapping
467
+ }
468
+ }
469
+ }
470
+
471
+ // ========== Page Management ==========
472
+
473
+ static inline nt_shm_page_ctx* c_nt_shm_page_new(nt_shm_allocator* allocator, size_t page_capacity) {
474
+ nt_shm_page_ctx* ctx = (nt_shm_page_ctx*) calloc(1, sizeof(nt_shm_page_ctx));
475
+ if (!ctx) return NULL;
476
+
477
+ nt_shm_page* meta = (nt_shm_page*) calloc(1, c_nt_shm_page_overhead);
478
+ if (!meta) {
479
+ free(ctx);
480
+ return NULL;
481
+ }
482
+ ctx->shm_page = meta;
483
+
484
+ meta->capacity = page_capacity;
485
+ meta->occupied = c_nt_shm_page_overhead;
486
+ meta->offset = 0;
487
+
488
+ c_nt_shm_page_name(allocator, meta->shm_name);
489
+
490
+ // Create named file mapping for the page
491
+ wchar_t wide_name[AP_SHM_NAME_LEN];
492
+ if (c_nt_shm_to_wide(meta->shm_name, wide_name) == 0) {
493
+ free(meta);
494
+ free(ctx);
495
+ return NULL;
496
+ }
497
+
498
+ DWORD size_high = (DWORD) (page_capacity >> 32);
499
+ DWORD size_low = (DWORD) (page_capacity & 0xFFFFFFFF);
500
+
501
+ HANDLE hMapping = CreateFileMappingW(
502
+ INVALID_HANDLE_VALUE,
503
+ NULL,
504
+ PAGE_READWRITE,
505
+ size_high,
506
+ size_low,
507
+ wide_name
508
+ );
509
+
510
+ if (!hMapping) {
511
+ free(meta);
512
+ free(ctx);
513
+ return NULL;
514
+ }
515
+
516
+ ctx->shm_page = meta;
517
+ ctx->handle = hMapping;
518
+ ctx->buffer = NULL;
519
+ ctx->prev = NULL;
520
+
521
+ return ctx;
522
+ }
523
+
524
+ static inline int c_nt_shm_page_map(nt_shm_allocator* allocator, nt_shm_page_ctx* page_ctx) {
525
+ if (!allocator || !page_ctx || !page_ctx->shm_page || !page_ctx->handle) {
526
+ errno = EINVAL;
527
+ return -1;
528
+ }
529
+
530
+ size_t page_capacity = page_ctx->shm_page->capacity;
531
+ size_t offset = allocator->mapped_size;
532
+
533
+ // Map view of the file mapping
534
+ void* mapped = MapViewOfFile(page_ctx->handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, page_capacity);
535
+ if (!mapped) {
536
+ return -1;
537
+ }
538
+
539
+ // Copy metadata to start of mapped page
540
+ nt_shm_page* page_meta = (nt_shm_page*) mapped;
541
+ memcpy(page_meta, page_ctx->shm_page, c_nt_shm_page_overhead);
542
+ free(page_ctx->shm_page);
543
+ page_ctx->shm_page = page_meta;
544
+ page_ctx->buffer = (char*) mapped;
545
+
546
+ // Set correct offset and linkage
547
+ page_meta->offset = offset;
548
+ page_meta->allocator = allocator;
549
+
550
+ if (allocator->mapped_pages == 0) {
551
+ page_meta->prev_name[0] = '\0';
552
+ }
553
+ else {
554
+ memcpy(page_meta->prev_name, allocator->active_page, AP_SHM_NAME_LEN);
555
+ page_meta->prev_name[AP_SHM_NAME_LEN - 1] = '\0';
556
+ }
557
+
558
+ allocator->mapped_size += page_capacity;
559
+ memcpy(allocator->active_page, page_meta->shm_name, AP_SHM_NAME_LEN);
560
+ allocator->active_page[AP_SHM_NAME_LEN - 1] = '\0';
561
+ allocator->mapped_pages++;
562
+
563
+ return 0;
564
+ }
565
+
566
+ static inline void c_nt_shm_page_reclaim(nt_shm_allocator* allocator, nt_shm_page_ctx* page_ctx) {
567
+ if (!allocator || !page_ctx || !page_ctx->shm_page) {
568
+ errno = EINVAL;
569
+ return;
570
+ }
571
+
572
+ nt_shm_page* page = page_ctx->shm_page;
573
+
574
+ nt_shm_memory_block** prevp = &page->allocated;
575
+ while (*prevp) {
576
+ nt_shm_memory_block* block = *prevp;
577
+ if (block->size != 0) break;
578
+
579
+ *prevp = block->next_allocated;
580
+ block->next_allocated = NULL;
581
+
582
+ nt_shm_memory_block** free_prev = &allocator->bins[c_nt_shm_block_bin(block->capacity)];
583
+ while (*free_prev && *free_prev != block) {
584
+ free_prev = &(*free_prev)->next_free;
585
+ }
586
+ if (*free_prev == block) {
587
+ *free_prev = block->next_free;
588
+ }
589
+ block->next_free = NULL;
590
+
591
+ size_t cap_total = block->capacity + c_nt_shm_block_overhead;
592
+ if (page->occupied >= cap_total) {
593
+ page->occupied -= cap_total;
594
+ }
595
+ }
596
+ }
597
+
598
+ // ========== Public API ==========
599
+
600
+ /**
601
+ * @brief Extend allocator with a new page, locking via pthread_mutex if provided.
602
+ */
603
+ static inline nt_shm_page_ctx* c_nt_shm_allocator_extend(nt_shm_allocator_ctx* ctx, size_t capacity, pthread_mutex_t* lock) {
604
+ if (!ctx || !ctx->shm_allocator) {
605
+ errno = EINVAL;
606
+ return NULL;
607
+ }
608
+
609
+ nt_shm_allocator* allocator = ctx->shm_allocator;
610
+ uint8_t locked = 0;
611
+
612
+ if (lock) {
613
+ int ret = pthread_mutex_lock(lock);
614
+ if (ret != 0) {
615
+ errno = ret;
616
+ return NULL;
617
+ }
618
+ locked = 1;
619
+ }
620
+
621
+ // Determine new page capacity
622
+ if (capacity == 0) {
623
+ if (!ctx->active_page) {
624
+ capacity = allocator->autopage_capacity;
625
+ }
626
+ else {
627
+ size_t prev_cap = ctx->active_page->shm_page->capacity;
628
+ capacity = prev_cap * 2;
629
+ if (capacity < allocator->autopage_capacity) {
630
+ capacity = allocator->autopage_capacity;
631
+ }
632
+ else if (capacity > allocator->autopage_capacity_max) {
633
+ capacity = allocator->autopage_capacity_max;
634
+ }
635
+ }
636
+ }
637
+
638
+ size_t aligned_capacity = c_nt_shm_page_roundup(allocator, capacity);
639
+
640
+ // Create new page
641
+ nt_shm_page_ctx* page_ctx = c_nt_shm_page_new(allocator, aligned_capacity);
642
+ if (!page_ctx) {
643
+ if (locked) pthread_mutex_unlock(lock);
644
+ return NULL;
645
+ }
646
+
647
+ // Map new page
648
+ if (c_nt_shm_page_map(allocator, page_ctx) != 0) {
649
+ CloseHandle(page_ctx->handle);
650
+ free(page_ctx->shm_page);
651
+ free(page_ctx);
652
+ if (locked) pthread_mutex_unlock(lock);
653
+ return NULL;
654
+ }
655
+
656
+ // Update context
657
+ page_ctx->prev = ctx->active_page;
658
+ ctx->active_page = page_ctx;
659
+
660
+ if (locked) pthread_mutex_unlock(lock);
661
+ return page_ctx;
662
+ }
663
+
664
+ /**
665
+ * @brief Create a new NT SHM allocator.
666
+ * @param region_size Ignored on NT (pages are independently mapped).
667
+ * @param shm_prefix Custom SHM name prefix (NULL for compile-time default).
668
+ * @return Allocator context or NULL on failure.
669
+ */
670
+ static inline nt_shm_allocator_ctx* c_nt_shm_allocator_new(size_t region_size, const char* shm_prefix) {
671
+ (void) region_size; // Not used on NT -- pages are independently mapped
672
+
673
+ nt_shm_allocator_ctx* ctx = (nt_shm_allocator_ctx*) calloc(1, sizeof(nt_shm_allocator_ctx));
674
+ if (!ctx) return NULL;
675
+
676
+ if (!shm_prefix) shm_prefix = AP_SHM_ALLOCATOR_PREFIX;
677
+
678
+ size_t prefix_len = strnlen(shm_prefix, AP_SHM_PREFIX_MAX);
679
+ if (prefix_len >= AP_SHM_PREFIX_MAX) {
680
+ fprintf(stderr, "c_nt_shm_allocator_new: shm_prefix exceeds AP_SHM_PREFIX_MAX (%zu)\n", (size_t) AP_SHM_PREFIX_MAX);
681
+ abort();
682
+ }
683
+
684
+ // Step 1: Create named file mapping for allocator metadata
685
+ char meta_shm_name[AP_SHM_NAME_LEN];
686
+ c_nt_shm_allocator_name(shm_prefix, meta_shm_name);
687
+
688
+ wchar_t wide_meta_name[AP_SHM_NAME_LEN];
689
+ if (c_nt_shm_to_wide(meta_shm_name, wide_meta_name) == 0) {
690
+ free(ctx);
691
+ return NULL;
692
+ }
693
+
694
+ HANDLE meta_hMapping = CreateFileMappingW(
695
+ INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
696
+ 0, sizeof(nt_shm_allocator), wide_meta_name
697
+ );
698
+
699
+ if (!meta_hMapping) {
700
+ free(ctx);
701
+ return NULL;
702
+ }
703
+
704
+ // Step 2: Map allocator metadata into memory
705
+ nt_shm_allocator* meta = (nt_shm_allocator*) MapViewOfFile(
706
+ meta_hMapping, FILE_MAP_READ | FILE_MAP_WRITE,
707
+ 0, 0, sizeof(nt_shm_allocator)
708
+ );
709
+
710
+ if (!meta) {
711
+ CloseHandle(meta_hMapping);
712
+ free(ctx);
713
+ return NULL;
714
+ }
715
+
716
+ // Step 3: Initialize metadata in shared memory
717
+ memset(meta, 0, sizeof(nt_shm_allocator));
718
+
719
+ size_t name_len = strnlen(meta_shm_name, AP_SHM_NAME_LEN - 1);
720
+ memcpy(meta->shm_name, meta_shm_name, name_len);
721
+ meta->shm_name[name_len] = '\0';
722
+ meta->pid = (size_t) GetCurrentProcessId();
723
+ meta->mapped_size = 0;
724
+ meta->mapped_pages = 0;
725
+ meta->active_page[0] = '\0';
726
+ meta->autopage_capacity = AP_SHM_AUTOPAGE_CAPACITY;
727
+ meta->autopage_capacity_max = AP_SHM_AUTOPAGE_CAPACITY_MAX;
728
+ meta->autopage_alignment = AP_SHM_AUTOPAGE_ALIGNMENT;
729
+
730
+ memcpy(meta->shm_prefix, shm_prefix, prefix_len);
731
+ meta->shm_prefix[prefix_len] = '\0';
732
+
733
+ // Step 4: Create named mutex for inter-process synchronization
734
+ c_nt_shm_mutex_name(shm_prefix, meta->lock_name);
735
+ meta->lock_name[AP_SHM_NAME_LEN - 1] = '\0';
736
+
737
+ wchar_t wide_mutex_name[AP_SHM_NAME_LEN];
738
+ if (c_nt_shm_to_wide(meta->lock_name, wide_mutex_name) == 0) {
739
+ UnmapViewOfFile(meta);
740
+ CloseHandle(meta_hMapping);
741
+ free(ctx);
742
+ return NULL;
743
+ }
744
+
745
+ HANDLE lock_handle = CreateMutexW(NULL, FALSE, wide_mutex_name);
746
+ if (!lock_handle) {
747
+ UnmapViewOfFile(meta);
748
+ CloseHandle(meta_hMapping);
749
+ free(ctx);
750
+ return NULL;
751
+ }
752
+
753
+ // Step 5: Initialize intra-process mutex (CRITICAL_SECTION via pthread compat)
754
+ if (pthread_mutex_init(&meta->lock, NULL) != 0) {
755
+ CloseHandle(lock_handle);
756
+ UnmapViewOfFile(meta);
757
+ CloseHandle(meta_hMapping);
758
+ free(ctx);
759
+ return NULL;
760
+ }
761
+
762
+ // Step 6: Fill context
763
+ ctx->shm_allocator = meta;
764
+ ctx->shm_handle = meta_hMapping;
765
+ ctx->lock_handle = lock_handle;
766
+ ctx->active_page = NULL;
767
+
768
+ return ctx;
769
+ }
770
+
771
+ /**
772
+ * @brief Tear down allocator, unmapping pages and closing handles.
773
+ */
774
+ static inline void c_nt_shm_allocator_free(nt_shm_allocator_ctx* ctx) {
775
+ if (!ctx) return;
776
+
777
+ // Step 1: Unmap all pages
778
+ nt_shm_page_ctx* page_ctx = ctx->active_page;
779
+ while (page_ctx) {
780
+ nt_shm_page_ctx* prev = page_ctx->prev;
781
+ nt_shm_page* page_meta = page_ctx->shm_page;
782
+
783
+ if (page_meta) {
784
+ UnmapViewOfFile((void*) page_meta);
785
+ }
786
+
787
+ if (page_ctx->handle) {
788
+ CloseHandle(page_ctx->handle);
789
+ }
790
+
791
+ free(page_ctx);
792
+ page_ctx = prev;
793
+ }
794
+
795
+ // Step 2: Unmap allocator metadata
796
+ nt_shm_allocator* allocator = ctx->shm_allocator;
797
+ if (allocator) {
798
+ pthread_mutex_destroy(&allocator->lock);
799
+ UnmapViewOfFile((void*) allocator);
800
+ }
801
+
802
+ // Step 3: Close handles
803
+ if (ctx->shm_handle) CloseHandle(ctx->shm_handle);
804
+ if (ctx->lock_handle) CloseHandle(ctx->lock_handle);
805
+
806
+ // Step 4: Free context
807
+ free(ctx);
808
+ }
809
+
810
+ static inline void* c_nt_shm_calloc(nt_shm_allocator_ctx* ctx, size_t size, pthread_mutex_t* lock) {
811
+ if (!ctx || !ctx->shm_allocator || size == 0) {
812
+ errno = EINVAL;
813
+ return NULL;
814
+ }
815
+
816
+ size_t cap_net = c_nt_shm_block_roundup(size);
817
+ size_t cap_total = cap_net + c_nt_shm_block_overhead;
818
+ nt_shm_allocator* allocator = ctx->shm_allocator;
819
+
820
+ uint8_t locked = 0;
821
+ pthread_mutex_t* builtin_lock = &allocator->lock;
822
+ pthread_mutex_t* child_lock = &allocator->lock;
823
+ if (lock) {
824
+ if (lock == builtin_lock) child_lock = NULL;
825
+ int ret = pthread_mutex_lock(lock);
826
+ if (ret != 0) {
827
+ errno = ret;
828
+ return NULL;
829
+ }
830
+ locked = 1;
831
+ }
832
+ else {
833
+ child_lock = NULL;
834
+ }
835
+
836
+ // Ensure there's an active page with space
837
+ nt_shm_page_ctx* page_ctx = ctx->active_page;
838
+ if (!page_ctx) {
839
+ size_t target_cap = allocator->autopage_capacity;
840
+ while (target_cap < cap_total + c_nt_shm_page_overhead) {
841
+ target_cap *= 2;
842
+ }
843
+
844
+ page_ctx = c_nt_shm_allocator_extend(ctx, target_cap, child_lock);
845
+ if (!page_ctx) {
846
+ if (locked) pthread_mutex_unlock(lock);
847
+ return NULL;
848
+ }
849
+ }
850
+
851
+ nt_shm_page* page_meta = page_ctx->shm_page;
852
+
853
+ if (page_meta->occupied + cap_total > page_meta->capacity) {
854
+ size_t target_cap = page_meta->capacity;
855
+ if (target_cap < allocator->autopage_capacity) {
856
+ target_cap = allocator->autopage_capacity;
857
+ }
858
+ else if (target_cap < allocator->autopage_capacity_max) {
859
+ target_cap *= 2;
860
+ }
861
+
862
+ while (target_cap < cap_total + c_nt_shm_page_overhead) {
863
+ target_cap *= 2;
864
+ }
865
+ page_ctx = c_nt_shm_allocator_extend(ctx, target_cap, child_lock);
866
+ if (!page_ctx) {
867
+ if (locked) pthread_mutex_unlock(lock);
868
+ return NULL;
869
+ }
870
+ page_meta = page_ctx->shm_page;
871
+ }
872
+
873
+ // Allocate from active page
874
+ size_t offset = page_meta->occupied;
875
+ nt_shm_memory_block* block = (nt_shm_memory_block*) (page_ctx->buffer + offset);
876
+ block->capacity = cap_net;
877
+ block->size = size;
878
+ block->next_free = NULL;
879
+ block->parent_page = page_meta;
880
+ block->next_allocated = page_meta->allocated;
881
+ page_meta->allocated = block;
882
+ page_meta->occupied += cap_total;
883
+
884
+ if (locked) pthread_mutex_unlock(lock);
885
+
886
+ memset(block + 1, 0, cap_net);
887
+ return (void*) block->buffer;
888
+ }
889
+
890
+ static inline void* c_nt_shm_request(nt_shm_allocator_ctx* ctx, size_t size, int scan_all_pages, pthread_mutex_t* lock) {
891
+ if (!ctx || !ctx->shm_allocator || size == 0) {
892
+ errno = EINVAL;
893
+ return NULL;
894
+ }
895
+
896
+ size_t cap_net = c_nt_shm_block_roundup(size);
897
+ size_t cap_total = cap_net + c_nt_shm_block_overhead;
898
+ nt_shm_allocator* allocator = ctx->shm_allocator;
899
+
900
+ uint8_t locked = 0;
901
+ pthread_mutex_t* builtin_lock = &allocator->lock;
902
+ pthread_mutex_t* child_lock = &allocator->lock;
903
+ if (lock) {
904
+ if (lock == builtin_lock) child_lock = NULL;
905
+ int ret = pthread_mutex_lock(lock);
906
+ if (ret != 0) {
907
+ errno = ret;
908
+ return NULL;
909
+ }
910
+ locked = 1;
911
+ }
912
+ else {
913
+ child_lock = NULL;
914
+ }
915
+
916
+ // Step 1: Size-binned free-list reuse (see c_nt_shm_block_bin).
917
+ // Exact bins always fit (capacity == cap_net) — O(1) head pop.
918
+ // Pow2-class bins use in-bin first-fit: bins are short and same-
919
+ // shaped, so the head is the common hit; larger-capacity blocks in
920
+ // the bin also satisfy smaller requests.
921
+ size_t bin = c_nt_shm_block_bin(cap_net);
922
+ nt_shm_memory_block* free_blk;
923
+ if (bin <= (size_t) AP_SHM_EXACT_BIN_COUNT) {
924
+ size_t found = bin;
925
+ free_blk = allocator->bins[bin];
926
+ if (!free_blk && AP_SHM_EXACT_BIN_PROBE_COUNT > 0) {
927
+ size_t probe_end = bin + (size_t) AP_SHM_EXACT_BIN_PROBE_COUNT;
928
+ if (probe_end > (size_t) AP_SHM_EXACT_BIN_COUNT) {
929
+ probe_end = (size_t) AP_SHM_EXACT_BIN_COUNT;
930
+ }
931
+ for (size_t b = bin + 1; b <= probe_end; b++) {
932
+ free_blk = allocator->bins[b];
933
+ if (free_blk) {
934
+ found = b;
935
+ break;
936
+ }
937
+ }
938
+ }
939
+ if (free_blk) {
940
+ allocator->bins[found] = free_blk->next_free;
941
+ free_blk->next_free = NULL;
942
+ free_blk->size = size;
943
+ if (locked) pthread_mutex_unlock(lock);
944
+ memset(free_blk + 1, 0, cap_net);
945
+ return (void*) free_blk->buffer;
946
+ }
947
+ }
948
+ else {
949
+ nt_shm_memory_block** prevp = &allocator->bins[bin];
950
+ free_blk = *prevp;
951
+ while (free_blk) {
952
+ if (free_blk->capacity >= cap_net) {
953
+ *prevp = free_blk->next_free;
954
+ free_blk->next_free = NULL;
955
+ free_blk->size = size;
956
+ if (locked) pthread_mutex_unlock(lock);
957
+ memset(free_blk + 1, 0, cap_net);
958
+ return (void*) free_blk->buffer;
959
+ }
960
+ prevp = &free_blk->next_free;
961
+ free_blk = free_blk->next_free;
962
+ }
963
+ }
964
+
965
+ // Step 2: Locate a page with enough space
966
+ nt_shm_page_ctx* target_ctx = NULL;
967
+ if (scan_all_pages) {
968
+ nt_shm_page_ctx* iter = ctx->active_page;
969
+ while (iter) {
970
+ nt_shm_page* meta = iter->shm_page;
971
+ if (meta && meta->occupied + cap_total <= meta->capacity) {
972
+ target_ctx = iter;
973
+ break;
974
+ }
975
+ iter = iter->prev;
976
+ }
977
+ }
978
+ else if (ctx->active_page) {
979
+ nt_shm_page* meta = ctx->active_page->shm_page;
980
+ if (meta && meta->occupied + cap_total <= meta->capacity) {
981
+ target_ctx = ctx->active_page;
982
+ }
983
+ }
984
+
985
+ // Step 3: Extend if no page fits
986
+ if (!target_ctx) {
987
+ size_t target_cap;
988
+ #if AP_SHM_PAGE_FIT_TO_REQUEST
989
+ // Fit the page to the request (floor = autopage_capacity): rare
990
+ // bin misses must not extend oversized pages.
991
+ target_cap = allocator->autopage_capacity;
992
+ #else
993
+ // Page scaling with a cap: double from the current page size up to
994
+ // AP_SHM_PAGE_EXTEND_MAX. A page larger than the cap (e.g. from
995
+ // a huge request) scales back down to the cap. Requests larger
996
+ // than the cap force a bigger page via the fit loop below.
997
+ nt_shm_page_ctx* current = ctx->active_page;
998
+ if (!current) {
999
+ target_cap = allocator->autopage_capacity;
1000
+ }
1001
+ else {
1002
+ target_cap = current->shm_page->capacity;
1003
+ if (target_cap < allocator->autopage_capacity) {
1004
+ target_cap = allocator->autopage_capacity;
1005
+ }
1006
+ else if (target_cap > (size_t) AP_SHM_PAGE_EXTEND_MAX) {
1007
+ target_cap = (size_t) AP_SHM_PAGE_EXTEND_MAX;
1008
+ }
1009
+ else if (target_cap < (size_t) AP_SHM_PAGE_EXTEND_MAX) {
1010
+ target_cap *= 2;
1011
+ if (target_cap > (size_t) AP_SHM_PAGE_EXTEND_MAX) {
1012
+ target_cap = (size_t) AP_SHM_PAGE_EXTEND_MAX;
1013
+ }
1014
+ }
1015
+ }
1016
+ #endif
1017
+ while (target_cap < cap_total + c_nt_shm_page_overhead) {
1018
+ target_cap *= 2;
1019
+ }
1020
+
1021
+ target_ctx = c_nt_shm_allocator_extend(ctx, target_cap, child_lock);
1022
+ if (!target_ctx) {
1023
+ if (locked) pthread_mutex_unlock(lock);
1024
+ return NULL;
1025
+ }
1026
+ }
1027
+
1028
+ // Step 4: Allocate from target page
1029
+ nt_shm_page* page_meta = target_ctx->shm_page;
1030
+ size_t offset = page_meta->occupied;
1031
+ nt_shm_memory_block* block = (nt_shm_memory_block*) (target_ctx->buffer + offset);
1032
+ block->capacity = cap_net;
1033
+ block->size = size;
1034
+ block->next_free = NULL;
1035
+ block->parent_page = page_meta;
1036
+ block->next_allocated = page_meta->allocated;
1037
+ page_meta->allocated = block;
1038
+ page_meta->occupied += cap_total;
1039
+
1040
+ if (locked) pthread_mutex_unlock(lock);
1041
+
1042
+ memset(block + 1, 0, cap_net);
1043
+ return (void*) block->buffer;
1044
+ }
1045
+
1046
+ static inline void c_nt_shm_free(void* ptr, pthread_mutex_t* lock) {
1047
+ if (!ptr) {
1048
+ errno = EINVAL;
1049
+ return;
1050
+ }
1051
+
1052
+ nt_shm_memory_block* block = (nt_shm_memory_block*) ((char*) ptr - c_nt_shm_block_overhead);
1053
+ nt_shm_page* page = block->parent_page;
1054
+ if (!page || !page->allocator) {
1055
+ errno = EINVAL;
1056
+ return;
1057
+ }
1058
+
1059
+ nt_shm_allocator* allocator = page->allocator;
1060
+
1061
+ uint8_t locked = 0;
1062
+ if (lock) {
1063
+ int ret = pthread_mutex_lock(lock);
1064
+ if (ret != 0) {
1065
+ errno = ret;
1066
+ return;
1067
+ }
1068
+ locked = 1;
1069
+ }
1070
+
1071
+ block->size = 0;
1072
+ size_t bin = c_nt_shm_block_bin(block->capacity);
1073
+ block->next_free = allocator->bins[bin];
1074
+ allocator->bins[bin] = block;
1075
+
1076
+ if (locked) pthread_mutex_unlock(lock);
1077
+ }
1078
+
1079
+ static inline void c_nt_shm_reclaim(nt_shm_allocator_ctx* ctx, pthread_mutex_t* lock) {
1080
+ if (!ctx || !ctx->shm_allocator) {
1081
+ errno = EINVAL;
1082
+ return;
1083
+ }
1084
+
1085
+ uint8_t locked = 0;
1086
+ if (lock) {
1087
+ int ret = pthread_mutex_lock(lock);
1088
+ if (ret != 0) {
1089
+ errno = ret;
1090
+ return;
1091
+ }
1092
+ locked = 1;
1093
+ }
1094
+
1095
+ nt_shm_allocator* allocator = ctx->shm_allocator;
1096
+ nt_shm_page_ctx* page_ctx = ctx->active_page;
1097
+ while (page_ctx) {
1098
+ c_nt_shm_page_reclaim(allocator, page_ctx);
1099
+ page_ctx = page_ctx->prev;
1100
+ }
1101
+
1102
+ if (locked) pthread_mutex_unlock(lock);
1103
+ }
1104
+
1105
+ #endif // AP_SHM_C_NT_SHM_ALLOCATOR_H