Skip to content

Commit cc2241c

Browse files
committed
Use dynamic array for channel registry, fail on registration error
The fixed-size registry (1024 entries) silently dropped registrations when full, causing: - Round-trip failures (capsules converted to strings) - Memory leaks (enif_release_resource skipped) Now uses a dynamic array that grows as needed: - Starts at 64 entries, doubles when full - Registration failure causes channel_alloc to fail - No silent data loss
1 parent 16312ff commit cc2241c

File tree

1 file changed

+65
-42
lines changed

1 file changed

+65
-42
lines changed

c_src/py_channel.c

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,73 +42,91 @@ static ERL_NIF_TERM ATOM_EMPTY;
4242
* A thread-safe registry of valid channel pointers. Used to validate
4343
* capsule pointers without dereferencing them, preventing crashes from
4444
* forged PyCapsules.
45+
*
46+
* Uses a dynamic array that grows as needed. Registration never fails.
4547
* ============================================================================ */
4648

47-
#define CHANNEL_REGISTRY_SIZE 1024 /* Must be power of 2 */
48-
#define CHANNEL_REGISTRY_MASK (CHANNEL_REGISTRY_SIZE - 1)
49+
#define CHANNEL_REGISTRY_INITIAL_SIZE 64
50+
51+
static void **g_channel_registry = NULL;
52+
static size_t g_channel_registry_count = 0;
53+
static size_t g_channel_registry_capacity = 0;
54+
static pthread_mutex_t g_channel_registry_lock = PTHREAD_MUTEX_INITIALIZER;
55+
56+
/* Initialize the registry (called once, lazily) */
57+
static bool channel_registry_init_locked(void) {
58+
if (g_channel_registry != NULL) return true;
59+
60+
g_channel_registry = enif_alloc(CHANNEL_REGISTRY_INITIAL_SIZE * sizeof(void *));
61+
if (g_channel_registry == NULL) return false;
4962

50-
static void *g_channel_registry[CHANNEL_REGISTRY_SIZE];
51-
static pthread_rwlock_t g_channel_registry_lock = PTHREAD_RWLOCK_INITIALIZER;
63+
g_channel_registry_capacity = CHANNEL_REGISTRY_INITIAL_SIZE;
64+
g_channel_registry_count = 0;
65+
return true;
66+
}
5267

53-
/* Hash a pointer to a registry index */
54-
static inline size_t channel_registry_hash(void *ptr) {
55-
uintptr_t p = (uintptr_t)ptr;
56-
/* Mix bits for better distribution */
57-
p ^= p >> 16;
58-
p *= 0x85ebca6b;
59-
p ^= p >> 13;
60-
return p & CHANNEL_REGISTRY_MASK;
68+
/* Grow the registry (called when full) */
69+
static bool channel_registry_grow_locked(void) {
70+
size_t new_capacity = g_channel_registry_capacity * 2;
71+
void **new_registry = enif_alloc(new_capacity * sizeof(void *));
72+
if (new_registry == NULL) return false;
73+
74+
memcpy(new_registry, g_channel_registry, g_channel_registry_count * sizeof(void *));
75+
enif_free(g_channel_registry);
76+
g_channel_registry = new_registry;
77+
g_channel_registry_capacity = new_capacity;
78+
return true;
6179
}
6280

6381
/* Register a channel pointer (called from channel_alloc) */
64-
static void channel_registry_add(void *ptr) {
65-
pthread_rwlock_wrlock(&g_channel_registry_lock);
66-
size_t idx = channel_registry_hash(ptr);
67-
for (size_t i = 0; i < CHANNEL_REGISTRY_SIZE; i++) {
68-
size_t slot = (idx + i) & CHANNEL_REGISTRY_MASK;
69-
if (g_channel_registry[slot] == NULL) {
70-
g_channel_registry[slot] = ptr;
71-
break;
82+
static bool channel_registry_add(void *ptr) {
83+
pthread_mutex_lock(&g_channel_registry_lock);
84+
85+
/* Initialize on first use */
86+
if (g_channel_registry == NULL && !channel_registry_init_locked()) {
87+
pthread_mutex_unlock(&g_channel_registry_lock);
88+
return false;
89+
}
90+
91+
/* Grow if full */
92+
if (g_channel_registry_count >= g_channel_registry_capacity) {
93+
if (!channel_registry_grow_locked()) {
94+
pthread_mutex_unlock(&g_channel_registry_lock);
95+
return false;
7296
}
7397
}
74-
pthread_rwlock_unlock(&g_channel_registry_lock);
98+
99+
g_channel_registry[g_channel_registry_count++] = ptr;
100+
pthread_mutex_unlock(&g_channel_registry_lock);
101+
return true;
75102
}
76103

77104
/* Unregister a channel pointer (called from destructor) */
78105
static void channel_registry_remove(void *ptr) {
79-
pthread_rwlock_wrlock(&g_channel_registry_lock);
80-
size_t idx = channel_registry_hash(ptr);
81-
for (size_t i = 0; i < CHANNEL_REGISTRY_SIZE; i++) {
82-
size_t slot = (idx + i) & CHANNEL_REGISTRY_MASK;
83-
if (g_channel_registry[slot] == ptr) {
84-
g_channel_registry[slot] = NULL;
106+
pthread_mutex_lock(&g_channel_registry_lock);
107+
for (size_t i = 0; i < g_channel_registry_count; i++) {
108+
if (g_channel_registry[i] == ptr) {
109+
/* Move last element to this slot (order doesn't matter) */
110+
g_channel_registry[i] = g_channel_registry[--g_channel_registry_count];
85111
break;
86112
}
87-
if (g_channel_registry[slot] == NULL) {
88-
break; /* Not found */
89-
}
90113
}
91-
pthread_rwlock_unlock(&g_channel_registry_lock);
114+
pthread_mutex_unlock(&g_channel_registry_lock);
92115
}
93116

94117
/* Validate a pointer without dereferencing (public API) */
95118
bool channel_validate(void *ptr) {
96119
if (ptr == NULL) return false;
97120

98-
pthread_rwlock_rdlock(&g_channel_registry_lock);
121+
pthread_mutex_lock(&g_channel_registry_lock);
99122
bool found = false;
100-
size_t idx = channel_registry_hash(ptr);
101-
for (size_t i = 0; i < CHANNEL_REGISTRY_SIZE; i++) {
102-
size_t slot = (idx + i) & CHANNEL_REGISTRY_MASK;
103-
if (g_channel_registry[slot] == ptr) {
123+
for (size_t i = 0; i < g_channel_registry_count; i++) {
124+
if (g_channel_registry[i] == ptr) {
104125
found = true;
105126
break;
106127
}
107-
if (g_channel_registry[slot] == NULL) {
108-
break; /* Not found */
109-
}
110128
}
111-
pthread_rwlock_unlock(&g_channel_registry_lock);
129+
pthread_mutex_unlock(&g_channel_registry_lock);
112130
return found;
113131
}
114132

@@ -172,8 +190,13 @@ py_channel_t *channel_alloc(size_t max_size) {
172190
return NULL;
173191
}
174192

175-
/* Register in the pointer registry for validation */
176-
channel_registry_add(channel);
193+
/* Register in the pointer registry for validation - fail if registration fails */
194+
if (!channel_registry_add(channel)) {
195+
pthread_mutex_destroy(&channel->mutex);
196+
enif_ioq_destroy(channel->queue);
197+
enif_release_resource(channel);
198+
return NULL;
199+
}
177200

178201
return channel;
179202
}

0 commit comments

Comments
 (0)