Skip to content

Commit e8bf3b3

Browse files
committed
local backend
1 parent 46d5220 commit e8bf3b3

12 files changed

Lines changed: 482 additions & 504 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Only the entry point needs `@trace`. Everything it calls -- `add()`, imports, cl
3838
Start an isolated worker (temporary venv with `--tmp`, cleaned up on exit):
3939

4040
```bash
41-
pyfuse worker --tmp --backend shm://localhost:9847 # Same machine using shared memory
41+
pyfuse worker --tmp --backend local://localhost:9748 # Same machine (async TCP)
4242
pyfuse worker --tmp --backend redis://localhost:6379 # Remote using Redis
4343
```
4444

@@ -84,7 +84,7 @@ Common mappings (`cv2` -> `opencv-python`, `PIL` -> `Pillow`, etc.) are built in
8484
- **Class methods** -- `self.method()` and `cls.method()` dependencies are detected. Entire class hierarchies (including `super()`), class-level attributes, decorators (`@dataclass`, etc.), and metaclass keywords are reconstructed.
8585
- **Retry and timeout** -- `@trace(timeout=30, retries=3)` with exponential backoff.
8686
- **Batch submission** -- `await func.map([(a1, b1), (a2, b2)])` submits and awaits multiple tasks.
87-
- **Pluggable backends** -- Redis (`redis://`) for multi-machine, shared memory (`shm://`) for same-machine IPC.
87+
- **Pluggable backends** -- Redis (`redis://`) for multi-machine, local (`local://`) for same-machine IPC.
8888
- **Content-hash caching** -- Workers cache compiled functions by content hash. Same code from different clients = cache hit.
8989

9090
## Examples

docs/CONTEXT.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pyfuse/
3333
└── backends/
3434
├── base.py # Backend ABC: async transport interface
3535
├── redis.py # RedisBackend: redis.asyncio with RPUSH/BLPOP pattern
36-
└── shm.py # SharedMemoryBackend: multiprocessing shared memory IPC
36+
└── local.py # LocalBackend: async-native TCP for same-machine IPC
3737
```
3838

3939
## Architecture overview
@@ -97,7 +97,7 @@ await Worker.run(task)
9797
- **Class-level attributes**: Class body statements (assignments, annotated assignments, docstrings) are extracted from AST and emitted in reconstructed class blocks. Class decorators (`@dataclass`, etc.) and metaclass keywords (`metaclass=ABCMeta`) are captured and emitted.
9898
- **Closure handling**: Multi-tier capture: repr validation → traced functions → lambdas (source extraction) → non-traced user functions (auto-registration) → constructor expressions (defaultdict/Counter/deque) → pickle fallback → warning. Traced function references become dependency edges.
9999
- **Decorator stripping**: `@trace` lines are removed from captured source so reconstructed code doesn't depend on pyfuse.
100-
- **Backend auto-detection**: `connect()` picks Redis or shared memory based on URL scheme. Falls back to `PYFUSE_BACKEND` env var.
100+
- **Backend auto-detection**: `connect()` picks Redis or local TCP backend based on URL scheme. Falls back to `PYFUSE_BACKEND` env var.
101101
- **Worker caching**: Keyed by SHA-256 of all reachable content hashes (sorted + joined). Same code from different clients = cache hit.
102102
- **Async-native I/O**: All backend methods, worker execution, result handling, pip installation, and subprocess management use `asyncio`. Sync user functions run in `loop.run_in_executor()` to avoid blocking the event loop.
103103
- **Heartbeat**: Workers send heartbeats via `asyncio.create_task`. Client-side stall detection tracks when heartbeat *values* last changed using local monotonic clock (no cross-machine timestamp comparison).
@@ -166,7 +166,7 @@ pytest # run all tests
166166
pytest tests/test_api.py # specific module
167167
```
168168

169-
15 test modules covering: API surface, AST analysis, async features (Result.result, await, .run(), .start(), .map(), gather, heartbeat, stall detection), auto-discovery (including metaclass keywords, class attributes, class decorators, `__init_subclass__`), dependency management, graph operations, integration scenarios, remote execution, runtime tracing (including closure capture of non-traced functions, lambdas, constructor expressions, pickle fallback), shared memory backend, store operations, stress tests (47 functions across 7 files), task serialization, temp venv management, and worker caching/execution.
169+
15 test modules covering: API surface, AST analysis, async features (Result.result, await, .run(), .start(), .map(), gather, heartbeat, stall detection), auto-discovery (including metaclass keywords, class attributes, class decorators, `__init_subclass__`), dependency management, graph operations, integration scenarios, local backend (async-native TCP), remote execution, runtime tracing (including closure capture of non-traced functions, lambdas, constructor expressions, pickle fallback), store operations, stress tests (47 functions across 7 files), task serialization, temp venv management, and worker caching/execution.
170170

