Skip to main content

pyo3/
coroutine.rs

1//! Python coroutine implementation, used notably when wrapping `async fn`
2//! with `#[pyfunction]`/`#[pymethods]`.
3use alloc::sync::Arc;
4use core::{
5    future::Future,
6    panic,
7    pin::Pin,
8    task::{Context, Poll, Waker},
9};
10
11use pyo3_macros::{pyclass, pymethods};
12
13use crate::platform::prelude::*;
14use crate::{
15    coroutine::{cancel::ThrowCallback, waker::AsyncioWaker},
16    exceptions::{PyAttributeError, PyRuntimeError, PyStopIteration},
17    panic::PanicException,
18    types::{string::PyStringMethods, PyIterator, PyString},
19    Bound, Py, PyAny, PyErr, PyResult, Python,
20};
21
22pub(crate) mod cancel;
23mod waker;
24
25pub use cancel::CancelHandle;
26
27const COROUTINE_REUSED_ERROR: &str = "cannot reuse already awaited coroutine";
28
29/// Python coroutine wrapping a [`Future`].
30#[pyclass(crate = "crate")]
31pub struct Coroutine {
32    name: Option<Py<PyString>>,
33    qualname_prefix: Option<&'static str>,
34    throw_callback: Option<ThrowCallback>,
35    #[expect(clippy::type_complexity)]
36    future: Option<Pin<Box<dyn Future<Output = PyResult<Py<PyAny>>> + Send>>>,
37    waker: Option<Arc<AsyncioWaker>>,
38}
39
40// Safety: `Coroutine` is allowed to be `Sync` even though the future is not,
41// because the future is polled with `&mut self` receiver
42unsafe impl Sync for Coroutine {}
43
44impl Coroutine {
45    ///  Wrap a future into a Python coroutine.
46    ///
47    /// Coroutine `send` polls the wrapped future, ignoring the value passed
48    /// (should always be `None` anyway).
49    ///
50    /// `Coroutine `throw` drop the wrapped future and reraise the exception passed
51    pub(crate) fn new<'py, F>(
52        name: Option<Bound<'py, PyString>>,
53        qualname_prefix: Option<&'static str>,
54        throw_callback: Option<ThrowCallback>,
55        future: F,
56    ) -> Self
57    where
58        F: Future<Output = Result<Py<PyAny>, PyErr>> + Send + 'static,
59    {
60        Self {
61            name: name.map(Bound::unbind),
62            qualname_prefix,
63            throw_callback,
64            future: Some(Box::pin(future)),
65            waker: None,
66        }
67    }
68
69    fn poll(&mut self, py: Python<'_>, throw: Option<Py<PyAny>>) -> PyResult<Py<PyAny>> {
70        // raise if the coroutine has already been run to completion
71        let future_rs = match self.future {
72            Some(ref mut fut) => fut,
73            None => return Err(PyRuntimeError::new_err(COROUTINE_REUSED_ERROR)),
74        };
75        // reraise thrown exception it
76        match (throw, &self.throw_callback) {
77            (Some(exc), Some(cb)) => cb.throw(exc),
78            (Some(exc), None) => {
79                self.close();
80                return Err(PyErr::from_value(exc.into_bound(py)));
81            }
82            (None, _) => {}
83        }
84        // create a new waker, or try to reset it in place
85        if let Some(waker) = self.waker.as_mut().and_then(Arc::get_mut) {
86            waker.reset();
87        } else {
88            self.waker = Some(Arc::new(AsyncioWaker::new()));
89        }
90        let waker = Waker::from(self.waker.clone().unwrap());
91        // poll the Rust future and forward its results if ready
92        // polling is UnwindSafe because the future is dropped in case of panic
93        let poll = || future_rs.as_mut().poll(&mut Context::from_waker(&waker));
94        match std::panic::catch_unwind(panic::AssertUnwindSafe(poll)) {
95            Ok(Poll::Ready(res)) => {
96                self.close();
97                return Err(PyStopIteration::new_err((res?,)));
98            }
99            Err(err) => {
100                self.close();
101                return Err(PanicException::from_panic_payload(err));
102            }
103            _ => {}
104        }
105        // otherwise, initialize the waker `asyncio.Future`
106        if let Some(future) = self.waker.as_ref().unwrap().initialize_future(py)? {
107            // `asyncio.Future` must be awaited; fortunately, it implements `__iter__ = __await__`
108            // and will yield itself if its result has not been set in polling above
109            if let Some(future) = PyIterator::from_object(future).unwrap().next() {
110                // future has not been leaked into Python for now, and Rust code can only call
111                // `set_result(None)` in `Wake` implementation, so it's safe to unwrap
112                return Ok(future.unwrap().into());
113            }
114        }
115        // if waker has been waken during future polling, this is roughly equivalent to
116        // `await asyncio.sleep(0)`, so just yield `None`.
117        Ok(py.None())
118    }
119}
120
121#[pymethods(crate = "crate")]
122impl Coroutine {
123    #[getter]
124    fn __name__(&self, py: Python<'_>) -> PyResult<Py<PyString>> {
125        match &self.name {
126            Some(name) => Ok(name.clone_ref(py)),
127            None => Err(PyAttributeError::new_err("__name__")),
128        }
129    }
130
131    #[getter]
132    fn __qualname__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
133        match (&self.name, &self.qualname_prefix) {
134            (Some(name), Some(prefix)) => Ok(PyString::new(
135                py,
136                &format!("{}.{}", prefix, name.bind(py).to_cow()?),
137            )),
138            (Some(name), None) => Ok(name.bind(py).clone()),
139            (None, _) => Err(PyAttributeError::new_err("__qualname__")),
140        }
141    }
142
143    fn send(&mut self, py: Python<'_>, _value: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
144        self.poll(py, None)
145    }
146
147    fn throw(&mut self, py: Python<'_>, exc: Py<PyAny>) -> PyResult<Py<PyAny>> {
148        self.poll(py, Some(exc))
149    }
150
151    fn close(&mut self) {
152        // the Rust future is dropped, and the field set to `None`
153        // to indicate the coroutine has been run to completion
154        drop(self.future.take());
155    }
156
157    fn __await__(self_: Py<Self>) -> Py<Self> {
158        self_
159    }
160
161    fn __next__(&mut self, py: Python<'_>) -> PyResult<Py<PyAny>> {
162        self.poll(py, None)
163    }
164}