Skip to content

Comments

Add handle-based Python C API for per-loop isolation#5

Merged
benoitc merged 4 commits intomainfrom
feature/per-loop-pending-queues
Feb 22, 2026
Merged

Add handle-based Python C API for per-loop isolation#5
benoitc merged 4 commits intomainfrom
feature/per-loop-pending-queues

Conversation

@benoitc
Copy link
Owner

@benoitc benoitc commented Feb 22, 2026

Summary

  • Add handle-based Python C API (_for methods) enabling multiple independent event loops
  • Each erlang_event_loop_t instance is now fully isolated with its own pending queue
  • Enables sub-interpreter support and concurrent asyncio usage

Changes

New Python C API methods:

  • _loop_new() / _loop_destroy(loop) - lifecycle management
  • _run_once_native_for(loop, timeout_ms) - run once on specific loop
  • _add_reader_for / _remove_reader_for - FD read monitoring
  • _add_writer_for / _remove_writer_for - FD write monitoring
  • _schedule_timer_for / _cancel_timer_for - timer operations
  • _wakeup_for / _is_initialized_for / _get_pending_for - utilities

New Erlang NIFs:

  • reselect_reader_fd/1, reselect_writer_fd/1 - FD-only reselect using fd_res->loop backref

Python ErlangEventLoop:

  • Added _loop_handle slot for per-loop isolation
  • All methods use _for variants when handle is set
  • Backward compatible: legacy API still uses global loop

Tests:

  • New py_multi_loop_SUITE.erl with isolation tests
  • All 108 tests pass

Implement per-loop pending queues so each erlang_event_loop_t instance
is fully isolated. This enables multiple independent event loops for
sub-interpreter support and concurrent asyncio usage.

New Python C API methods (_for variants):
- _loop_new() -> capsule: Create new event loop
- _loop_destroy(loop): Destroy event loop
- _run_once_native_for(loop, timeout_ms): Run once on specific loop
- _add_reader_for/_remove_reader_for: FD read monitoring
- _add_writer_for/_remove_writer_for: FD write monitoring
- _schedule_timer_for/_cancel_timer_for: Timer operations
- _wakeup_for/_is_initialized_for/_get_pending_for: Utilities

New Erlang NIFs:
- reselect_reader_fd/1, reselect_writer_fd/1: FD-only reselect using
  fd_res->loop backref (simplifies router code)

Python ErlangEventLoop updates:
- Added _loop_handle slot for per-loop isolation
- All methods use _for variants when handle is set
- Backward compatible: legacy API still uses global loop

Test suite:
- New py_multi_loop_SUITE.erl with isolation tests
- All 108 tests pass
Feature flag:
- Add {event_loop_isolation, global | per_loop} to app env
- Default is 'global' for backward compatibility
- When 'per_loop', each ErlangEventLoop creates its own isolated native loop

Implementation:
- Add g_isolation_mode global flag in C
- Add nif_set_isolation_mode/1 NIF to set the mode
- Add py_get_isolation_mode() Python function to query the mode
- Update ErlangEventLoop.__init__ to auto-create isolated loop in per_loop mode
- py_event_loop:init reads env and sets isolation mode on startup

Integration tests (py_multi_loop_integration_SUITE):
- test_per_loop_mode_enabled: verify mode is correctly set
- test_two_loops_concurrent_sleep: two threads with isolated loops
- test_two_loops_concurrent_gather: isolation with unique markers
- test_isolated_loop_tcp_echo: multiple loop create/destroy

All 112 tests pass.
Per-loop created loops (via _loop_new()) now automatically use a shared
router, enabling full asyncio support including timers and FD operations.

Implementation:
- Add global shared router storage in C (g_shared_router)
- Add set_shared_router/1 NIF to set the shared router
- py_loop_new() automatically assigns shared router to new loops
- Timer messages now include LoopRef for correct dispatch
- Router stores LoopRef with each timer, dispatches to correct loop

Test:
- Add test_two_loops_with_timers verifying timer callbacks work
- All 113 tests pass
@benoitc benoitc force-pushed the feature/per-loop-pending-queues branch from e391ce9 to e36c3f9 Compare February 22, 2026 23:07
Instead of a global mode switch, users can now create isolated loops
explicitly with ErlangEventLoop(isolated=True). This is simpler and
more intuitive.

Changes:
- Add isolated=False parameter to ErlangEventLoop.__init__
- Remove event_loop_isolation app env setting
- Remove set_isolation_mode call from py_event_loop init
- Update integration tests to use isolated=True
- Update docs/asyncio.md with new API
- Update CHANGELOG.md
@benoitc benoitc merged commit 9150564 into main Feb 22, 2026
9 checks passed
@benoitc benoitc deleted the feature/per-loop-pending-queues branch February 22, 2026 23:29
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