Process-external local singleton via loopback daemon
Project description
loopback-singleton
loopback-singleton is a lightweight Python package that gives multiple local processes access to a single shared object instance hosted in a background daemon on 127.0.0.1.
It is useful when you want one process-external object (cache, counter, coordinator, adapter, etc.) and you want all local workers to call into that object without standing up a full RPC system.
Current status (v0.1.1)
Current release: 0.1.1.
What works today
- Local singleton daemon auto-start on first use.
- Concurrent startup coordination with file locking to reduce duplicate daemons.
- Authenticated handshake (shared token in runtime dir) between client and daemon.
- Sequential method execution on the singleton object (single executor queue).
- Idle TTL auto-shutdown for daemon cleanup.
- Recovery from stale or corrupted runtime metadata.
- Cross-platform runtime location strategy (Windows + POSIX fallback behavior).
Installation
pip install loopback-singleton
For local development:
pip install -e .[dev]
Quickstart
Create a module with a factory target (class or callable):
# mypkg/services.py
class Counter:
def __init__(self):
self.value = 0
def inc(self) -> int:
self.value += 1
return self.value
def ping(self) -> str:
return "pong"
Use local_singleton from any process:
from loopback_singleton import local_singleton
svc = local_singleton(
name="my-counter",
factory="mypkg.services:Counter",
idle_ttl=2.0,
serializer="pickle",
)
with svc.proxy() as obj:
print(obj.ping())
print(obj.inc())
API overview
local_singleton(
name: str,
factory: str,
*,
scope: str = "user",
idle_ttl: float = 2.0,
serializer: str = "pickle",
connect_timeout: float = 0.5,
start_timeout: float = 3.0,
)
name: singleton identity (shared runtime namespace).factory: import string in form"module:callable_or_class".scope: currently only"user"is implemented.idle_ttl: daemon stops after this many seconds with zero active connections.serializer: currently only"pickle"is implemented.connect_timeout,start_timeout: socket/startup tuning.
svc.proxy() returns a dynamic proxy where method calls are forwarded to the daemon.
Additional lifecycle APIs are available on LocalSingletonService:
svc.ensure_started()
info = svc.ping()
svc.shutdown()
How it works
- Client computes runtime paths for the singleton name.
- Client attempts connection using runtime metadata.
- If missing/failing, it takes a file lock, cleans stale metadata, and spawns daemon.
- Daemon binds ephemeral loopback TCP port, writes runtime metadata, and serves requests.
- Each
CALLrequest is executed sequentially against one in-memory object instance.
Lifecycle and robustness scenarios
Scenario A — Oversized payload fails fast, daemon remains healthy
svc = local_singleton("svc", factory="mypkg.m:MyObj")
with svc.proxy() as p:
p.process_bytes(b"x" * (100 * 1024 * 1024)) # 100MB
Large frames are capped (16 MiB by default). Oversized frames are rejected with a clear protocol/connection error, and the daemon keeps serving other clients.
Scenario B — Idle shutdown survives stuck clients
Daemon client handlers use bounded socket read timeouts, so an idle/stuck TCP client cannot block daemon shutdown forever.
Scenario C — Private methods are denied by daemon
svc = local_singleton("svc", factory="mypkg.m:MyObj")
with svc.proxy() as p:
p._reset_state()
Even if a client bypasses proxy-side checks, daemon-side policy rejects CALL for methods starting with _.
Scenario D — Warm-up without creating a proxy
svc = local_singleton("svc", factory="mypkg.m:MyObj")
svc.ensure_started()
This starts (or verifies) the daemon and completes handshake without creating a Proxy.
Scenario E — Health check and deterministic shutdown
svc = local_singleton("svc", factory="mypkg.m:MyObj")
info = svc.ping()
svc.shutdown()
ping() returns daemon metadata (pid, active, and protocol/runtime info). shutdown() requests daemon exit and cleans runtime metadata.
Error model
Main exception classes exported by the package:
LoopbackSingletonError(base)DaemonConnectionErrorConnectionFailedErrorHandshakeError
ProtocolError(invalid or oversized transport frames/messages)RemoteError(remote traceback payload)
Security notes (important)
This MVP uses pickle for transport serialization. pickle is not safe for untrusted input and can execute arbitrary code.
Use this package only in trusted local environments for now.
Runtime files and cleanup
Runtime files are created under:
- Windows:
%LOCALAPPDATA%/loopback-singleton/<name>/ - Linux/macOS:
$XDG_RUNTIME_DIR/loopback-singleton/<name>/ - POSIX fallback:
~/.cache/loopback-singleton/<name>/
If startup repeatedly fails due to stale metadata, stop clients and remove the directory for that singleton name.
Known limitations (MVP)
- Factory must be an import string (
"module:callable_or_class"). - No identity transparency for proxies (
isinstance(proxy, MyType)is not preserved). - No magic-method forwarding (
__len__, operators, iteration, etc.). - Only
scope="user"implemented. - Only
serializer="pickle"implemented (msgpackplaceholder exists but not implemented). - Transport is loopback TCP only.
Development
Run checks and tests:
ruff check .
pytest -q
Build package:
python -m build
Future work
Planned directions for post-MVP releases:
-
Safer serialization options
- Implement
msgpackserializer path and typed payload envelopes. - Add optional schema validation for RPC payloads.
- Implement
-
Richer proxy semantics
- Support selected dunder/magic methods.
- Improve error transport with structured remote exception metadata.
-
Lifecycle and observability
- Add daemon health/metrics endpoint(s) and lightweight tracing hooks.
- Expose explicit client APIs for graceful shutdown and restart policies.
-
Scope and deployment flexibility
- Add additional scope modes beyond per-user.
- Evaluate optional Unix domain socket transport on POSIX.
-
Robustness and compatibility
- Protocol version negotiation for rolling upgrades.
- Expanded stress/regression suite for high-concurrency scenarios.
-
Security hardening
- Optional mutual-auth improvements and stricter runtime file hardening.
- Guidance and tooling for locked-down local deployments.
Contributions and issue reports are welcome at:
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file loopback_singleton-0.2.1.tar.gz.
File metadata
- Download URL: loopback_singleton-0.2.1.tar.gz
- Upload date:
- Size: 20.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8fbca278ecfd6c27c51d952ace508e459b0ce5a75314bfada7da3d190ab4dd1
|
|
| MD5 |
371bde5fe0cbab80e8c2e30dee8da192
|
|
| BLAKE2b-256 |
1523496ac9cfa84fb174009198e678a263cf80c52b2c467ab152640d0eea8131
|
Provenance
The following attestation bundles were made for loopback_singleton-0.2.1.tar.gz:
Publisher:
publish.yml on TovarnovM/loopback_singleton
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loopback_singleton-0.2.1.tar.gz -
Subject digest:
b8fbca278ecfd6c27c51d952ace508e459b0ce5a75314bfada7da3d190ab4dd1 - Sigstore transparency entry: 938100383
- Sigstore integration time:
-
Permalink:
TovarnovM/loopback_singleton@ff3f57148f994a7220440496dc1ac775aa5f97a6 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/TovarnovM
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ff3f57148f994a7220440496dc1ac775aa5f97a6 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file loopback_singleton-0.2.1-py3-none-any.whl.
File metadata
- Download URL: loopback_singleton-0.2.1-py3-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f92bf90f7d42e7c8c44fd871c1b3cf6c6f83636f9f5d7dfe63eb91cc449e3d8e
|
|
| MD5 |
8e5c858e006316407300924b6e1a53e7
|
|
| BLAKE2b-256 |
b3cd99fdea8d263edc08f3ef708bab939f9296266e3cf3ca0c991c1a02c80c1a
|
Provenance
The following attestation bundles were made for loopback_singleton-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on TovarnovM/loopback_singleton
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loopback_singleton-0.2.1-py3-none-any.whl -
Subject digest:
f92bf90f7d42e7c8c44fd871c1b3cf6c6f83636f9f5d7dfe63eb91cc449e3d8e - Sigstore transparency entry: 938100407
- Sigstore integration time:
-
Permalink:
TovarnovM/loopback_singleton@ff3f57148f994a7220440496dc1ac775aa5f97a6 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/TovarnovM
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ff3f57148f994a7220440496dc1ac775aa5f97a6 -
Trigger Event:
workflow_dispatch
-
Statement type: