Cello v1.2.4 — Critical Async Handler Fix¶
Release Date: June 14, 2026 License: MIT Python: 3.12+
Overview¶
Cello v1.2.4 fixes a critical regression introduced in v1.2.1 where all async def route handlers silently returned HTTP 500 and emitted:
Drop-in upgrade from v1.2.3. No API changes.
Root Cause¶
In v1.2.1 the server startup was changed from pyo3_asyncio::tokio::run to py.allow_threads + tokio::block_on to fix port binding on Python 3.12+. That fix was correct, but it had an unintended side effect: pyo3_asyncio was no longer initialised, so pyo3_asyncio::tokio::into_future (used in handler.rs to drive async handlers) failed silently on every request — returning 500 and dropping the unawaited coroutine.
v1.2.1 change (lib.rs):
- pyo3_asyncio::tokio::run(py, server_future) ← initialises pyo3_asyncio
+ py.allow_threads(|| rt.block_on(server_future)) ← does NOT initialise it
handler.rs (unchanged):
pyo3_asyncio::tokio::into_future(coro) ← fails: pyo3_asyncio not ready → 500
Fix¶
handler.rs Phase 2 now drives coroutines via tokio::task::spawn_blocking + asyncio.run() instead of pyo3_asyncio::tokio::into_future:
// Before (broken since v1.2.1):
let future = Python::with_gil(|py| {
pyo3_asyncio::tokio::into_future(raw_result.as_ref(py)) // ← failed silently
})?;
future.await?
// After (v1.2.4):
let (tx, rx) = tokio::sync::oneshot::channel();
let coro = raw_result;
tokio::task::spawn_blocking(move || {
let result = Python::with_gil(|py| {
let asyncio = py.import("asyncio")?;
asyncio.call_method1("run", (coro.as_ref(py),))
.map(|r| r.into_py(py))
});
let _ = tx.send(result);
});
rx.await??
The Tokio thread is released during spawn_blocking, so other requests can be served while the coroutine runs. asyncio.run() creates a fresh event loop per call (~0.1 ms overhead).
Files Changed¶
| File | Change |
|---|---|
src/handler.rs | Replace pyo3_asyncio::tokio::into_future with spawn_blocking + asyncio.run() |
tests/verify_async_client.py | New test — all HTTP methods via local echo server |
Cargo.toml, pyproject.toml, python/cello/__init__.py | Version → 1.2.4 |
Upgrade¶
No code changes required — all async def handlers that were silently returning 500 will now work correctly after upgrading.
Impact¶
Any application using async def handlers on Cello v1.2.1, v1.2.2, or v1.2.3 was affected. Sync (def) handlers were not impacted.