Skip to content

Releases: benoitc/erlang-python

v1.8.1

25 Feb 02:56

Choose a tag to compare

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

25 Feb 01:23

Choose a tag to compare

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
  • erlang_asyncio Module - Asyncio-compatible primitives using Erlang's native scheduler

    • erlang_asyncio.sleep(delay, result=None) - Sleep using Erlang's erlang:send_after/3
    • erlang_asyncio.run(coro) - Run coroutine with ErlangEventLoop
    • erlang_asyncio.gather(*coros) - Run coroutines concurrently
    • erlang_asyncio.wait_for(coro, timeout) - Wait with timeout
    • erlang_asyncio.wait(fs, timeout, return_when) - Wait for multiple futures
    • erlang_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_reselect NIF
  • New Test Suite - test/py_erlang_sleep_SUITE.erl with 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

22 Feb 23:51

Choose a tag to compare

Fixed

  • Hex package missing priv directory - Added explicit files configuration to include priv/erlang_loop.py and other necessary files in the hex.pm package

v1.7.0

22 Feb 23:38

Choose a tag to compare

Added

  • Shared Router Architecture for Event Loops

    • Single py_event_router process 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
  • 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.md for usage and architecture details

v1.6.1

22 Feb 22:01
7326786

Choose a tag to compare

Fixed

  • ASGI headers now correctly use bytes instead of str - Fixed ASGI spec compliance issue where headers were being converted to Python str objects instead of bytes. The ASGI specification requires headers to be list[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

v1.6.0

22 Feb 20:57

Choose a tag to compare

Added

  • Python Logging Integration - Forward Python's logging module to Erlang's logger

    • py:configure_logging/0,1 - Setup Python logging to forward to Erlang
    • erlang.ErlangHandler - Python logging handler that sends to Erlang
    • erlang.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 collection
    • py:get_traces/0 - Retrieve collected spans
    • erlang.Span(name, **attrs) - Context manager for creating spans
    • erlang.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_identical for atom comparison instead of strcmp
    • Stack allocate small tuples/maps (≤16 elements) to avoid heap allocation
    • Use enif_make_map_from_arrays for O(n) map building vs O(n²) puts
  • 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+

    • ErlangEventLoop now only used for main thread; worker threads get SelectorEventLoop
    • Per-call ErlNifEnv for thread-safe timer scheduling in free-threaded mode
    • Fail-fast error handling in erlang_loop.py instead 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

18 Feb 09:45

Choose a tag to compare

Added

  • py_asgi module - 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 runner option
    • Sub-interpreter and free-threading (Python 3.13+) support
  • py_wsgi module - 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

18 Feb 03:16

Choose a tag to compare

Added

  • Erlang-native asyncio event loop - Custom asyncio event loop backed by Erlang's scheduler

    • ErlangEventLoop class in priv/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 connections
    • create_server() - TCP server with accept loop
    • _ErlangSocketTransport - Non-blocking socket transport with write buffering
    • _ErlangServer - TCP server with serve_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
    • DatagramProtocol callbacks: datagram_received(), error_received()
    • Support for both connected and unconnected UDP
  • Asyncio event loop documentation

    • New documentation: docs/asyncio.md

Performance

  • Event loop optimizations
    • Fixed run_until_complete callback removal bug
    • Cached ast.literal_eval lookup 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_BUILD cmake option for aggressive optimizations (-O3, LTO, -march=native)

Full Changelog: https://github.com/benoitc/erlang-python/blob/main/CHANGELOG.md

1.3.2

17 Feb 00:39

Choose a tag to compare

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. The erlang module's __getattr__ was returning ErlangFunction wrappers for any attribute access.
    • Solution: Added C-side callback name registry. Now __getattr__ only returns ErlangFunction wrappers for actually registered callbacks. Unregistered attributes raise AttributeError (normal Python behavior).
    • New test: test_callback_name_registry in py_reentrant_SUITE.erl

1.3.1

16 Feb 21:35

Choose a tag to compare

Fixed

  • Hex.pm packaging - Added files section to app.src to include build scripts (do_cmake.sh, do_build.sh) and other necessary files in the hex.pm package