Skip to content

Commit cf75085

Browse files
authored
Merge pull request #48 from benoitc/feature/per-interpreter-import-registry
Per-interpreter import and path registry
2 parents 4ac10a4 + 11eeaac commit cf75085

23 files changed

Lines changed: 1624 additions & 478 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

README.md

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ erlang_python embeds Python into the BEAM VM, letting you call Python functions,
1515
evaluate expressions, and stream from generators - all without blocking Erlang
1616
schedulers.
1717

18-
**Three paths to parallelism:**
19-
- **Sub-interpreters** (Python 3.12+) - Each interpreter has its own GIL
20-
- **Free-threaded Python** (3.13+) - No GIL at all
18+
**Parallelism options:**
19+
- **Worker mode** (default, recommended) - Works with any Python version. With free-threaded Python (3.13t+), provides true parallelism automatically
20+
- **SHARED_GIL sub-interpreters** (Python 3.12+) - Isolated namespaces, shared GIL (isolation improves in 3.14+)
21+
- **OWN_GIL sub-interpreters** (Python 3.14+) - Each interpreter has its own GIL, true parallelism
2122
- **BEAM processes** - Fan out work across lightweight Erlang processes
2223

2324
Key features:
@@ -313,10 +314,11 @@ Ref = py:async_call(aiohttp, get, [<<"https://api.example.com/data">>]),
313314

314315
## Parallel Execution with Sub-interpreters
315316

316-
True parallelism without GIL contention using Python 3.12+ sub-interpreters:
317+
True parallelism without GIL contention using Python 3.14+ OWN_GIL sub-interpreters:
317318

318319
```erlang
319-
%% Execute multiple calls in parallel across sub-interpreters
320+
%% Execute multiple calls in parallel across OWN_GIL sub-interpreters
321+
%% Requires Python 3.14+
320322
{ok, Results} = py:parallel([
321323
{math, factorial, [100]},
322324
{math, factorial, [200]},
@@ -326,6 +328,8 @@ True parallelism without GIL contention using Python 3.12+ sub-interpreters:
326328
%% Each call runs in its own interpreter with its own GIL
327329
```
328330

331+
For Python 3.12/3.13, use SHARED_GIL sub-interpreters (`mode => subinterp`) for namespace isolation, but note that parallelism is limited by the shared GIL.
332+
329333
## Parallel Processing with BEAM Processes
330334

331335
Leverage Erlang's lightweight processes for massive parallelism:
@@ -595,19 +599,47 @@ ok = py:clear_traces().
595599

596600
## Execution Modes
597601

598-
The library auto-detects the best execution mode:
602+
### Context Modes
599603

600-
| Mode | Python Version | Parallelism |
604+
When creating Python contexts, you can choose the execution mode:
605+
606+
| Mode | Python Version | Description |
601607
|------|----------------|-------------|
602-
| Free-threaded | 3.13+ (nogil) | True parallel, no GIL |
603-
| Sub-interpreter | 3.12+ | Per-interpreter GIL |
604-
| Multi-executor | Any | GIL contention |
608+
| `worker` | Any | Main interpreter, shared namespace (default, recommended) |
609+
| `subinterp` | 3.12+ | SHARED_GIL sub-interpreter, isolated namespace |
610+
| `owngil` | 3.14+ | OWN_GIL sub-interpreter, true parallelism |
611+
612+
```erlang
613+
%% Default: worker mode (recommended)
614+
%% With free-threaded Python (3.13t+), provides true parallelism automatically
615+
{ok, Ctx} = py_context:new(#{}).
616+
617+
%% Explicit subinterpreter with shared GIL (Python 3.12+)
618+
%% Provides namespace isolation but no parallelism
619+
{ok, Ctx} = py_context:new(#{mode => subinterp}).
620+
621+
%% OWN_GIL mode for true parallelism (Python 3.14+ required)
622+
%% Each context runs in its own pthread with independent GIL
623+
{ok, Ctx} = py_context:new(#{mode => owngil}).
624+
```
625+
626+
**Worker mode is recommended** because it works with any Python version and automatically benefits from free-threaded Python (3.13t+) when available.
627+
628+
**Why OWN_GIL requires Python 3.14+**: Some C extensions (e.g., `_decimal`, `numpy`) have global state bugs in sub-interpreters on Python 3.12/3.13. These are fixed in Python 3.14. SHARED_GIL mode works on 3.12+ but with caveats for C extensions with global state.
605629

606-
Check current mode:
630+
### Runtime Detection
631+
632+
Check the current execution mode:
607633
```erlang
608634
py:execution_mode(). %% => free_threaded | subinterp | multi_executor
609635
```
610636

637+
| Mode | Python Version | Parallelism |
638+
|------|----------------|-------------|
639+
| Free-threaded | 3.13+ (nogil) | True parallel, no GIL |
640+
| Sub-interpreter | 3.12+ | Per-interpreter GIL |
641+
| Multi-executor | Any | GIL contention |
642+
611643
## Error Handling
612644

613645
```erlang

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()

0 commit comments

Comments
 (0)