Skip to content

Commit 81c871a

Browse files
committed
Document reactor/event loop integration with OWN_GIL
1 parent f57f30b commit 81c871a

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

docs/owngil_internals.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)