PyO3/pyo3 · error
unexpected error in coroutine waker
Error message
unexpected error in coroutine waker
What it means
This panic fires inside a Rust `#[pyclass]` coroutine's `wake_by_ref` when the stored loop-and-future slot reports an error while setting the wake result, i.e. the waker's internal `Once`-style state was initialized to a value that cannot produce a `set_result`. It signals a corrupted or never-properly-initialized coroutine waker rather than a user-facing Python error. The `get_or_init(..., || None)` path stored `None` on first use, so this branch should only run when a loop/future pair actually exists.
Source
Thrown at src/coroutine/waker.rs:48
py: Python<'py>,
) -> PyResult<Option<&Bound<'py, PyAny>>> {
let init = || LoopAndFuture::new(py).map(Some);
let loop_and_future = self.0.get_or_try_init(py, init)?.as_ref();
Ok(loop_and_future.map(|LoopAndFuture { future, .. }| future.bind(py)))
}
}
impl Wake for AsyncioWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref()
}
fn wake_by_ref(self: &Arc<Self>) {
Python::attach(|py| {
if let Some(loop_and_future) = self.0.get_or_init(py, || None) {
loop_and_future
.set_result(py)
.expect("unexpected error in coroutine waker");
}
});
}
}
struct LoopAndFuture {
event_loop: Py<PyAny>,
future: Py<PyAny>,
}
impl LoopAndFuture {
fn new(py: Python<'_>) -> PyResult<Self> {
static GET_RUNNING_LOOP: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
let import = || -> PyResult<_> {
let module = py.import("asyncio")?;
Ok(module.getattr("get_running_loop")?.into())
};
let event_loop = GET_RUNNING_LOOP.get_or_try_init(py, import)?.call0(py)?;View on GitHub (pinned to ac9b6899d3)
Solutions
- Ensure the coroutine future is not dropped or resolved before its waker is invoked (do not cancel the asyncio task while the Rust side still holds the waker)
- Keep the event loop alive for the duration of the coroutine; do not close the loop before awaiting finishes
- Upgrade pyo3 — waker state management has been reworked in newer releases
- Reproduce with a minimal example and report to PyO3 if the panic occurs in normal awaiting
Example fix
// before let task = asyncio.create_task(coro); task.cancel(); await asyncio.sleep(0) // after await task # let the coroutine finish before dropping/awaking its waker
Defensive patterns
Strategy: try-catch
Validate before calling
# Python: ensure the asyncio task/loop is alive before interacting assert not task.done(), 'coroutine task already finished; do not wake its waker'
Type guard
def is_task_alive(task) -> bool:
return task is not None and not task.done() and not task.cancelled() Try / catch
try:
result = await rust_coroutine(...)
except asyncio.CancelledError:
# do not touch the waker afterwards
pass Prevention
- Await tasks to completion before cancelling or dropping them
- Keep the event loop open until all Rust coroutines resolve
- Upgrade pyo3 to get fixed waker state handling
- Avoid holding Rust wakers beyond the future's completion
When it happens
Trigger: Waking an already-completed or dropped coroutine task; the future slot was consumed/cleared between scheduling and wake; calling `wake_by_ref` after the event loop future was already resolved.
Common situations: Long-running async Rust functions wrapped with pyo3's coroutine support; cancelling Python `asyncio` tasks whose Rust waker still fires; mixed sync/async shutdown ordering at interpreter teardown.
Related errors
- Neither abi3 or abi3t features are enabled
- Cannot target an abi3t version below {MINIMUM_SUPPORTED_VERS
- failed to run the Python interpreter at {}: {}
- Python script failed
- cannot set a minimum Python version {} higher than the inter
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/93467f2e53ab43a4.
Report an issue: GitHub.