Current section
Files
Jump to
Current section
Files
examples/reactor_owngil_example.erl
%% @doc Example: OWN_GIL reactor with dedicated threads.
%%
%% Each subinterpreter handle runs in a dedicated pthread with its own GIL.
%% This provides true parallelism for CPU-bound protocol processing.
%%
%% Best for: ML inference, heavy parsing, CPU-bound protocol logic.
%%
%% Note: Requires Python 3.12+ with subinterpreter support.
-module(reactor_owngil_example).
-export([start/0, start/1, stop/1]).
%% Protocol that simulates CPU-intensive work
-define(CPU_PROTOCOL, <<"
import erlang.reactor as reactor
import hashlib
class CPUProtocol(reactor.Protocol):
'''Protocol with CPU-intensive hashing.'''
def __init__(self):
super().__init__()
self.iterations = 10000
def connection_made(self, fd, client_info):
super().connection_made(fd, client_info)
def data_received(self, data):
# CPU-intensive hashing (runs in parallel due to OWN_GIL)
result = bytes(data)
for _ in range(self.iterations):
result = hashlib.sha256(result).digest()
self.write_buffer.extend(result)
return 'write_pending'
def write_ready(self):
if self.write_buffer:
written = self.write(bytes(self.write_buffer))
del self.write_buffer[:written]
if self.write_buffer:
return 'continue'
return 'read_pending'
reactor.set_protocol_factory(CPUProtocol)
">>).
%% @doc Start with default settings (4 handles).
start() ->
start(#{handles => 4, port => 8081}).
%% @doc Start OWN_GIL reactor.
%%
%% Options:
%% handles - Number of subinterpreter handles (default: 4)
%% port - Port to listen on (default: 8081)
%%
%% Returns: {ok, State} where State can be passed to stop/1
start(Opts) ->
NumHandles = maps:get(handles, Opts, 4),
Port = maps:get(port, Opts, 8081),
%% Start OWN_GIL thread pool
ok = py:subinterp_pool_start(NumHandles),
%% Create subinterpreter handles - each with its own pthread + GIL
Handles = [begin
{ok, Handle} = py:subinterp_create(),
%% Initialize reactor protocol in this subinterpreter
ok = py:subinterp_exec(Handle, ?CPU_PROTOCOL),
Handle
end || _ <- lists:seq(1, NumHandles)],
%% Start acceptor
{ok, ListenSock} = gen_tcp:listen(Port, [
binary,
{active, false},
{reuseaddr, true},
{backlog, 64}
]),
Acceptor = spawn_link(fun() ->
accept_loop(ListenSock, Handles, 1)
end),
io:format("OWN_GIL reactor started on port ~p with ~p handles~n", [Port, NumHandles]),
io:format("Each handle runs in its own pthread with dedicated GIL~n"),
{ok, #{handles => Handles, acceptor => Acceptor, socket => ListenSock}}.
%% @doc Stop the OWN_GIL reactor.
stop(#{handles := Handles, acceptor := Acceptor, socket := Socket}) ->
exit(Acceptor, shutdown),
gen_tcp:close(Socket),
[py:subinterp_destroy(H) || H <- Handles],
py:subinterp_pool_stop(),
ok.
accept_loop(ListenSock, Handles, Idx) ->
case gen_tcp:accept(ListenSock) of
{ok, Socket} ->
{ok, Fd} = prim_inet:getfd(Socket),
Handle = lists:nth(Idx, Handles),
ClientInfo = get_client_info(Socket),
%% Initialize connection via OWN_GIL reactor API
ok = py:subinterp_reactor_init(Handle, Fd, ClientInfo),
%% Spawn handler for this connection
spawn_link(fun() -> handle_connection(Handle, Fd, Socket) end),
NextIdx = (Idx rem length(Handles)) + 1,
accept_loop(ListenSock, Handles, NextIdx);
{error, closed} ->
ok
end.
get_client_info(Socket) ->
case inet:peername(Socket) of
{ok, {Addr, Port}} ->
#{addr => inet:ntoa(Addr), port => Port, type => tcp};
_ ->
#{type => tcp}
end.
handle_connection(Handle, Fd, Socket) ->
%% Simple blocking receive for example purposes
case gen_tcp:recv(Socket, 0, 30000) of
{ok, Data} ->
%% Dispatch to OWN_GIL subinterpreter
case py:subinterp_reactor_read(Handle, Fd, Data) of
{ok, <<"write_pending">>} ->
handle_write(Handle, Fd, Socket);
{ok, <<"continue">>} ->
handle_connection(Handle, Fd, Socket);
{ok, <<"close">>} ->
py:subinterp_reactor_close(Handle, Fd),
gen_tcp:close(Socket);
{error, _Reason} ->
py:subinterp_reactor_close(Handle, Fd),
gen_tcp:close(Socket)
end;
{error, closed} ->
py:subinterp_reactor_close(Handle, Fd);
{error, _} ->
py:subinterp_reactor_close(Handle, Fd),
gen_tcp:close(Socket)
end.
handle_write(Handle, Fd, Socket) ->
case py:subinterp_reactor_write(Handle, Fd) of
{ok, <<"read_pending">>} ->
handle_connection(Handle, Fd, Socket);
{ok, <<"continue">>} ->
handle_write(Handle, Fd, Socket);
{ok, <<"close">>} ->
py:subinterp_reactor_close(Handle, Fd),
gen_tcp:close(Socket);
_ ->
py:subinterp_reactor_close(Handle, Fd),
gen_tcp:close(Socket)
end.