Skip to content

Add safe GIL helpers for Python 3.12+ and 3.13t compatibility#3

Merged
benoitc merged 4 commits intomainfrom
fix-python313-gil-crash
Feb 22, 2026
Merged

Add safe GIL helpers for Python 3.12+ and 3.13t compatibility#3
benoitc merged 4 commits intomainfrom
fix-python313-gil-crash

Conversation

@benoitc
Copy link
Copy Markdown
Owner

@benoitc benoitc commented Feb 22, 2026

Summary

  • Add gil_acquire()/gil_release() helpers that check PyGILState_Check() before calling PyGILState_Ensure() to avoid double-acquisition issues on Python 3.12+
  • Add per-interpreter event loop storage using PyCapsule in module attribute for sub-interpreter support
  • Store event loop in both global C variable (fast access) and Python module attribute (sub-interpreter support)

Based on analysis of PyO3 and Python docs, PyGILState_Ensure()/PyGILState_Release() work correctly in both GIL-enabled and free-threaded builds.

@benoitc benoitc force-pushed the fix-python313-gil-crash branch from 297059d to 84f3570 Compare February 22, 2026 17:55
- Add gil_acquire()/gil_release() helpers that check PyGILState_Check()
  before calling PyGILState_Ensure() to avoid double-acquisition
- Add per-interpreter event loop storage infrastructure (not yet used)
  for future sub-interpreter support

The per-interpreter storage functions are defined but not called yet.
This commit only adds infrastructure without changing behavior.
@benoitc benoitc force-pushed the fix-python313-gil-crash branch from 84f3570 to d557289 Compare February 22, 2026 18:10
- Replace global g_python_event_loop usage with per-interpreter lookup
  via get_interpreter_event_loop() in all Python-callable functions
- Set per-interpreter storage in nif_set_python_event_loop and
  create_default_event_loop to ensure proper loop binding
- Clear per-interpreter storage in event_loop_destructor when loop
  is destroyed to prevent stale references
- Use per-call ErlNifEnv for timer scheduling/cancellation to prevent
  races in free-threaded Python mode
- Fail fast on RuntimeError in erlang_loop.py instead of silently
  swallowing initialization errors that cause hangs

This fixes py_async_e2e_SUITE timeouts caused by stale event loop
references after test restarts and enables proper sub-interpreter
event loop isolation.
The per-interpreter event loop lookup now falls back to the global
g_python_event_loop when the per-interpreter storage is not set.
This ensures compatibility when the module attribute hasn't been
initialized yet or in environments where the per-interpreter storage
setup fails silently.
- Simplify get_interpreter_event_loop() to use global pointer directly,
  avoiding per-interpreter module attribute lookup issues on Python 3.12+
- Force async worker threads in py_callback.c to use SelectorEventLoop
  directly, bypassing the policy to avoid ErlangEventLoop conflicts
- Update ErlangEventLoopPolicy to only return ErlangEventLoop for the
  main thread; worker threads get default SelectorEventLoop

The ErlangEventLoop is designed for the main execution thread where the
Erlang event router is available. Worker threads need independent event
loops that don't depend on the Erlang-native infrastructure.
@benoitc benoitc merged commit 0c5b5af into main Feb 22, 2026
9 checks passed
@benoitc benoitc deleted the fix-python313-gil-crash branch February 22, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant