Skip to content

Commit ba5f4fe

Browse files
committed
Require Python 3.14+ for OWN_GIL mode
OWN_GIL mode is disabled on Python < 3.14 due to C extension global state bugs (e.g., _decimal). See python/cpython#106078 Changes: - Add HAVE_OWNGIL compile flag (Python 3.14+) - Add py_nif:owngil_supported/0 NIF - Update py_context and py_event_loop_pool to check owngil_supported - Update OWN_GIL test suites to skip on Python < 3.14 - Add Python 3.14 to CI matrix and ASAN tests
1 parent a2d50bb commit ba5f4fe

10 files changed

Lines changed: 63 additions & 16 deletions

.github/workflows/ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ jobs:
2222
- os: ubuntu-24.04
2323
otp: "27.0"
2424
python: "3.13"
25+
- os: ubuntu-24.04
26+
otp: "27.0"
27+
python: "3.14"
2528
# macOS
2629
- os: macos-15
2730
otp: "27"
2831
python: "3.12"
2932
- os: macos-15
3033
otp: "27"
3134
python: "3.13"
35+
- os: macos-15
36+
otp: "27"
37+
python: "3.14"
3238

3339
steps:
3440
- name: Checkout
@@ -194,7 +200,7 @@ jobs:
194200
strategy:
195201
fail-fast: false
196202
matrix:
197-
python: ["3.12", "3.13"]
203+
python: ["3.12", "3.13", "3.14"]
198204

199205
steps:
200206
- name: Checkout

c_src/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,23 @@ int main(void) {
159159

160160
if(HAVE_SUBINTERPRETERS)
161161
message(STATUS "Subinterpreter API detected (PyInterpreterConfig_OWN_GIL available)")
162+
# OWN_GIL mode requires Python 3.14+ due to C extension global state bugs
163+
# in 3.12/3.13 (e.g., _decimal). See https://github.com/python/cpython/issues/106078
164+
if(Python3_VERSION VERSION_GREATER_EQUAL "3.14")
165+
set(HAVE_OWNGIL TRUE)
166+
message(STATUS "Python ${Python3_VERSION} >= 3.14, OWN_GIL mode enabled")
167+
else()
168+
set(HAVE_OWNGIL FALSE)
169+
message(STATUS "Python ${Python3_VERSION} < 3.14, OWN_GIL mode disabled (C extension bugs)")
170+
endif()
162171
else()
163172
message(STATUS "Subinterpreter API compile test failed, using shared GIL fallback")
173+
set(HAVE_OWNGIL FALSE)
164174
endif()
165175
else()
166176
message(STATUS "Python ${Python3_VERSION} < 3.12, subinterpreter API not available")
167177
set(HAVE_SUBINTERPRETERS FALSE)
178+
set(HAVE_OWNGIL FALSE)
168179
endif()
169180

170181
# Check for free-threaded Python (Python 3.13+ with --disable-gil / nogil build)
@@ -201,6 +212,9 @@ endif()
201212
if(HAVE_SUBINTERPRETERS)
202213
target_compile_definitions(py_nif PRIVATE HAVE_SUBINTERPRETERS=1)
203214
endif()
215+
if(HAVE_OWNGIL)
216+
target_compile_definitions(py_nif PRIVATE HAVE_OWNGIL=1)
217+
endif()
204218
if(HAVE_FREE_THREADED)
205219
target_compile_definitions(py_nif PRIVATE HAVE_FREE_THREADED=1)
206220
endif()

c_src/py_nif.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,23 @@ static ERL_NIF_TERM nif_subinterp_supported(ErlNifEnv *env, int argc, const ERL_
19701970
#endif
19711971
}
19721972

1973+
/**
1974+
* @brief Check if OWN_GIL mode is supported (Python 3.14+)
1975+
*
1976+
* OWN_GIL requires Python 3.14+ due to C extension global state bugs
1977+
* in earlier versions (e.g., _decimal). See gh-106078.
1978+
*/
1979+
static ERL_NIF_TERM nif_owngil_supported(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
1980+
(void)argc;
1981+
(void)argv;
1982+
1983+
#ifdef HAVE_OWNGIL
1984+
return ATOM_TRUE;
1985+
#else
1986+
return ATOM_FALSE;
1987+
#endif
1988+
}
1989+
19731990
#ifdef HAVE_SUBINTERPRETERS
19741991

19751992
static ERL_NIF_TERM nif_subinterp_worker_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
@@ -7130,6 +7147,7 @@ static ErlNifFunc nif_funcs[] = {
71307147

71317148
/* Sub-interpreter support (shared GIL pool model) */
71327149
{"subinterp_supported", 0, nif_subinterp_supported, 0},
7150+
{"owngil_supported", 0, nif_owngil_supported, 0},
71337151
{"subinterp_worker_new", 0, nif_subinterp_worker_new, 0},
71347152
{"subinterp_worker_destroy", 1, nif_subinterp_worker_destroy, 0},
71357153
{"subinterp_call", 5, nif_subinterp_call, ERL_NIF_DIRTY_JOB_CPU_BOUND},

src/py_context.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -531,10 +531,10 @@ create_context(subinterp) ->
531531
create_context(worker) ->
532532
py_nif:context_create(worker);
533533
create_context(owngil) ->
534-
%% OWN_GIL mode requires Python 3.12+
535-
case py_nif:subinterp_supported() of
534+
%% OWN_GIL mode requires Python 3.14+ due to C extension bugs in earlier versions
535+
case py_nif:owngil_supported() of
536536
true -> py_nif:context_create(owngil);
537-
false -> {error, owngil_requires_python312}
537+
false -> {error, owngil_requires_python314}
538538
end.
539539

540540
%% @private

src/py_event_loop_pool.erl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,9 @@ run_async(Request) ->
370370
init([NumLoops]) ->
371371
process_flag(trap_exit, true),
372372

373-
%% Check if OWN_GIL mode is enabled
374-
OwnGilEnabled = application:get_env(erlang_python, event_loop_pool_owngil, false),
373+
%% Check if OWN_GIL mode is enabled and supported (Python 3.14+)
374+
OwnGilConfigured = application:get_env(erlang_python, event_loop_pool_owngil, false),
375+
OwnGilEnabled = OwnGilConfigured andalso py_nif:owngil_supported(),
375376

376377
%% Initialize OWN_GIL infrastructure if enabled
377378
{Sessions, OwnGilReady} = case OwnGilEnabled of

src/py_nif.erl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
async_stream/6,
5555
%% Sub-interpreters (Python 3.12+) - shared GIL pool model
5656
subinterp_supported/0,
57+
owngil_supported/0,
5758
subinterp_worker_new/0,
5859
subinterp_worker_destroy/1,
5960
subinterp_call/5,
@@ -490,6 +491,13 @@ async_stream(_WorkerRef, _Module, _Func, _Args, _Kwargs, _CallerPid) ->
490491
subinterp_supported() ->
491492
?NIF_STUB.
492493

494+
%% @doc Check if OWN_GIL mode is supported (Python 3.14+).
495+
%% OWN_GIL requires Python 3.14+ due to C extension global state bugs
496+
%% in earlier versions (e.g., _decimal). See gh-106078.
497+
-spec owngil_supported() -> boolean().
498+
owngil_supported() ->
499+
?NIF_STUB.
500+
493501
%% @doc Create a new sub-interpreter worker with its own GIL.
494502
%% Returns an opaque reference to be used with subinterp functions.
495503
-spec subinterp_worker_new() -> {ok, reference()} | {error, term()}.

test/py_context_owngil_SUITE.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ groups() ->
9696
]}].
9797

