@@ -253,6 +253,129 @@ py_env_resource_dtor(env, res) {
253253}
254254```
255255
256+ ## Reactor / Event Loop Integration
257+
258+ OWN_GIL contexts support the reactor pattern for I/O-driven protocols. The `py_event_loop` module is registered in each OWN_GIL subinterpreter during startup.
259+
260+ ### Why Event Loop Registration Matters
261+
262+ Each Python subinterpreter has its own module namespace. The `py_event_loop` module provides:
263+ - `erlang.reactor` protocol callbacks (`on_read_ready`, `on_write_ready`, `init_connection`)
264+ - Per-interpreter state for cached function references
265+ - Module state isolation between interpreters
266+
267+ ### Reactor Request Flow
268+
269+ ```
270+ ┌────────────────────────────────────────────────────────────────────────┐
271+ │ Erlang │
272+ ├────────────────────────────────────────────────────────────────────────┤
273+ │ │
274+ │ py_reactor_context │
275+ │ │ │
276+ │ │ {select, FdRes, Ref, ready_input} │
277+ │ ▼ │
278+ │ handle_info │
279+ │ │ │
280+ │ ├── Read data from fd into ReactorBuffer │
281+ │ │ │
282+ │ └── py_nif: reactor_on_read_ready (CtxRef, Fd) │
283+ │ │ │
284+ └────────────────┼────────────────────────────────────────────────────────┘
285+ │
286+ │ [ ctx->uses_own_gil == true]
287+ ▼
288+ ┌────────────────────────────────────────────────────────────────────────┐
289+ │ dispatch_reactor_read_to_owngil(env, ctx, fd, buffer_ptr) │
290+ │ │ │
291+ │ ├── ctx->reactor_buffer_ptr = buffer_ptr │
292+ │ ├── ctx->request_type = CTX_REQ_REACTOR_READ │
293+ │ ├── pthread_cond_signal(&request_ready) │
294+ │ └── pthread_cond_wait(&response_ready) │
295+ └────────────────────────────────────────────────────────────────────────┘
296+ │
297+ ▼
298+ ┌────────────────────────────────────────────────────────────────────────┐
299+ │ OWN_GIL Thread │
300+ ├────────────────────────────────────────────────────────────────────────┤
301+ │ │
302+ │ owngil_execute_reactor_read(ctx) │
303+ │ │ │
304+ │ ├── Create ReactorBuffer Python object │
305+ │ │ │
306+ │ ├── Get module state (per-interpreter reactor cache) │
307+ │ │ state = get_module_state() │
308+ │ │ ensure_reactor_cached_for_interp(state) │
309+ │ │ │
310+ │ └── Call Python: state->reactor_on_read(fd, buffer) │
311+ │ │ │
312+ │ ▼ │
313+ │ erlang.reactor.on_read_ready(fd, data) │
314+ │ │ │
315+ │ ▼ │
316+ │ Protocol.data_received(data) │
317+ │ │ │
318+ │ └── Returns action: "continue" | "write_pending" | ... │
319+ │ │
320+ └────────────────────────────────────────────────────────────────────────┘
321+ ```
322+
323+ ### Module State Per-Interpreter
324+
325+ Each OWN_GIL subinterpreter maintains its own cached references:
326+
327+ ```c
328+ typedef struct {
329+ PyObject *reactor_module; // erlang.reactor module
330+ PyObject *reactor_on_read; // Cached on_read_ready function
331+ PyObject *reactor_on_write; // Cached on_write_ready function
332+ PyObject *reactor_init_conn; // Cached init_connection function
333+ // ...
334+ } py_event_loop_module_state_t;
335+ ```
336+
337+ The ` ensure_reactor_cached_for_interp() ` function lazily imports ` erlang.reactor ` and caches the callback functions on first use within each interpreter.
338+
339+ ### Reactor Request Types
340+
341+ | Request Type | Dispatch Function | Execute Function |
342+ | --------------| -------------------| ------------------|
343+ | ` CTX_REQ_REACTOR_READ ` | ` dispatch_reactor_read_to_owngil ` | ` owngil_execute_reactor_read ` |
344+ | ` CTX_REQ_REACTOR_WRITE ` | ` dispatch_reactor_write_to_owngil ` | ` owngil_execute_reactor_write ` |
345+ | ` CTX_REQ_REACTOR_INIT ` | ` dispatch_reactor_init_to_owngil ` | ` owngil_execute_reactor_init ` |
346+
347+ ### Buffer Handling
348+
349+ For read operations, the ` ReactorBuffer ` (zero-copy buffer) is passed through:
350+
351+ 1 . ` py_reactor_context ` reads data into a ` reactor_buffer_resource_t `
352+ 2 . Buffer pointer stored in ` ctx->reactor_buffer_ptr `
353+ 3 . OWN_GIL thread wraps it in a Python ` ReactorBuffer ` object
354+ 4 . Python protocol receives data via buffer protocol (zero-copy)
355+
356+ ### Example: TCP Echo Server with OWN_GIL
357+
358+ ``` erlang
359+ % % Start OWN_GIL context for protocol handling
360+ {ok , Ctx } = py_context :start_link (1 , owngil ),
361+
362+ % % Define protocol in Python
363+ py_context :exec (Ctx , <<"
364+ import erlang.reactor as reactor
365+
366+ class EchoProtocol(reactor.Protocol):
367+ def data_received(self, data):
368+ self.write(data) # Echo back
369+ return 'write_pending'
370+ " >>),
371+
372+ % % Start reactor with the context
373+ {ok , Reactor } = py_reactor_context :start_link (#{
374+ context => Ctx ,
375+ protocol_class => <<" EchoProtocol" >>
376+ }).
377+ ```
378+
256379## Performance Characteristics
257380
258381| Operation | Shared-GIL | OWN_GIL |
0 commit comments