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
@@ -60,6 +66,7 @@ Handles remote execution. Built entirely on `asyncio`:
60
66
-**`result.py`**: `Result` is an awaitable future returned by `.start()`. Simple async polling loop for stall detection. Supports `cancel()` and `progress()` methods.
61
67
-**`deps.py`**: Package installation via `asyncio.create_subprocess_exec`.
62
68
-**`backends/`**: All backend methods are `async def`. `listen()` and `subscribe_results()` are async generators.
69
+
-**`sandbox/`**: Optional Docker-based execution isolation. `DockerSandbox` boots a container, connects to a stdlib-only `guest_agent.py` over TCP (length-prefixed JSON), and delegates code execution. When `--sandbox` is off, the worker runs code directly in the host process.
63
70
64
71
## Data flow
65
72
@@ -203,3 +210,6 @@ pytest # test suite
203
210
-`_capture_closure()` in `graph.py` uses a multi-tier strategy: repr validation → traced functions → lambdas (source extraction) → non-traced user functions (auto-registration) → constructor expressions (defaultdict/Counter/deque) → pickle fallback → warning. Returns function objects for auto-registration.
204
211
-`_set_class_metadata()` in `graph.py` captures class-level attributes and decorators from the class source AST. Called from both `_auto_register_class` and `_discover_self_call_deps` to handle both constructor-discovered and directly-traced method classes.
205
212
-`_resolve_class_bases()` now also extracts class definition keywords (e.g., `metaclass=ABCMeta`) and adds necessary imports for keyword values.
213
+
-`guest_agent.py` is intentionally stdlib-only so it can be deployed into containers without installing pyfuse. It duplicates `_protocol.py` wire helpers for this reason.
214
+
-`DockerSandbox` is an async context manager (`__aenter__`/`__aexit__`). The `Worker` calls `start()`/`stop()` at lifecycle boundaries.
215
+
-`DockerSandbox` auto-builds the Docker image from the bundled `Dockerfile` on first use.
By default, workers run tasks in the host process. For security or isolation, you can run tasks inside Docker containers or tart micro-VMs. Sandboxing is fully transparent to clients.
pyfuse worker --backend redis://localhost:6379 --sandbox vm
291
+
```
292
+
293
+
See the [Sandbox guide](SANDBOX.md) for full setup and management instructions.
294
+
271
295
## Running scripts in a temporary venv
272
296
273
297
The `run` command creates an isolated venv, auto-detects third-party dependencies from the script (including `install_package_as` blocks), installs them, and runs the script:
Run worker tasks inside isolated Docker containers. Sandboxing is transparent to clients -- no code changes required on the sender side.
4
+
5
+
## Overview
6
+
7
+
By default, workers execute tasks in the host process. With `--sandbox`, execution is delegated to a guest agent running inside a Docker container. The worker still handles caching, dependency resolution, and retries on the host; only the `compile()` → `exec()` → call step moves into the sandbox.
8
+
9
+
## Requirements
10
+
11
+
- Docker installed and running (`docker info` should succeed)
12
+
- The current user can run `docker` commands (docker group or rootless Docker)
13
+
14
+
## Setup
15
+
16
+
```bash
17
+
pyfuse sandbox setup
18
+
```
19
+
20
+
This builds the `pyfuse-sandbox` Docker image from the bundled Dockerfile. The image is based on `python:3.12-slim` and contains only the stdlib-only guest agent -- no pyfuse installation needed inside the container.
21
+
22
+
You can also build manually:
23
+
24
+
```bash
25
+
bash scripts/setup_sandbox_docker.sh
26
+
```
27
+
28
+
Or let the worker build automatically on first use (the image is built lazily if it doesn't exist).
`DockerSandbox` accepts the following keyword arguments:
94
+
95
+
| Parameter | Default | Description |
96
+
|-----------|---------|-------------|
97
+
|`image`|`"pyfuse-sandbox"`| Docker image name |
98
+
|`container_name`|`"pyfuse-sandbox"`| Container name |
99
+
|`guest_port`|`9749`| TCP port the guest agent listens on |
100
+
|`cpus`|`2`| vCPUs allocated to the container |
101
+
|`memory_gb`|`2`| RAM (GB) allocated to the container |
102
+
|`timeout`|`60.0`| Max seconds per function execution |
103
+
|`boot_timeout`|`30.0`| Max seconds to wait for container to start |
104
+
105
+
## How it works
106
+
107
+
The sandbox uses a guest agent (`guest_agent.py`) — a lightweight, stdlib-only Python script deployed inside the container. The worker communicates with it over TCP using a length-prefixed JSON protocol:
108
+
109
+
1. Worker sends the reconstructed source code, function name, and arguments
110
+
2. Guest agent `exec`s the source, calls the function, and returns the result
111
+
3. Errors are serialized with type, message, and traceback
112
+
113
+
The sandbox stays running between tasks. The first execution incurs a startup cost (container boot + agent connection), but subsequent calls reuse the same connection.
114
+
115
+
## Troubleshooting
116
+
117
+
**Image build fails**
118
+
- Ensure Docker daemon is running: `docker info`
119
+
- Check permissions: your user should be in the `docker` group or use rootless Docker
120
+
121
+
**Sandbox timeout**
122
+
- Increase `timeout` in `DockerSandbox` for long-running functions
@@ -487,6 +492,74 @@ When a module contains `from X import *`:
487
492
2. Create individual `ImportInfo` entries per exported name.
488
493
3. Filter to only names the function actually uses.
489
494
495
+
## Sandbox execution
496
+
497
+
Workers can optionally execute tasks inside an isolated Docker container instead of the host process. This is controlled by the `--sandbox` CLI flag or by passing `sandbox=True` (or a `DockerSandbox` instance) programmatically.
498
+
499
+
### How it works
500
+
501
+
When sandboxing is enabled, only the `exec → call` step moves into the container. The worker's own control logic — caching, dependency resolution, retry — stays on the host.
Worker->>Worker: compile() → exec() → call function
520
+
end
521
+
522
+
Worker->>Client: Result
523
+
```
524
+
525
+
A lightweight **guest agent** (`guest_agent.py`) runs inside the container. It is stdlib-only (no pyfuse install required) and communicates with the worker over TCP using a **length-prefixed JSON protocol** (4-byte big-endian header + UTF-8 JSON payload).
526
+
527
+
### Container lifecycle
528
+
529
+
```mermaid
530
+
stateDiagram-v2
531
+
[*] --> ImageCheck: worker starts with --sandbox
532
+
ImageCheck --> BuildImage: image missing
533
+
ImageCheck --> StartContainer: image exists
534
+
BuildImage --> StartContainer: docker build
535
+
StartContainer --> WaitForAgent: docker run -d
536
+
WaitForAgent --> Connected: TCP handshake
537
+
Connected --> Execute: task arrives
538
+
Execute --> Connected: result returned
539
+
Connected --> Stopped: worker shutdown
540
+
Stopped --> [*]
541
+
```
542
+
543
+
1.**Start** — `DockerSandbox.start()` builds the image (if absent), starts the container, waits for the guest agent to become reachable, then opens a persistent TCP connection.
544
+
2.**Execute** — Each task is sent as a JSON request. The guest agent `exec`s the source, calls the function, and returns the result. The connection is reused across tasks.
545
+
3.**Stop** — On worker shutdown, the connection is closed and the container is stopped.
546
+
547
+
### Configuration
548
+
549
+
`DockerSandbox` accepts the following keyword arguments:
550
+
551
+
| Parameter | Default | Description |
552
+
|-----------|---------|-------------|
553
+
|`image`|`"pyfuse-sandbox"`| Docker image name |
554
+
|`container_name`|`"pyfuse-sandbox"`| Container name |
555
+
|`guest_port`|`9749`| TCP port for the guest agent |
556
+
|`cpus`|`2`| vCPUs allocated to the container |
557
+
|`memory_gb`|`2`| RAM (GB) allocated to the container |
558
+
|`timeout`|`60.0`| Max seconds per function execution |
559
+
|`boot_timeout`|`30.0`| Max seconds to wait for container to start |
560
+
561
+
Environment variables `PYFUSE_SANDBOX_DOCKER_IMAGE` and `PYFUSE_SANDBOX_DOCKER_CONTAINER` override the image and container names.
0 commit comments