Skip to content

Commit 9fa6132

Browse files
committed
Extract common utilities to py_util.h/c
- Add close_pipe_pair() for safe pipe closure - Add RUNTIME_CHECK_ERROR, GET_RESOURCE_OR_BADARG, CHECK_NOT_DESTROYED, INSPECT_BINARY_OR_BADARG macros for NIF error handling - Add make_error_atom() helper for error tuples - Update py_nif.c destructors to use close_pipe_pair() - Refactor py_shared_dict.c NIF functions to use utility macros - Reduces ~55 lines of duplicated boilerplate code
1 parent 4b527ff commit 9fa6132

4 files changed

Lines changed: 179 additions & 82 deletions

File tree

c_src/py_nif.c

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
*/
3838

3939
#include "py_nif.h"
40+
#include "py_util.h"
4041
#include "py_event_loop.h"
4142
#include "py_channel.h"
4243
#include "py_buffer.h"
@@ -285,6 +286,7 @@ static int is_inline_schedule_marker(PyObject *obj);
285286
* Include module implementations
286287
* ============================================================================ */
287288

289+
#include "py_util.c"
288290
#include "py_convert.c"
289291
#include "py_exec.c"
290292
#include "py_logging.c"
@@ -309,12 +311,7 @@ static void worker_destructor(ErlNifEnv *env, void *obj) {
309311
py_worker_t *worker = (py_worker_t *)obj;
310312

311313
/* Close callback pipes */
312-
if (worker->callback_pipe[0] >= 0) {
313-
close(worker->callback_pipe[0]);
314-
}
315-
if (worker->callback_pipe[1] >= 0) {
316-
close(worker->callback_pipe[1]);
317-
}
314+
close_pipe_pair(worker->callback_pipe);
318315

319316
/* Only clean up Python state if Python is still initialized */
320317
if (worker->thread_state != NULL && runtime_is_running()) {
@@ -405,14 +402,7 @@ static void context_destructor(ErlNifEnv *env, void *obj) {
405402
py_context_t *ctx = (py_context_t *)obj;
406403

407404
/* Close callback pipes if open */
408-
if (ctx->callback_pipe[0] >= 0) {
409-
close(ctx->callback_pipe[0]);
410-
ctx->callback_pipe[0] = -1;
411-
}
412-
if (ctx->callback_pipe[1] >= 0) {
413-
close(ctx->callback_pipe[1]);
414-
ctx->callback_pipe[1] = -1;
415-
}
405+
close_pipe_pair(ctx->callback_pipe);
416406

417407
/* Skip if already destroyed by nif_context_destroy */
418408
if (ctx->destroyed) {

c_src/py_shared_dict.c

Lines changed: 23 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,13 @@ static ERL_NIF_TERM nif_shared_dict_new(ErlNifEnv *env, int argc,
100100
(void)argc;
101101
(void)argv;
102102

103-
if (!runtime_is_running()) {
104-
return enif_make_tuple2(env, ATOM_ERROR,
105-
enif_make_atom(env, "not_initialized"));
106-
}
103+
RUNTIME_CHECK_ERROR(env);
107104

108105
/* Allocate resource */
109106
py_shared_dict_t *sd = enif_alloc_resource(
110107
PY_SHARED_DICT_RESOURCE_TYPE, sizeof(py_shared_dict_t));
111108
if (sd == NULL) {
112-
return enif_make_tuple2(env, ATOM_ERROR,
113-
enif_make_atom(env, "alloc_failed"));
109+
return make_error_atom(env, "alloc_failed");
114110
}
115111

116112
/* Initialize fields */
@@ -125,8 +121,7 @@ static ERL_NIF_TERM nif_shared_dict_new(ErlNifEnv *env, int argc,
125121
PyGILState_Release(gstate);
126122
pthread_mutex_destroy(&sd->mutex);
127123
enif_release_resource(sd);
128-
return enif_make_tuple2(env, ATOM_ERROR,
129-
enif_make_atom(env, "dict_alloc_failed"));
124+
return make_error_atom(env, "dict_alloc_failed");
130125
}
131126
PyGILState_Release(gstate);
132127

@@ -154,20 +149,12 @@ static ERL_NIF_TERM nif_shared_dict_get(ErlNifEnv *env, int argc,
154149
(void)argc;
155150

156151
py_shared_dict_t *sd;
157-
if (!enif_get_resource(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, (void **)&sd)) {
158-
return enif_make_badarg(env);
159-
}
160-
161-
/* Check if destroyed */
162-
if (atomic_load(&sd->destroyed)) {
163-
return enif_make_badarg(env);
164-
}
152+
GET_RESOURCE_OR_BADARG(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, &sd);
153+
CHECK_NOT_DESTROYED(env, sd);
165154

166155
/* Get key as binary */
167156
ErlNifBinary key_bin;
168-
if (!enif_inspect_binary(env, argv[1], &key_bin)) {
169-
return enif_make_badarg(env);
170-
}
157+
INSPECT_BINARY_OR_BADARG(env, argv[1], key_bin);
171158

172159
/* Lock and access dict */
173160
pthread_mutex_lock(&sd->mutex);
@@ -205,8 +192,7 @@ static ERL_NIF_TERM nif_shared_dict_get(ErlNifEnv *env, int argc,
205192
PyErr_Clear();
206193
PyGILState_Release(gstate);
207194
pthread_mutex_unlock(&sd->mutex);
208-
return enif_make_tuple2(env, ATOM_ERROR,
209-
enif_make_atom(env, "pickle_unavailable"));
195+
return make_error_atom(env, "pickle_unavailable");
210196
}
211197

212198
PyObject *loads = PyObject_GetAttrString(pickle_mod, "loads");
@@ -215,8 +201,7 @@ static ERL_NIF_TERM nif_shared_dict_get(ErlNifEnv *env, int argc,
215201
PyErr_Clear();
216202
PyGILState_Release(gstate);
217203
pthread_mutex_unlock(&sd->mutex);
218-
return enif_make_tuple2(env, ATOM_ERROR,
219-
enif_make_atom(env, "pickle_loads_unavailable"));
204+
return make_error_atom(env, "pickle_loads_unavailable");
220205
}
221206

222207
PyObject *value = PyObject_CallFunctionObjArgs(loads, pickled, NULL);
@@ -226,8 +211,7 @@ static ERL_NIF_TERM nif_shared_dict_get(ErlNifEnv *env, int argc,
226211
PyErr_Clear();
227212
PyGILState_Release(gstate);
228213
pthread_mutex_unlock(&sd->mutex);
229-
return enif_make_tuple2(env, ATOM_ERROR,
230-
enif_make_atom(env, "unpickle_failed"));
214+
return make_error_atom(env, "unpickle_failed");
231215
}
232216

233217
/* Convert Python value to Erlang term */
@@ -253,20 +237,12 @@ static ERL_NIF_TERM nif_shared_dict_set(ErlNifEnv *env, int argc,
253237
(void)argc;
254238

255239
py_shared_dict_t *sd;
256-
if (!enif_get_resource(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, (void **)&sd)) {
257-
return enif_make_badarg(env);
258-
}
259-
260-
/* Check if destroyed */
261-
if (atomic_load(&sd->destroyed)) {
262-
return enif_make_badarg(env);
263-
}
240+
GET_RESOURCE_OR_BADARG(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, &sd);
241+
CHECK_NOT_DESTROYED(env, sd);
264242

265243
/* Get key as binary */
266244
ErlNifBinary key_bin;
267-
if (!enif_inspect_binary(env, argv[1], &key_bin)) {
268-
return enif_make_badarg(env);
269-
}
245+
INSPECT_BINARY_OR_BADARG(env, argv[1], key_bin);
270246

271247
/* Lock and access dict */
272248
pthread_mutex_lock(&sd->mutex);
@@ -285,8 +261,7 @@ static ERL_NIF_TERM nif_shared_dict_set(ErlNifEnv *env, int argc,
285261
PyErr_Clear();
286262
PyGILState_Release(gstate);
287263
pthread_mutex_unlock(&sd->mutex);
288-
return enif_make_tuple2(env, ATOM_ERROR,
289-
enif_make_atom(env, "term_conversion_failed"));
264+
return make_error_atom(env, "term_conversion_failed");
290265
}
291266

292267
/* Pickle the value for cross-interpreter safety */
@@ -296,8 +271,7 @@ static ERL_NIF_TERM nif_shared_dict_set(ErlNifEnv *env, int argc,
296271
Py_DECREF(py_value);
297272
PyGILState_Release(gstate);
298273
pthread_mutex_unlock(&sd->mutex);
299-
return enif_make_tuple2(env, ATOM_ERROR,
300-
enif_make_atom(env, "pickle_unavailable"));
274+
return make_error_atom(env, "pickle_unavailable");
301275
}
302276

303277
PyObject *dumps = PyObject_GetAttrString(pickle_mod, "dumps");
@@ -307,8 +281,7 @@ static ERL_NIF_TERM nif_shared_dict_set(ErlNifEnv *env, int argc,
307281
Py_DECREF(py_value);
308282
PyGILState_Release(gstate);
309283
pthread_mutex_unlock(&sd->mutex);
310-
return enif_make_tuple2(env, ATOM_ERROR,
311-
enif_make_atom(env, "pickle_dumps_unavailable"));
284+
return make_error_atom(env, "pickle_dumps_unavailable");
312285
}
313286

314287
PyObject *pickled = PyObject_CallFunctionObjArgs(dumps, py_value, NULL);
@@ -319,8 +292,7 @@ static ERL_NIF_TERM nif_shared_dict_set(ErlNifEnv *env, int argc,
319292
PyErr_Clear();
320293
PyGILState_Release(gstate);
321294
pthread_mutex_unlock(&sd->mutex);
322-
return enif_make_tuple2(env, ATOM_ERROR,
323-
enif_make_atom(env, "pickle_failed"));
295+
return make_error_atom(env, "pickle_failed");
324296
}
325297

326298
/* Create Python key from binary */
@@ -341,8 +313,7 @@ static ERL_NIF_TERM nif_shared_dict_set(ErlNifEnv *env, int argc,
341313
PyErr_Clear();
342314
PyGILState_Release(gstate);
343315
pthread_mutex_unlock(&sd->mutex);
344-
return enif_make_tuple2(env, ATOM_ERROR,
345-
enif_make_atom(env, "dict_set_failed"));
316+
return make_error_atom(env, "dict_set_failed");
346317
}
347318

348319
PyGILState_Release(gstate);
@@ -363,20 +334,12 @@ static ERL_NIF_TERM nif_shared_dict_del(ErlNifEnv *env, int argc,
363334
(void)argc;
364335

365336
py_shared_dict_t *sd;
366-
if (!enif_get_resource(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, (void **)&sd)) {
367-
return enif_make_badarg(env);
368-
}
369-
370-
/* Check if destroyed */
371-
if (atomic_load(&sd->destroyed)) {
372-
return enif_make_badarg(env);
373-
}
337+
GET_RESOURCE_OR_BADARG(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, &sd);
338+
CHECK_NOT_DESTROYED(env, sd);
374339

375340
/* Get key as binary */
376341
ErlNifBinary key_bin;
377-
if (!enif_inspect_binary(env, argv[1], &key_bin)) {
378-
return enif_make_badarg(env);
379-
}
342+
INSPECT_BINARY_OR_BADARG(env, argv[1], key_bin);
380343

381344
/* Lock and access dict */
382345
pthread_mutex_lock(&sd->mutex);
@@ -419,14 +382,8 @@ static ERL_NIF_TERM nif_shared_dict_keys(ErlNifEnv *env, int argc,
419382
(void)argc;
420383

421384
py_shared_dict_t *sd;
422-
if (!enif_get_resource(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, (void **)&sd)) {
423-
return enif_make_badarg(env);
424-
}
425-
426-
/* Check if destroyed */
427-
if (atomic_load(&sd->destroyed)) {
428-
return enif_make_badarg(env);
429-
}
385+
GET_RESOURCE_OR_BADARG(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, &sd);
386+
CHECK_NOT_DESTROYED(env, sd);
430387

431388
/* Lock and access dict */
432389
pthread_mutex_lock(&sd->mutex);
@@ -499,9 +456,7 @@ static ERL_NIF_TERM nif_shared_dict_destroy(ErlNifEnv *env, int argc,
499456
(void)argc;
500457

501458
py_shared_dict_t *sd;
502-
if (!enif_get_resource(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, (void **)&sd)) {
503-
return enif_make_badarg(env);
504-
}
459+
GET_RESOURCE_OR_BADARG(env, argv[0], PY_SHARED_DICT_RESOURCE_TYPE, &sd);
505460

506461
/* Check if already destroyed - idempotent */
507462
if (atomic_load(&sd->destroyed)) {

c_src/py_util.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2026 Benoit Chesneau
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* py_util.c - Common utility function implementations
19+
*
20+
* This file is included directly by py_nif.c after py_util.h.
21+
* Currently all utilities are header-only (inline functions and macros).
22+
* This file exists for future non-inline utility functions.
23+
*/
24+
25+
/* All utilities are currently defined in py_util.h as inline functions
26+
* or macros. This file serves as a placeholder for future implementations
27+
* that need separate compilation. */

0 commit comments

Comments
 (0)