Skip to content

Add ByteChannel API for raw byte streaming#35

Merged
benoitc merged 6 commits intomainfrom
feature/byte-channel
Mar 17, 2026
Merged

Add ByteChannel API for raw byte streaming#35
benoitc merged 6 commits intomainfrom
feature/byte-channel

Conversation

@benoitc
Copy link
Owner

@benoitc benoitc commented Mar 17, 2026

Summary

  • Add ByteChannel API for raw byte streaming without term serialization
  • Add event-driven async receive for Channel and ByteChannel (replaces polling)
  • Add direct C methods for async wait/cancel (bypasses Erlang callback overhead)

ByteChannel API

Erlang:

{ok, Ch} = py_byte_channel:new(),
ok = py_byte_channel:send(Ch, <<"raw bytes">>),
{ok, Data} = py_byte_channel:recv(Ch),
py_byte_channel:close(Ch).

Python:

from erlang import ByteChannel
ch = ByteChannel(channel_ref)
data = ch.receive_bytes()
data = await ch.async_receive_bytes()  # Event-driven, no polling

Event-Driven Async

When using ErlangEventLoop, async_receive() and async_receive_bytes() now use event-driven notification instead of polling:

  • Register with channel via direct C method
  • Wait for EVENT_TYPE_TIMER dispatch when data arrives
  • Falls back to polling for non-Erlang event loops

Benchmark Results

--- Streaming Benchmark (1MB transfer) ---
     Chunk |      Channel |  ByteChannel |     PyBuffer
   (bytes) |     (MB/sec) |     (MB/sec) |     (MB/sec)
------------------------------------------------------
      1024 |      1009.08 |      1445.09 |      6172.84
      4096 |      3401.36 |      4950.50 |      5882.35
     16384 |      6756.76 |      5649.72 |      6493.51
     65536 |      6250.00 |     12500.00 |      7407.41

benoitc added 6 commits March 17, 2026 15:01
ByteChannel provides raw byte streaming between Erlang and Python without
term serialization overhead, suitable for HTTP bodies, file transfers, and
binary protocols.

Erlang API:
- py_byte_channel:new/0,1 - Create channel with optional backpressure
- py_byte_channel:send/2 - Send raw bytes
- py_byte_channel:recv/1,2 - Blocking receive with optional timeout
- py_byte_channel:try_receive/1 - Non-blocking receive
- py_byte_channel:close/1 - Close channel

Python API:
- ByteChannel class with send_bytes, receive_bytes, try_receive_bytes
- async_receive_bytes for asyncio compatibility
- Sync and async iteration support

Implementation reuses the existing py_channel_t infrastructure with new
NIF functions that skip term_to_binary/binary_to_term conversion.
Use simple polling (asyncio.sleep) like the existing Channel.async_receive()
instead of the more complex event loop dispatch integration. Both can be
upgraded to proper event-driven async in a future change.

Added async e2e test for ByteChannel.
Replaces polling with proper event loop integration:
- Register with channel via direct C method (no Erlang callback overhead)
- Wait for EVENT_TYPE_TIMER dispatch when data arrives
- Falls back to polling for non-Erlang event loops

New direct Python methods (bypass erlang.call):
- erlang._channel_wait(ch, callback_id, loop_capsule)
- erlang._channel_cancel_wait(ch, callback_id)
- erlang._byte_channel_wait(ch, callback_id, loop_capsule)
- erlang._byte_channel_cancel_wait(ch, callback_id)

When ErlangEventLoop is used:
1. async_receive registers handle in loop._timers
2. Direct C call registers waiter with channel
3. channel_send dispatches via event_loop_add_pending
4. Event loop _dispatch fires callback, resolves Future
5. No polling overhead
- Add behavior notes for async_receive() explaining event-driven vs polling
- Add event-driven async for ByteChannel documentation
- Add architecture diagram showing async receive flow with ErlangEventLoop
Benchmark measures:
- Erlang send throughput (no Python)
- Erlang roundtrip (send + receive)
- Python receive performance
- Streaming throughput (1MB transfer)

Run with: escript examples/bench_byte_channel.erl
Only use _timers dict for channel waiter registration. The
_handle_to_callback_id dict is for timer cancellation and shouldn't
be modified by channel waiters - this could cause race conditions
in free-threaded Python.
@benoitc benoitc merged commit 2b0e7d3 into main Mar 17, 2026
11 checks passed
@benoitc benoitc deleted the feature/byte-channel branch March 17, 2026 15:01
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