9898
init_per_suite(Config) ->
99-
case py_nif:subinterp_supported() of
99+
case py_nif:owngil_supported() of
100100
true ->
101101
{ok, _} = application:ensure_all_started(erlang_python),
102102
Config;
103103
false ->
104-
{skip, "Requires Python 3.12+"}
104+
{skip, "OWN_GIL requires Python 3.14+"}
105105
end.
106106

107107
end_per_suite(_Config) ->

test/py_import_SUITE.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,10 +540,10 @@ def encode_test(data):
540540
%% OWN_GIL contexts each have their own interpreter, so imports in one
541541
%% should NOT be visible in another (different sys.modules).
542542
subinterp_isolation_test(_Config) ->
543-
%% Skip if subinterpreters not supported
544-
case py_nif:subinterp_supported() of
543+
%% Skip if OWN_GIL not supported (requires Python 3.14+)
544+
case py_nif:owngil_supported() of
545545
false ->
546-
{skip, "Subinterpreters not supported"};
546+
{skip, "OWN_GIL requires Python 3.14+"};
547547
true ->
548548
%% Clear registry so new contexts don't get pre-imported modules
549549
ok = py_import:clear_imports(),

test/py_import_owngil_SUITE.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ init_per_suite(Config) ->
6868
%% Clear any imports from previous test suites to avoid
6969
%% importing C extensions that crash in OWN_GIL subinterpreters
7070
py_import:clear_imports(),
71-
%% Then check if subinterpreters are supported
72-
case py_nif:subinterp_supported() of
71+
%% Check if OWN_GIL is supported (requires Python 3.14+)
72+
case py_nif:owngil_supported() of
7373
true ->
7474
Config;
7575
false ->
76-
{skip, "Requires Python 3.12+"}
76+
{skip, "OWN_GIL requires Python 3.14+"}
7777
end.
7878

7979
end_per_suite(_Config) ->

test/py_owngil_features_SUITE.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,15 +220,15 @@ groups() ->
220220
]}].
221221

222222
init_per_suite(Config) ->
223-
case py_nif:subinterp_supported() of
223+
case py_nif:owngil_supported() of
224224
true ->
225225
{ok, _} = application:ensure_all_started(erlang_python),
226226
%% Add test directory to Python path
227227
PrivDir = code:priv_dir(erlang_python),
228228
TestDir = filename:join(filename:dirname(PrivDir), "test"),
229229
Config ++ [{test_dir, TestDir}];
230230
false ->
231-
{skip, "Requires Python 3.12+"}
231+
{skip, "OWN_GIL requires Python 3.14+"}
232232
end.
233233

234234
end_per_suite(_Config) ->

0 commit comments

Comments
 (0)