Releases: benoitc/erlang-python
v1.8.1
Fixed
- ASGI scope caching bug - HTTP method was not treated as a dynamic field in the scope template cache. This caused incorrect method values when the same path was accessed with different HTTP methods (e.g., GET /path followed by POST /path would return method="GET" for both requests).
v1.8.0
Added
-
ASGI NIF Optimizations - Six optimizations for high-performance ASGI request handling
- Direct Response Tuple Extraction - Extract
(status, headers, body)directly without generic conversion - Pre-Interned Header Names - 16 common HTTP headers cached as PyBytes objects
- Cached Status Code Integers - 14 common HTTP status codes cached as PyLong objects
- Zero-Copy Request Body - Large bodies (≥1KB) use buffer protocol for zero-copy access
- Scope Template Caching - Thread-local cache of 64 scope templates keyed by path hash
- Lazy Header Conversion - Headers converted on-demand for requests with ≥4 headers
- Direct Response Tuple Extraction - Extract
-
erlang_asyncio Module - Asyncio-compatible primitives using Erlang's native scheduler
erlang_asyncio.sleep(delay, result=None)- Sleep using Erlang'serlang:send_after/3erlang_asyncio.run(coro)- Run coroutine with ErlangEventLooperlang_asyncio.gather(*coros)- Run coroutines concurrentlyerlang_asyncio.wait_for(coro, timeout)- Wait with timeouterlang_asyncio.wait(fs, timeout, return_when)- Wait for multiple futureserlang_asyncio.create_task(coro)- Create background task- Event loop functions:
get_event_loop(),new_event_loop(),set_event_loop(),get_running_loop()
-
Erlang Sleep NIF - Synchronous sleep primitive for Python
py_event_loop._erlang_sleep(delay_ms)- Sleep using Erlang timer- Releases GIL during sleep, no Python event loop overhead
-
Scalable I/O Model - Worker-per-context architecture
py_event_worker- Dedicated worker process per Python context- Combined FD event dispatch and reselect via
handle_fd_event_and_reselectNIF
-
New Test Suite -
test/py_erlang_sleep_SUITE.erlwith 8 tests
Performance
- ASGI marshalling optimizations - 40-60% improvement for typical ASGI workloads
- Eliminates event loop overhead for sleep operations (~0.5-1ms saved per call)
- Sub-millisecond timer precision via BEAM scheduler (vs 10ms asyncio polling)
- Zero CPU when idle - event-driven, no polling
See CHANGELOG.md for full details.
v1.7.1
v1.7.0
Added
-
Shared Router Architecture for Event Loops
- Single
py_event_routerprocess handles all event loops (both shared and isolated) - Timer and FD messages include loop identity for correct dispatch
- Eliminates need for per-loop router processes
- Handle-based Python C API using PyCapsule for loop references
- Single
-
Isolated Event Loops - Create isolated event loops with
ErlangEventLoop(isolated=True)- Default (
isolated=False): uses the shared global loop managed by Erlang - Isolated (
isolated=True): creates a dedicated loop with its own pending queue - Full asyncio support (timers, FD operations) for both modes
- Useful for multi-threaded Python applications where each thread needs its own loop
- See
docs/asyncio.mdfor usage and architecture details
- Default (
v1.6.1
Fixed
- ASGI headers now correctly use bytes instead of str - Fixed ASGI spec compliance issue where headers were being converted to Python
strobjects instead ofbytes. The ASGI specification requires headers to belist[tuple[bytes, bytes]]. This was causing authentication failures and form parsing issues with frameworks like Starlette and FastAPI, which search for headers using bytes keys (e.g.,b"content-type").- Added explicit header handling in
asgi_scope_from_map()to bypass generic conversion - Headers are now correctly converted using
PyBytes_FromStringAndSize() - Supports both list
[name, value]and tuple{name, value}header formats from Erlang - Fixes GitHub issue #1
- Added explicit header handling in
v1.6.0
Added
-
Python Logging Integration - Forward Python's
loggingmodule to Erlang'sloggerpy:configure_logging/0,1- Setup Python logging to forward to Erlangerlang.ErlangHandler- Python logging handler that sends to Erlangerlang.setup_logging(level, format)- Configure logging from Python- Fire-and-forget architecture using
enif_send()for non-blocking messaging - Level filtering at NIF level for performance
- Thread-safe - works from any Python thread
-
Distributed Tracing - Collect trace spans from Python code
py:enable_tracing/0,py:disable_tracing/0- Enable/disable span collectionpy:get_traces/0- Retrieve collected spanserlang.Span(name, **attrs)- Context manager for creating spanserlang.trace(name)- Decorator for tracing functions- Automatic parent/child span linking via thread-local storage
-
New Erlang modules:
py_logger,py_tracer
Performance
-
Type conversion optimizations - Faster Python ↔ Erlang marshalling
- Use
enif_is_identicalfor atom comparison instead ofstrcmp - Stack allocate small tuples/maps (≤16 elements) to avoid heap allocation
- Use
enif_make_map_from_arraysfor O(n) map building vs O(n²) puts
- Use
-
Fire-and-forget NIF architecture - Log and trace calls never block Python execution
Fixed
-
Python 3.12+ event loop thread isolation - Fixed asyncio timeouts on Python 3.12+
ErlangEventLoopnow only used for main thread; worker threads getSelectorEventLoop- Per-call
ErlNifEnvfor thread-safe timer scheduling in free-threaded mode - Fail-fast error handling in
erlang_loop.pyinstead of silent hangs - Added
gil_acquire()/gil_release()helpers to avoid GIL double-acquisition
-
Intermittent test failures on free-threaded Python - Added startup synchronization
v1.5.0
Added
-
py_asgimodule - Optimized ASGI request handling with:- Pre-interned Python string keys (15+ ASGI scope keys)
- Cached constant values (http type, HTTP versions, methods, schemes)
- Thread-local response pooling (16 slots per thread, 4KB initial buffer)
- Direct NIF path bypassing generic py:call()
- ~60-80% throughput improvement over py:call()
- Configurable runner module via
runneroption - Sub-interpreter and free-threading (Python 3.13+) support
-
py_wsgimodule - Optimized WSGI request handling with:- Pre-interned WSGI environ keys
- Direct NIF path for marshalling
- ~60-80% throughput improvement over py:call()
- Sub-interpreter and free-threading support
-
Web frameworks documentation - New documentation at
docs/web-frameworks.md
v1.4.0 - Erlang-native asyncio event loop
Added
-
Erlang-native asyncio event loop - Custom asyncio event loop backed by Erlang's scheduler
ErlangEventLoopclass inpriv/erlang_loop.py- Sub-millisecond latency via Erlang's
enif_select(vs 10ms polling) - Zero CPU usage when idle - no busy-waiting or polling overhead
- Full GIL release during waits for better concurrency
- Native Erlang scheduler integration for I/O events
- Event loop policy via
get_event_loop_policy()
-
TCP support for asyncio event loop
create_connection()- TCP client connectionscreate_server()- TCP server with accept loop_ErlangSocketTransport- Non-blocking socket transport with write buffering_ErlangServer- TCP server withserve_forever()support
-
UDP/datagram support for asyncio event loop
create_datagram_endpoint()- Create UDP endpoints with full parameter support_ErlangDatagramTransport- Datagram transport implementation- Parameters:
local_addr,remote_addr,reuse_address,reuse_port,allow_broadcast DatagramProtocolcallbacks:datagram_received(),error_received()- Support for both connected and unconnected UDP
-
Asyncio event loop documentation
- New documentation:
docs/asyncio.md
- New documentation:
Performance
- Event loop optimizations
- Fixed
run_until_completecallback removal bug - Cached
ast.literal_evallookup at module initialization - O(1) timer cancellation via handle-to-callback_id reverse map
- Detach pending queue under mutex, build Erlang terms outside lock
- O(1) duplicate event detection using hash set
- Added
PERF_BUILDcmake option for aggressive optimizations (-O3, LTO, -march=native)
- Fixed
Full Changelog: https://github.com/benoitc/erlang-python/blob/main/CHANGELOG.md
1.3.2
Fixed
- torch/PyTorch introspection compatibility - Fixed
AttributeError: 'erlang.Function' object has no attribute 'endswith'when importing torch or sentence_transformers in contexts where erlang_python callbacks are registered.- Root cause: torch does dynamic introspection during import, iterating through Python's namespace and calling
.endswith()on objects. Theerlangmodule's__getattr__was returningErlangFunctionwrappers for any attribute access. - Solution: Added C-side callback name registry. Now
__getattr__only returnsErlangFunctionwrappers for actually registered callbacks. Unregistered attributes raiseAttributeError(normal Python behavior). - New test:
test_callback_name_registryinpy_reentrant_SUITE.erl
- Root cause: torch does dynamic introspection during import, iterating through Python's namespace and calling