@@ -289,6 +289,118 @@ class SimpleHTTPProtocol(reactor.Protocol):
289289reactor.set_protocol_factory(SimpleHTTPProtocol)
290290```
291291
292+ ## Passing Sockets from Erlang to Python
293+
294+ ### Method 1: Socket FD Handoff to Reactor
295+
296+ The most efficient way is to hand off the socket's file descriptor directly:
297+
298+ ``` erlang
299+ % % Erlang: Accept and hand off to Python reactor
300+ {ok , ClientSock } = gen_tcp :accept (ListenSock ),
301+ {ok , {Addr , Port }} = inet :peername (ClientSock ),
302+
303+ % % Get the raw file descriptor
304+ {ok , Fd } = inet :getfd (ClientSock ),
305+
306+ % % Hand off to Python - Erlang no longer owns this socket
307+ py_reactor_context :handoff (Fd , #{
308+ addr => inet :ntoa (Addr ),
309+ port => Port ,
310+ type => tcp
311+ }).
312+ ```
313+
314+ ``` python
315+ # Python: Protocol handles the fd
316+ import erlang.reactor as reactor
317+
318+ class MyProtocol (reactor .Protocol ):
319+ def data_received (self , data ):
320+ # self.fd is the socket fd from Erlang
321+ self .write_buffer.extend(b " Got: " + data)
322+ return " write_pending"
323+
324+ reactor.set_protocol_factory(MyProtocol)
325+ ```
326+
327+ ### Method 2: Pass Socket FD to asyncio
328+
329+ For asyncio-based code, pass the fd and wrap it in Python:
330+
331+ ``` erlang
332+ % % Erlang: Get fd and pass to Python
333+ {ok , ClientSock } = gen_tcp :accept (ListenSock ),
334+ {ok , Fd } = inet :getfd (ClientSock ),
335+
336+ % % Call Python with the fd
337+ Ctx = py :context (1 ),
338+ py :call (Ctx , my_handler , handle_connection , [Fd ]).
339+ ```
340+
341+ ``` python
342+ # Python: Wrap fd in asyncio
343+ import asyncio
344+ import socket
345+
346+ async def handle_connection (fd : int ):
347+ # Create socket from fd (Python takes ownership)
348+ sock = socket.socket(fileno = fd)
349+ sock.setblocking(False )
350+
351+ # Use asyncio streams
352+ reader, writer = await asyncio.open_connection(sock = sock)
353+
354+ data = await reader.read(1024 )
355+ writer.write(b " Echo: " + data)
356+ await writer.drain()
357+ writer.close()
358+ await writer.wait_closed()
359+
360+ def handle_connection_sync (fd : int ):
361+ """ Sync wrapper for Erlang call."""
362+ asyncio.run(handle_connection(fd))
363+ ```
364+
365+ ### Method 3: Pass Socket Object via Pickle (Not Recommended)
366+
367+ For simple cases, you can pickle socket info, but this is less efficient:
368+
369+ ``` erlang
370+ % % Erlang: Pass connection info
371+ {ok , {Addr , Port }} = inet :peername (ClientSock ),
372+ py :call (Ctx , my_handler , connect_to , [Addr , Port ]).
373+ ```
374+
375+ ``` python
376+ # Python: Create new connection (less efficient - new socket)
377+ import socket
378+
379+ def connect_to (addr : str , port : int ):
380+ sock = socket.create_connection((addr, port))
381+ # ... use socket
382+ ```
383+
384+ ### Important: Socket Ownership
385+
386+ When passing an fd from Erlang to Python:
387+
388+ 1 . ** Erlang releases ownership** : After ` inet:getfd/1 ` , don't use the Erlang socket
389+ 2 . ** Python takes ownership** : Close the socket in Python when done
390+ 3 . ** Don't double-close** : Either Erlang or Python closes, not both
391+
392+ ``` erlang
393+ % % WRONG - double close
394+ {ok , Fd } = inet :getfd (ClientSock ),
395+ py_reactor_context :handoff (Fd , #{}),
396+ gen_tcp :close (ClientSock ). % % BAD: Python will also close
397+
398+ % % RIGHT - let Python handle it
399+ {ok , Fd } = inet :getfd (ClientSock ),
400+ py_reactor_context :handoff (Fd , #{}).
401+ % % Don't close ClientSock - Python owns it now
402+ ```
403+
292404## Integration with Erlang
293405
294406### From Erlang: Starting a Reactor Server
@@ -300,7 +412,8 @@ reactor.set_protocol_factory(SimpleHTTPProtocol)
300412
301413start (Port ) ->
302414 % % Set up the Python protocol factory first
303- ok = py :exec (<<"
415+ Ctx = py :context (1 ),
416+ ok = py :exec (Ctx , <<"
304417import erlang.reactor as reactor
305418from my_protocols import MyProtocol
306419reactor.set_protocol_factory(MyProtocol)
0 commit comments