Current section
Files
Jump to
Current section
Files
docs/decisions/0004-isolated-mode-child-process.md
# 0004: Isolation is a child OS process over a Unix socket
Since 5.0.0. Code: `src/py_isolated.erl`, `priv/py_isolated_child.py`,
`priv/_erlang_impl/_isolated.py`.
## Situation
Embedded modes cannot stop a Python call stuck in C, cannot bound its
memory or CPU, and a segfault in an extension kills the node. Running
untrusted or unknown Python code needs those guarantees, with the public
API unchanged so a pool can mix modes.
## Decision
An isolated context is one child process per context, started with
`open_port` so the VM reaps it, talking to its `py_context` process over
a Unix socket with the frame format of the callback pipe
(`<<Id:64, Len:32, Body>>`, body `<<Status, ETF>>`). Interrupts are a
signal to the child's main thread with a `SIGKILL` backstop; limits are
rlimits, cgroups v2 on Linux and an RSS watchdog on macOS; a crash
restarts the child within a budget. The child uses the standard asyncio
loop; there is no C code of its own in the VM beyond `os_kill`.
Not chosen: a seccomp or Capsicum sandbox (a later hardening step, the
process boundary is the first one), a NIF-side sub-process pool, and a
new wire protocol (the existing frame and ETF conventions are enough).
## Consequences
- Everything crossing the socket is a term. NIF resources (channels,
native buffers, object references) do not cross; the API says so with
`{error, not_supported_in_isolated}`.
- The child can create atoms (`binary_to_term` is not called with
`safe` because handles and control terms need atoms); untrusted code
must not mint unbounded distinct atoms.
- Each call copies arguments and results through the socket; bulk data
needs shared memory (0006).
- Python state is lost on restart; the context stays usable.
- Platform code lives in the child launcher: parent-death signal
(`prctl` on Linux, `procctl` on FreeBSD), memory limits per OS.