Skip to content

Commit 6f8350f

Browse files
committed
Remove SHARED_GIL subinterp pool (dead code after API simplification)
Since the API now only exposes worker mode, the SHARED_GIL subinterpreter pool (py_subinterp_pool.c/h) is no longer reachable from user code. Removed: - c_src/py_subinterp_pool.c and .h - All subinterp_pool references from py_nif.c and py_nif.h - test/py_web_frameworks_SUITE.erl (tested removed py_asgi/py_wsgi) The parallel pool (py_parallel_pool.c) remains for ENABLE_PARALLEL_PYTHON builds which use OWN_GIL subinterpreters for true parallel execution.
1 parent ff06a39 commit 6f8350f

5 files changed

Lines changed: 6 additions & 875 deletions

File tree

c_src/py_nif.c

Lines changed: 4 additions & 227 deletions
Original file line numberDiff line numberDiff line change
@@ -108,25 +108,8 @@ static void py_env_resource_dtor(ErlNifEnv *env, void *obj) {
108108
PyGILState_STATE gstate = PyGILState_Ensure();
109109

110110
#ifdef HAVE_SUBINTERPRETERS
111-
if (res->pool_slot >= 0) {
112-
/* Created in a shared-GIL subinterpreter - must DECREF in correct interpreter */
113-
subinterp_slot_t *slot = subinterp_pool_get(res->pool_slot);
114-
115-
/* Verify slot is still valid and has same interpreter */
116-
if (slot != NULL && slot->initialized && slot->interp != NULL) {
117-
int64_t slot_interp_id = PyInterpreterState_GetID(slot->interp);
118-
if (slot_interp_id == res->interp_id) {
119-
/* Same interpreter, safe to DECREF */
120-
PyThreadState *saved = PyThreadState_Swap(slot->tstate);
121-
Py_XDECREF(res->globals);
122-
Py_XDECREF(res->locals);
123-
PyThreadState_Swap(saved);
124-
}
125-
/* If interp_id mismatch, slot was reused - skip DECREF */
126-
}
127-
/* If slot invalid/not initialized, interpreter destroyed - skip DECREF */
128-
} else if (res->interp_id != 0) {
129-
/* OWN_GIL subinterpreter: pool_slot == -1 but interp_id != 0
111+
if (res->interp_id != 0) {
112+
/* OWN_GIL subinterpreter: interp_id != 0
130113
* These dicts were created in an OWN_GIL interpreter. We cannot safely
131114
* DECREF them here because:
132115
* 1. The interpreter might already be destroyed
@@ -290,7 +273,6 @@ static int is_inline_schedule_marker(PyObject *obj);
290273
#include "py_event_loop.c"
291274
#include "py_worker_pool.h"
292275
#include "py_worker_pool.c"
293-
#include "py_subinterp_pool.c"
294276
#include "py_subinterp_thread.c"
295277
#include "py_parallel_pool.c"
296278
#include "py_reactor_buffer.c"
@@ -441,38 +423,6 @@ static void context_destructor(ErlNifEnv *env, void *obj) {
441423
}
442424
#endif
443425

444-
#ifdef HAVE_SUBINTERPRETERS
445-
#ifndef ENABLE_PARALLEL_PYTHON
446-
/* For subinterpreter mode: clean up context's own dictionaries and release pool slot */
447-
if (ctx->is_subinterp && ctx->pool_slot >= 0) {
448-
/* Clean up Python objects with GIL */
449-
if (runtime_is_running()) {
450-
subinterp_slot_t *slot = subinterp_pool_get(ctx->pool_slot);
451-
if (slot != NULL && slot->initialized) {
452-
PyGILState_STATE gstate = PyGILState_Ensure();
453-
PyThreadState *saved = PyThreadState_Swap(slot->tstate);
454-
455-
Py_XDECREF(ctx->module_cache);
456-
Py_XDECREF(ctx->globals);
457-
Py_XDECREF(ctx->locals);
458-
459-
PyThreadState_Swap(saved);
460-
PyGILState_Release(gstate);
461-
}
462-
}
463-
ctx->module_cache = NULL;
464-
ctx->globals = NULL;
465-
ctx->locals = NULL;
466-
467-
subinterp_pool_free(ctx->pool_slot);
468-
ctx->pool_slot = -1;
469-
ctx->destroyed = true;
470-
atomic_fetch_add(&g_counters.ctx_destroyed, 1);
471-
return;
472-
}
473-
#endif /* !ENABLE_PARALLEL_PYTHON */
474-
#endif /* HAVE_SUBINTERPRETERS */
475-
476426
if (!runtime_is_running()) {
477427
return;
478428
}
@@ -1224,37 +1174,6 @@ static ERL_NIF_TERM nif_py_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM arg
12241174
/* Save main thread state and release GIL for other threads */
12251175
g_main_thread_state = PyEval_SaveThread();
12261176

1227-
/* Initialize subinterpreter pool (Python 3.12+) before starting executors */
1228-
#ifdef HAVE_SUBINTERPRETERS
1229-
#ifndef ENABLE_PARALLEL_PYTHON
1230-
/* Only init shared-GIL pool if not in parallel mode */
1231-
{
1232-
int pool_size = DEFAULT_POOL_SIZE; /* Default pool size */
1233-
/* Check for config */
1234-
if (argc > 0 && enif_is_map(env, argv[0])) {
1235-
ERL_NIF_TERM key = enif_make_atom(env, "pool_size");
1236-
ERL_NIF_TERM value;
1237-
if (enif_get_map_value(env, argv[0], key, &value)) {
1238-
enif_get_int(env, value, &pool_size);
1239-
}
1240-
}
1241-
1242-
/* Restore GIL temporarily to create subinterpreters */
1243-
PyEval_RestoreThread(g_main_thread_state);
1244-
int pool_result = subinterp_pool_init(pool_size);
1245-
g_main_thread_state = PyEval_SaveThread();
1246-
1247-
if (pool_result < 0) {
1248-
PyEval_RestoreThread(g_main_thread_state);
1249-
g_main_thread_state = NULL;
1250-
Py_Finalize();
1251-
atomic_store(&g_runtime_state, PY_STATE_STOPPED);
1252-
return make_error(env, "subinterp_pool_init_failed");
1253-
}
1254-
}
1255-
#endif /* !ENABLE_PARALLEL_PYTHON */
1256-
#endif /* HAVE_SUBINTERPRETERS */
1257-
12581177
/* Initialize parallel OWN_GIL pool (Python 3.14+) */
12591178
#ifdef ENABLE_PARALLEL_PYTHON
12601179
{
@@ -1402,19 +1321,12 @@ static ERL_NIF_TERM nif_finalize(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
14021321
Py_XDECREF(g_numpy_ndarray_type);
14031322
g_numpy_ndarray_type = NULL;
14041323

1405-
#ifdef HAVE_SUBINTERPRETERS
1406-
/* Step 4: Shutdown subinterpreter pool - must be done with GIL held */
1407-
subinterp_pool_shutdown();
1408-
#endif
14091324
g_main_thread_state = PyEval_SaveThread();
14101325
} else {
14111326
/* Fallback to PyGILState if no main thread state saved */
14121327
PyGILState_STATE gstate = PyGILState_Ensure();
14131328
Py_XDECREF(g_numpy_ndarray_type);
14141329
g_numpy_ndarray_type = NULL;
1415-
#ifdef HAVE_SUBINTERPRETERS
1416-
subinterp_pool_shutdown();
1417-
#endif
14181330
PyGILState_Release(gstate);
14191331
}
14201332
#endif
@@ -4231,92 +4143,10 @@ static ERL_NIF_TERM nif_context_create(ErlNifEnv *env, int argc, const ERL_NIF_T
42314143
}
42324144
/* Fall through to worker mode for non-subinterp requests in parallel build */
42334145
#endif /* ENABLE_PARALLEL_PYTHON */
4234-
4235-
#ifndef ENABLE_PARALLEL_PYTHON
4236-
if (use_owngil) {
4237-
/* OWN_GIL mode: create dedicated pthread with OWN_GIL subinterpreter */
4238-
if (owngil_context_init(ctx) != 0) {
4239-
close(ctx->callback_pipe[0]);
4240-
close(ctx->callback_pipe[1]);
4241-
enif_release_resource(ctx);
4242-
return make_error(env, "owngil_init_failed");
4243-
}
4244-
4245-
ERL_NIF_TERM ref = enif_make_resource(env, ctx);
4246-
enif_release_resource(ctx);
4247-
atomic_fetch_add(&g_counters.ctx_created, 1);
4248-
return enif_make_tuple3(env, ATOM_OK, ref, enif_make_uint(env, ctx->interp_id));
4249-
} else if (use_subinterp) {
4250-
/* Allocate a slot from the subinterpreter pool */
4251-
int slot = subinterp_pool_alloc();
4252-
if (slot < 0) {
4253-
close(ctx->callback_pipe[0]);
4254-
close(ctx->callback_pipe[1]);
4255-
enif_release_resource(ctx);
4256-
return make_error(env, "pool_exhausted");
4257-
}
4258-
4259-
ctx->pool_slot = slot;
4260-
4261-
/* Get the pool slot for interpreter access */
4262-
subinterp_slot_t *pool_slot = subinterp_pool_get(slot);
4263-
if (pool_slot == NULL || !pool_slot->initialized) {
4264-
subinterp_pool_free(slot);
4265-
close(ctx->callback_pipe[0]);
4266-
close(ctx->callback_pipe[1]);
4267-
enif_release_resource(ctx);
4268-
return make_error(env, "pool_slot_invalid");
4269-
}
4270-
4271-
/* Create context's own namespace dictionaries.
4272-
* Each context needs its own globals/locals for isolation,
4273-
* even though they share the interpreter. */
4274-
PyGILState_STATE gstate = PyGILState_Ensure();
4275-
PyThreadState *saved = PyThreadState_Swap(pool_slot->tstate);
4276-
4277-
ctx->globals = PyDict_New();
4278-
ctx->locals = PyDict_New();
4279-
ctx->module_cache = PyDict_New();
4280-
4281-
if (ctx->globals == NULL || ctx->locals == NULL || ctx->module_cache == NULL) {
4282-
Py_XDECREF(ctx->globals);
4283-
Py_XDECREF(ctx->locals);
4284-
Py_XDECREF(ctx->module_cache);
4285-
PyThreadState_Swap(saved);
4286-
PyGILState_Release(gstate);
4287-
subinterp_pool_free(slot);
4288-
close(ctx->callback_pipe[0]);
4289-
close(ctx->callback_pipe[1]);
4290-
enif_release_resource(ctx);
4291-
return make_error(env, "dict_alloc_failed");
4292-
}
4293-
4294-
/* Import __builtins__ into globals */
4295-
PyObject *builtins = PyEval_GetBuiltins();
4296-
PyDict_SetItemString(ctx->globals, "__builtins__", builtins);
4297-
4298-
/* Import erlang module into globals */
4299-
PyObject *erlang_module = PyImport_ImportModule("erlang");
4300-
if (erlang_module != NULL) {
4301-
PyDict_SetItemString(ctx->globals, "erlang", erlang_module);
4302-
Py_DECREF(erlang_module);
4303-
} else {
4304-
PyErr_Clear();
4305-
}
4306-
4307-
PyThreadState_Swap(saved);
4308-
PyGILState_Release(gstate);
4309-
4310-
#ifdef DEBUG
4311-
fprintf(stderr, "[NIF] Created context %u using pool slot %d with own namespace\n",
4312-
ctx->interp_id, slot);
4313-
fflush(stderr);
4314-
#endif
4315-
} else
4316-
#endif /* !ENABLE_PARALLEL_PYTHON */
43174146
#else
4318-
/* Pre-3.12 Python - ignore subinterp mode request */
4147+
/* Pre-3.12 Python or non-parallel build - ignore subinterp mode request */
43194148
(void)use_subinterp;
4149+
(void)use_owngil;
43204150
#endif /* HAVE_SUBINTERPRETERS */
43214151
{
43224152
/* Worker mode - create a thread state in main interpreter */
@@ -4378,59 +4208,6 @@ static ERL_NIF_TERM nif_context_destroy(ErlNifEnv *env, int argc, const ERL_NIF_
43784208
/* Mark as destroyed early to prevent new operations */
43794209
ctx->destroyed = true;
43804210

4381-
#ifdef HAVE_SUBINTERPRETERS
4382-
#ifndef ENABLE_PARALLEL_PYTHON
4383-
/* OWN_GIL mode: shutdown the dedicated thread */
4384-
if (ctx->uses_own_gil) {
4385-
owngil_context_shutdown(ctx);
4386-
/* Close callback pipes */
4387-
if (ctx->callback_pipe[0] >= 0) {
4388-
close(ctx->callback_pipe[0]);
4389-
ctx->callback_pipe[0] = -1;
4390-
}
4391-
if (ctx->callback_pipe[1] >= 0) {
4392-
close(ctx->callback_pipe[1]);
4393-
ctx->callback_pipe[1] = -1;
4394-
}
4395-
atomic_fetch_add(&g_counters.ctx_destroyed, 1);
4396-
return ATOM_OK;
4397-
}
4398-
#endif /* !ENABLE_PARALLEL_PYTHON */
4399-
4400-
if (ctx->is_subinterp && ctx->pool_slot >= 0) {
4401-
/* Clean up context's own namespace dictionaries */
4402-
if (runtime_is_running()) {
4403-
subinterp_slot_t *slot = subinterp_pool_get(ctx->pool_slot);
4404-
if (slot != NULL && slot->initialized) {
4405-
PyGILState_STATE gstate = PyGILState_Ensure();
4406-
PyThreadState *saved = PyThreadState_Swap(slot->tstate);
4407-
4408-
Py_XDECREF(ctx->module_cache);
4409-
Py_XDECREF(ctx->globals);
4410-
Py_XDECREF(ctx->locals);
4411-
4412-
PyThreadState_Swap(saved);
4413-
PyGILState_Release(gstate);
4414-
}
4415-
}
4416-
ctx->globals = NULL;
4417-
ctx->locals = NULL;
4418-
ctx->module_cache = NULL;
4419-
4420-
/* Release the pool slot back to the pool */
4421-
subinterp_pool_free(ctx->pool_slot);
4422-
ctx->pool_slot = -1;
4423-
4424-
#ifdef DEBUG
4425-
fprintf(stderr, "[NIF] Destroyed context %u, released pool slot\n", ctx->interp_id);
4426-
fflush(stderr);
4427-
#endif
4428-
4429-
atomic_fetch_add(&g_counters.ctx_destroyed, 1);
4430-
return ATOM_OK;
4431-
}
4432-
#endif
4433-
44344211
/* Worker mode - clean up Python objects with GIL */
44354212
if (runtime_is_running()) {
44364213
PyGILState_STATE gstate = PyGILState_Ensure();

c_src/py_nif.h

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,6 @@ static inline PyObject *Py_NewRef(PyObject *o) {
148148

149149
/** @} */
150150

151-
/* Include subinterpreter pool header for shared-GIL pool model */
152-
#include "py_subinterp_pool.h"
153-
154151
/* Include subinterpreter thread pool header for OWN_GIL parallelism */
155152
#include "py_subinterp_thread.h"
156153

@@ -787,12 +784,11 @@ typedef struct {
787784
* - Mutex/condvar dispatch overhead
788785
* - Term copying between environments
789786
*
790-
* @note Python 3.12+ uses shared-GIL subinterpreters via pool slots
791-
* @note Older Python uses worker mode with main interpreter namespace
787+
* @note With ENABLE_PARALLEL_PYTHON, uses OWN_GIL subinterpreters via parallel pool
788+
* @note Otherwise uses worker mode with main interpreter namespace
792789
*
793790
* @see nif_context_create
794791
* @see nif_context_call
795-
* @see subinterp_pool_alloc
796792
*/
797793
typedef struct {
798794
/** @brief Unique interpreter ID for routing (0 = main, >0 = subinterp) */
@@ -1051,27 +1047,6 @@ static inline py_context_guard_t py_context_acquire(py_context_t *ctx) {
10511047
/* Acquire the GIL first (works for both modes) */
10521048
guard.gstate = PyGILState_Ensure();
10531049

1054-
#ifdef HAVE_SUBINTERPRETERS
1055-
#ifndef ENABLE_PARALLEL_PYTHON
1056-
if (ctx->is_subinterp && ctx->pool_slot >= 0) {
1057-
/* Subinterpreter mode: swap to the pool slot's thread state */
1058-
subinterp_slot_t *slot = subinterp_pool_get(ctx->pool_slot);
1059-
1060-
if (slot == NULL || !slot->initialized) {
1061-
/* Pool slot invalid - release GIL and fail */
1062-
PyGILState_Release(guard.gstate);
1063-
return guard;
1064-
}
1065-
1066-
/* Swap to subinterpreter's thread state */
1067-
guard.saved_tstate = PyThreadState_Swap(slot->tstate);
1068-
guard.mode = PY_GUARD_SUBINTERP;
1069-
guard.acquired = true;
1070-
return guard;
1071-
}
1072-
#endif /* !ENABLE_PARALLEL_PYTHON */
1073-
#endif /* HAVE_SUBINTERPRETERS */
1074-
10751050
/* Worker mode: just use the GIL we acquired */
10761051
guard.mode = PY_GUARD_WORKER;
10771052
guard.acquired = true;

0 commit comments

Comments
 (0)