171171
All async tests use `pytest-asyncio` with `asyncio_mode = "auto"`.
172172

docs/QUICK_START.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,14 +232,14 @@ pyfuse supports pluggable transport backends:
232232
| Backend | URL scheme | Use case |
233233
|---------|-----------|----------|
234234
| Redis | `redis://` / `rediss://` | Production, multi-machine |
235-
| Shared memory | `shm://` | Same-machine IPC, zero-copy |
235+
| Local | `local://` | Same-machine IPC (async-native TCP) |
236236

237237
```python
238238
# Redis (requires pip install redis)
239239
pyfuse.connect("redis://localhost:6379")
240240

241-
# Shared memory (no external services needed)
242-
pyfuse.connect("shm://localhost:9847")
241+
# Local (no external services needed)
242+
pyfuse.connect("local://localhost:9748")
243243
```
244244

245245
The backend can also be set via the `PYFUSE_BACKEND` environment variable:
@@ -447,7 +447,7 @@ except RemoteError as e:
447447

448448
```bash
449449
# Remote execution
450-
python -m pyfuse worker --backend shm://localhost:9847 --tmp # Terminal 1
450+
python -m pyfuse worker --backend local://localhost:9748 --tmp # Terminal 1
451451
python -m pyfuse run examples/remote_execution.py # Terminal 2
452452
```
453453

docs/TECHNICAL_OVERVIEW.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pyfuse/
4343
backends/
4444
base.py Backend ABC: async pluggable transport interface
4545
redis.py RedisBackend: redis.asyncio with RPUSH/BLPOP pattern
46-
shm.py SharedMemoryBackend: same-machine IPC via shared memory
46+
local.py LocalBackend: async-native TCP for same-machine IPC
4747
```
4848

4949
## Remote execution flow
@@ -107,15 +107,13 @@ Result notifications use Redis Pub/Sub (`PUBLISH`/`SUBSCRIBE`). Batch heartbeat
107107

108108
The `redis` package is imported lazily and is an optional dependency.
109109

110-
### SharedMemoryBackend
110+
### LocalBackend
111111

112-
Uses `multiprocessing.shared_memory` for zero-copy payload transfer and `multiprocessing.managers.BaseManager` for cross-process coordination. No external services required -- suitable for same-machine worker pools.
112+
An async-native TCP backend for same-machine IPC. A lightweight broker server built on `asyncio.start_server` handles task dispatch, result routing, and heartbeats -- no threads, no `multiprocessing`, no external services.
113113

114-
URL format: `shm://host:port?authkey=secret` (defaults: `127.0.0.1:9847`, authkey `pyfuse`).
114+
URL format: `local://host:port` (default: `127.0.0.1:9748`).
115115

116-
The backend auto-detects its role: it tries to connect as a client first; if no server is running, it starts one. Shared memory blocks are tracked and cleaned up on exit via `atexit`.
117-
118-
Since `BaseManager` is inherently synchronous, proxy RPC calls are wrapped in `asyncio.to_thread()`. The overhead is negligible for sub-millisecond IPC calls.
116+
The broker auto-starts as a subprocess on first connection (or can be started explicitly with `server=True`). All I/O is native `asyncio` -- the backend opens TCP connections to the broker and communicates via a length-prefixed JSON protocol. Streaming operations (`listen()`, `subscribe_results()`) use dedicated connections; RPC operations share a single connection protected by an `asyncio.Lock`.
119117

120118
### Custom backends
121119

examples/async_execution.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import pyfuse
1313
from pyfuse import trace
1414

15-
pyfuse.connect("redis://localhost:6379")
15+
pyfuse.connect("local://localhost:9748")
1616

1717
async def add(a: int, b: int) -> int:
1818
return a + b

pyfuse/worker/backends/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from pyfuse.worker.backends.base import Backend
2+
from pyfuse.worker.backends.local import LocalBackend
23
from pyfuse.worker.backends.redis import RedisBackend
3-
from pyfuse.worker.backends.shm import SharedMemoryBackend

pyfuse/worker/backends/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Backend(abc.ABC):
88
"""Abstract transport backend for remote task execution.
99
1010
Subclass this to implement custom transports (Redis, RabbitMQ,
11-
shared memory, etc.).
11+
TCP, etc.).
1212
"""
1313

1414
@abc.abstractmethod

0 commit comments

Comments
 (0)