Multi-core async runtime for Python, backed by Tokio. Drop-in asyncio replacement.
Project description
ferro_io
Multi-core async runtime for Python, backed by Tokio. Ships with ferro_io, a
100% drop-in replacement for asyncio (every public symbol resolves; 313
tests verify it, including real asyncpg / SQLAlchemy / FastAPI integration).
# Mode 1 — library-level
import ferro_io as asyncio
asyncio.run(main())
# Mode 2 — process-wide (third-party libs also benefit)
import ferro_io
ferro_io.install()
import aiofiles # now uses ferro_io under the hood
Why
CPython's GIL serializes asyncio onto one core. ferro_io moves the scheduler
into Tokio with a multi-thread runtime, so:
- IO workloads stay at the theoretical sleep floor (no slower than stdlib).
- CPU workloads that go through
ferro_io.AsyncRuntime.map_blockingusetokio::task::spawn_blockingand bypass the GIL entirely.
Benchmarks (M-series Mac, 14 cores)
Best of 5 trials. Full matrix with uvloop in benchmarks/RESULTS.md.
| Workload | stdlib asyncio | uvloop | ferro_io | vs stdlib |
|---|---|---|---|---|
| 50 × 50ms IO sleep | 51.62 ms | 51.57 ms | 51.44 ms | ~1× (sleep floor) |
| 200 × 20ms IO sleep | 22.94 ms | 22.00 ms | 21.23 ms | ~1× (sleep floor) |
| 14 CPU chains × 5M LCG iters | 6036 ms | 5578 ms | 6.37 ms | 🔥 947× |
IO workloads: ferro_io and uvloop are both pinned at the theoretical sleep floor —
neither beats physics. CPU workloads: uvloop (libuv) stays GIL-bound like stdlib
because it optimizes the event loop, not CPU parallelism. ferro_io's map_blocking
routes through tokio::task::spawn_blocking, which releases the GIL and saturates
all worker threads.
Coverage
- Symbol-level: 119/119 asyncio public symbols resolve through
ferro_io. - Real-world programs verified:
TaskGroup, subprocess pipelines, TCP client/server with streams,to_thread,Queueproducer/consumer, gather withreturn_exceptions,wait_for,timeout,Runnerwith contextvars. - Third-party library smoke test:
aiofilesworks underferro_io.install(). - Heavyweight libraries verified against real services under
ferro_io.install():asyncpg(Cython records, prepared statements, transactions, pools),SQLAlchemyasync (greenlet sync→async bridge over asyncpg),FastAPI/Starlette(anyio, contextvars middleware,to_thread). Seetests/test_heavyweights.py. - 307 unit tests + 6 heavyweight integration tests, 0 skipped, 0 failed.
Compatibility matrix
| stdlib | uvloop | trio | ferro_io | |
|---|---|---|---|---|
Drop-in asyncio replacement |
— | ✅ | ❌ | ✅ |
asyncio symbol coverage |
100% | ~98% | 0% | 100% (119/119) |
asyncio.TaskGroup (3.11+) |
✅ | ✅ | n/a (nurseries) | ✅ |
| Multi-core CPU workloads | ❌ GIL | ❌ GIL | ❌ GIL | ✅ (via spawn_blocking) |
| Windows support | ✅ | ❌ | ✅ | ✅ |
httpx / aiohttp / websockets |
✅ | ✅ | partial | ✅ (tested) |
asyncpg (Cython records) |
✅ | ✅ | ❌ | ✅ (tested — see test_heavyweights.py) |
SQLAlchemy async (greenlets) |
✅ | ✅ | ❌ | ✅ (tested) |
FastAPI / Starlette |
✅ | ✅ | partial | ✅ (tested) |
All "tested" cells are backed by tests/test_heavyweights.py, which runs in the
integration CI job against a real Postgres service container and also executes
a matching stdlib-control run so a test-script bug can't masquerade as a ferro_io
incompatibility.
Heavyweight compat tests run in the integration CI job against a real
Postgres service container. To run them locally:
docker compose -f tests/docker-compose.yml up -d
FERRO_IO_INTEGRATION=1 pytest tests/test_heavyweights.py -v
Benchmark matrix vs uvloop
Generate the full matrix (runs each column in a subprocess so uvloop.install()
can't contaminate the others):
pip install uvloop # optional
python benchmarks/bench_matrix.py # writes benchmarks/RESULTS.md
See benchmarks/RESULTS.md for the latest numbers.
TL;DR: uvloop and ferro_io both sit at the IO sleep floor (within noise of
each other). On workload C, ferro_io's spawn_blocking path bypasses the GIL
while uvloop — like stdlib — remains serialized.
Caveat for process-wide mode
Code that does from asyncio import sleep at module top-level captures the
stdlib reference at import time, before ferro_io.install() can run. To work
around it, call ferro_io.install() as the very first statement in your entry
point — before any third-party import.
Build
python -m venv .venv && source .venv/bin/activate
pip install maturin pytest pytest-asyncio
maturin develop --release
pytest tests/ -v
python benchmarks/bench.py
Built with PyO3 0.28 + pyo3-async-runtimes 0.28 + Tokio + maturin. ABI3 wheels cover Python 3.9+.
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 Distributions
Built Distributions
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 ferro_io-0.1.0-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: ferro_io-0.1.0-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 379.5 kB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a9ab0a213d341a5c2f8484cf899b6b69fe283799a224f43a7871d9ca1cbb268
|
|
| MD5 |
eea6503567b749867ea594e03d6f81c7
|
|
| BLAKE2b-256 |
b353bf6e201e3a4678b953b1810ceddc67f73ed21b7e37fad672f4545b8b9518
|
File details
Details for the file ferro_io-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: ferro_io-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 457.9 kB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
01f1ce08410d272941acd6b5dc55d5f1d26a56b6768b69c62654dc78b1c9f066
|
|
| MD5 |
1cbcf82e51f22fe66558700f201fa921
|
|
| BLAKE2b-256 |
acb74a39704aeca70efa39cb44eb32c71cc71d99909fa7227d7e6e158641a02c
|
File details
Details for the file ferro_io-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: ferro_io-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 444.3 kB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6fadadfb5094132e390e04d57ff53df552ea56b55a8812caf352f858121a6560
|
|
| MD5 |
c0c78f3e96939d2a9b3e9f0d2ab593b5
|
|
| BLAKE2b-256 |
d64fceb06434ea1a826ea250ada6a25fdbb328083a62f5ce7f3f546bbbc461fe
|
File details
Details for the file ferro_io-0.1.0-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: ferro_io-0.1.0-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 403.8 kB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74c67c13a900684bc0da12758329ef8aa0541fee8afa04227352d169fb5ae64b
|
|
| MD5 |
44e1ed9c8fe10a49c275a40a6ae0ecff
|
|
| BLAKE2b-256 |
0c7e358372c0246ccd6485252910502bb21e77ed005d2432da6deafa8d5a507d
|
File details
Details for the file ferro_io-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: ferro_io-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 421.0 kB
- Tags: CPython 3.9+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bcee7016747d32e43e5e75e77fc90ea5914e2cdf0e952f0c677644b45f9b420
|
|
| MD5 |
58a6f5c2b1d99e6693508b7056f49d9b
|
|
| BLAKE2b-256 |
9e4ae77f79ed323c8f8526be29d56d856b7852d605101af66e2f75fd3c3c044c
|