Deterministic simulation testing for Python asyncio: seeded scheduling, virtual time, replayable failures
Project description
simloop
Deterministic simulation testing for Python asyncio: seeded scheduling, virtual time, and a simulated network with fault injection. Any failure simloop finds replays exactly from a seed.
Rust has madsim and turmoil; FoundationDB and TigerBeetle made the technique famous. simloop brings it to asyncio: your real, unmodified networking code runs on a simulated loop where time is virtual, every scheduling decision is seeded, and the network loses, delays, duplicates, and partitions traffic on command.
Install
pip install simloop
Python 3.12+. No runtime dependencies. The pytest plugin ships in the same package and activates automatically.
Find a bug, then replay it
import asyncio
from simloop import sim_test
@sim_test(seeds=200)
async def test_replies_survive_a_lossy_network():
loop = asyncio.get_running_loop()
loop.net.set_defaults(latency=(0.001, 0.050))
async def serve():
async def handle(reader, writer):
writer.write((await reader.readline()).upper())
writer.close()
server = await asyncio.start_server(handle, port=8080)
async with server:
await server.serve_forever()
loop.net.host("server").create_task(serve())
await asyncio.sleep(1.0)
async with asyncio.timeout(30.0):
reader, writer = await asyncio.open_connection("server", 8080)
writer.write(b"hello\n")
assert await reader.readline() == b"HELLO\n"
@sim_test(seeds=200) runs the test under 200 seeds, each on a fresh
simulated loop, and stops at the first failure:
simloop: failed at seed 41 (41 seeds passed first)
replay: pytest 'tests/test_echo.py::test_replies_survive_a_lossy_network' --simloop-replay=41
last 20 trace events:
[t=1.0312] net seq=812 send driver>server
...
pending tasks by host:
server Task 'Task-2' awaiting serve_forever at ...
The replay command reproduces the failure exactly — same scheduling decisions, same fault decisions, same trace. In CI, crank the search without touching code:
pytest --simloop-seeds=1000
What the simulation gives you
- Seeded scheduling — the ready queue's execution order comes from a per-run PRNG; a seed pins the entire interleaving.
- Virtual time —
asyncio.sleep(300)costs nothing; timeouts fire in simulated seconds. - A simulated network —
open_connection/start_server/ datagram endpoints run over an in-memory packet core with per-link latency, drop, and duplication, plus partitions and host crashes:
net = loop.net
net.set_defaults(latency=(0.001, 0.010), drop=0.05)
net.partition({"node1"}, {"node2", "node3"}) # silent blackhole
loop.call_later(5.0, net.heal) # heals in virtual time
net.crash("node2") # no reset, just silence
- Replayable traces — every scheduling and fault decision lands in an append-only trace whose hash proves a replay is exact.
Proving it: the jobqueue demo
examples/jobqueue/ is a complete distributed system — an exactly-once
job scheduler (leases, fencing tokens, idempotency keys, backoff, and
dead-lettering) written in plain asyncio — tested end to end with
simloop. Its suite runs hundreds of seeds of partitions, crashes, and
poison jobs, and shows that removing any load-bearing safeguard produces
a violation the explorer finds and replays from a seed. The bug table
lives in examples/jobqueue/README.md.
Honest limits
Code that goes through the event-loop API is supported; code that
bypasses it is fenced: threads and executors, raw sockets, subprocesses,
signals, TLS, and getaddrinfo raise SimulationFenceError rather than
silently breaking determinism. Write-side flow control is not simulated.
The full contract is in docs/supported-api.md.
License
MIT
Project details
Release history Release notifications | RSS feed
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 simloop-0.1.0.tar.gz.
File metadata
- Download URL: simloop-0.1.0.tar.gz
- Upload date:
- Size: 19.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
942e7c9db83dc39b7aabe3628503f318d896127f494d4fd53ed8530fbd835a12
|
|
| MD5 |
8e12e38e69407daaa7126010b7a5dac0
|
|
| BLAKE2b-256 |
47468fae4b95277542512fd5577c1e3def79e957792d20bb6c38fa7e75238c08
|
Provenance
The following attestation bundles were made for simloop-0.1.0.tar.gz:
Publisher:
release.yml on dhruvl/simloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
simloop-0.1.0.tar.gz -
Subject digest:
942e7c9db83dc39b7aabe3628503f318d896127f494d4fd53ed8530fbd835a12 - Sigstore transparency entry: 2194943734
- Sigstore integration time:
-
Permalink:
dhruvl/simloop@1969ef42da83fc3dd215bfb386a6353d4c5f077b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/dhruvl
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1969ef42da83fc3dd215bfb386a6353d4c5f077b -
Trigger Event:
push
-
Statement type:
File details
Details for the file simloop-0.1.0-py3-none-any.whl.
File metadata
- Download URL: simloop-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bb4d3021ff524543ff052008ff5a1a40444455e3d900cb280416e78f43338f7
|
|
| MD5 |
a4d66fdaeac94eabc8e5a5cc17abbb85
|
|
| BLAKE2b-256 |
c3dae9e5b50907f2608e1a4e779a8acf225fed5fbee03507f4ff0471902a5f4d
|
Provenance
The following attestation bundles were made for simloop-0.1.0-py3-none-any.whl:
Publisher:
release.yml on dhruvl/simloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
simloop-0.1.0-py3-none-any.whl -
Subject digest:
5bb4d3021ff524543ff052008ff5a1a40444455e3d900cb280416e78f43338f7 - Sigstore transparency entry: 2194943749
- Sigstore integration time:
-
Permalink:
dhruvl/simloop@1969ef42da83fc3dd215bfb386a6353d4c5f077b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/dhruvl
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1969ef42da83fc3dd215bfb386a6353d4c5f077b -
Trigger Event:
push
-
Statement type: