You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/context-affinity.md
+116Lines changed: 116 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -240,10 +240,126 @@ case py:contexts_started() of
240
240
end.
241
241
```
242
242
243
+
## Process-Bound Environments
244
+
245
+
Process-bound environments provide true process-level isolation for Python state. Each Erlang process automatically gets its own Python namespace that persists across calls.
246
+
247
+
### How It Works
248
+
249
+
When you call `py:call()`, `py:eval()`, or `py:exec()`, the library automatically:
250
+
251
+
1. Looks up or creates a process-local Python environment for your Erlang process
252
+
2. Executes the Python code using that environment
253
+
3. Stores variables, imports, and objects in that environment
254
+
4. Cleans up automatically when your Erlang process exits
255
+
256
+
This happens transparently - no explicit binding required.
257
+
258
+
### Basic Usage
259
+
260
+
```erlang
261
+
%% Get a context
262
+
Ctx=py:context(1),
263
+
264
+
%% Define a variable - it persists for THIS Erlang process
265
+
ok=py:exec(Ctx, <<"counter = 0">>),
266
+
ok=py:exec(Ctx, <<"counter += 1">>),
267
+
{ok, 1} =py:eval(Ctx, <<"counter">>).
268
+
269
+
%% In a different Erlang process, counter is independent:
270
+
spawn(fun() ->
271
+
ok=py:exec(Ctx, <<"counter = 100">>),
272
+
{ok, 100} =py:eval(Ctx, <<"counter">>)
273
+
end).
274
+
275
+
%% Back in original process, still 1
276
+
{ok, 1} =py:eval(Ctx, <<"counter">>).
277
+
```
278
+
279
+
### Process Affinity for AI Workloads
280
+
281
+
Process-bound environments are ideal for scenarios where each Erlang process needs isolated Python state:
%% Process B - same context, but isolated environment
319
+
spawn(fun() ->
320
+
Ctx=py:context(1), %% Same context!
321
+
ok=py:exec(Ctx, <<"x = 'from process B'">>),
322
+
{ok, <<"from process B">>} =py:eval(Ctx, <<"x">>) %% Own value
323
+
end).
324
+
```
325
+
326
+
### Memory Management
327
+
328
+
Environments are automatically freed when:
329
+
- The Erlang process exits (normal, abnormal, or killed)
330
+
- The NIF resource destructor runs during garbage collection
331
+
332
+
No manual cleanup is needed. The environments use the correct memory allocator for each interpreter (critical for subinterpreters which have isolated allocators).
333
+
334
+
### When to Use Process-Bound Environments
335
+
336
+
**Good use cases:**
337
+
- Stateful sessions (chat, game state, user preferences)
338
+
- Long-running workers that accumulate state
339
+
- Process-per-request patterns with state
340
+
- AI pipelines with per-request context
341
+
342
+
**Consider alternatives when:**
343
+
- State must be shared between Erlang processes (use shared state API instead)
344
+
- State needs to outlive the Erlang process (use explicit storage)
345
+
- You need multiple independent namespaces per process (use explicit contexts)
346
+
347
+
### Technical Details
348
+
349
+
Process-bound environments work by:
350
+
351
+
1. Storing a `reference()` in the calling process's dictionary under `py_local_env`
352
+
2. The reference points to a Python dict created inside the interpreter
353
+
3. Each interpreter ID maps to a separate environment (for subinterpreter support)
354
+
4. The NIF uses this dict as `locals` for `exec()` and `eval()` operations
355
+
356
+
For subinterpreters, environments are created inside the target interpreter to ensure memory safety - Python's subinterpreters have isolated memory allocators.
357
+
243
358
## Best Practices
244
359
245
360
1.**Use explicit contexts for stateful operations**: `Ctx = py:context(1)` ensures state persists
246
361
2.**Use automatic routing for stateless calls**: Let the router handle distribution
247
362
3.**Always unbind in finally blocks**: Prevent context leaks
248
363
4.**Minimize binding time**: Don't hold contexts longer than necessary
249
364
5.**Monitor pool size**: Check `py_context_router:num_contexts()` to understand capacity
365
+
6.**Leverage process-bound environments**: For per-process state, rely on automatic environment isolation rather than manual binding
0 commit comments