Skip to content

Commit 3ff6ba1

Browse files
committed
Update README: document COW fork API (init/work/fork)
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent 3d2c86a commit 3ff6ba1

File tree

1 file changed

+51
-1
lines changed

1 file changed

+51
-1
lines changed

README.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protects your working directory automatically.
2525
| Network isolation | Landlock port range + seccomp notif port virtualization | Network namespace | TAP device | Sentry kernel |
2626
| Syscall filtering | seccomp-bpf | seccomp | N/A (full kernel) | Sentry kernel |
2727
| Resource limits | seccomp notif + SIGSTOP/SIGCONT | cgroup v2 | VM config | cgroup v2 |
28-
| Memory sharing | COW (fork), zero-copy | Bind-mount + re-init | Shared mem (explicit) | N/A |
28+
| Memory sharing | COW fork (`sb.fork()`), zero-copy | Bind-mount + re-init | Shared mem (explicit) | N/A |
2929
| Nesting | Native (fork) | Complex (DinD/DooD) | Not supported | Supported |
3030
| COW filesystem | OverlayFS (kernel) / BranchFS | Overlay | Block-level | N/A |
3131
| Checkpoint/restore | ptrace + BranchFS | CRIU | VM snapshot | N/A |
@@ -140,6 +140,22 @@ with Sandbox(parent_policy) as parent:
140140
result = parent.sandbox(child_policy).run(["python3", "untrusted.py"])
141141
```
142142

143+
COW fork — init once, fork many (for AI agents, RL rollouts):
144+
145+
```python
146+
def init():
147+
global model
148+
model = load_model() # expensive, done once
149+
150+
def work():
151+
seed = int(os.environ["SEED"])
152+
rollout(model, seed) # reads COW-shared model
153+
154+
with Sandbox(policy, init, work) as sb:
155+
for seed in range(1000):
156+
sb.fork(env={"SEED": str(seed)}).wait()
157+
```
158+
143159
## Architecture
144160

145161
Sandlock applies confinement in sequence after `fork()`:
@@ -317,6 +333,40 @@ cp = Checkpoint.load("my-env")
317333
Sandbox.from_checkpoint(cp, restore_fn=lambda state: load_state(state))
318334
```
319335

336+
### COW Fork
337+
338+
Initialize expensive state once, then fork COW clones that share memory.
339+
Each `fork()` is an `os.fork()` — the kernel shares all pages copy-on-write.
340+
1000 clones of a 50 MB Python process use ~50 MB total, not 50 GB.
341+
342+
```python
343+
def init():
344+
global model, data
345+
model = load_model() # 2 GB, loaded once
346+
data = preprocess_dataset() # stored in globals
347+
348+
def work():
349+
seed = int(os.environ["SEED"])
350+
result = rollout(model, data, seed) # reads COW-shared globals
351+
save_result(result)
352+
353+
with Sandbox(policy, init, work) as sb:
354+
for seed in range(1000):
355+
sb.fork(env={"SEED": str(seed)}).wait()
356+
```
357+
358+
How it works:
359+
1. `Sandbox(policy, init, work)` forks a child, runs `init()`, then the
360+
child's main thread enters a fork-ready loop on the control socket
361+
2. `sb.fork(env=...)` sends a command — the main thread calls `os.fork()`,
362+
the clone applies env overrides and runs `work()`
363+
3. No signals, no ptrace — the main thread is in a blocking `os.read()`
364+
(GIL released), so it forks itself cleanly
365+
4. Each clone inherits Landlock + seccomp confinement via `fork()`
366+
367+
Designed for AI agent sandboxing and RL rollouts where setup is expensive
368+
but each run needs different parameters.
369+
320370
### Privileged Mode
321371

322372
`privileged=True` maps UID 0 inside a user namespace. The child appears as

0 commit comments

Comments
 (0)