Skip to content

Commit 2964965

Browse files
author
Andrew J Westlake
committed
Added missing local_cancellable_* variants to tokio and async-std runtimes and added new tests for them
1 parent b56f111 commit 2964965

File tree

4 files changed

+354
-4
lines changed

4 files changed

+354
-4
lines changed

pytests/test_async_std_asyncio.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,52 @@ async fn test_cancel() -> PyResult<()> {
217217
Ok(())
218218
}
219219

220+
#[pyo3_asyncio::async_std::test]
221+
fn test_local_cancel(event_loop: PyObject) -> PyResult<()> {
222+
async_std::task::block_on(pyo3_asyncio::async_std::scope_local(event_loop, async {
223+
let completed = Arc::new(Mutex::new(false));
224+
225+
let py_future = Python::with_gil(|py| -> PyResult<PyObject> {
226+
let completed = Arc::clone(&completed);
227+
Ok(
228+
pyo3_asyncio::async_std::cancellable_future_into_py(py, async move {
229+
async_std::task::sleep(Duration::from_secs(1)).await;
230+
*completed.lock().unwrap() = true;
231+
232+
Ok(Python::with_gil(|py| py.None()))
233+
})?
234+
.into(),
235+
)
236+
})?;
237+
238+
if let Err(e) = Python::with_gil(|py| -> PyResult<_> {
239+
py_future.as_ref(py).call_method0("cancel")?;
240+
pyo3_asyncio::async_std::into_future(py_future.as_ref(py))
241+
})?
242+
.await
243+
{
244+
Python::with_gil(|py| -> PyResult<()> {
245+
assert!(py
246+
.import("asyncio")?
247+
.getattr("CancelledError")?
248+
.downcast::<PyType>()
249+
.unwrap()
250+
.is_instance(e.pvalue(py))?);
251+
Ok(())
252+
})?;
253+
} else {
254+
panic!("expected CancelledError");
255+
}
256+
257+
async_std::task::sleep(Duration::from_secs(1)).await;
258+
if *completed.lock().unwrap() {
259+
panic!("future still completed")
260+
}
261+
262+
Ok(())
263+
}))
264+
}
265+
220266
/// This module is implemented in Rust.
221267
#[pymodule]
222268
fn test_mod(_py: Python, m: &PyModule) -> PyResult<()> {

pytests/tokio_asyncio/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,54 @@ async fn test_cancel() -> PyResult<()> {
214214
Ok(())
215215
}
216216

217+
#[pyo3_asyncio::tokio::test]
218+
fn test_local_cancel(event_loop: PyObject) -> PyResult<()> {
219+
tokio::task::LocalSet::new().block_on(
220+
pyo3_asyncio::tokio::get_runtime(),
221+
pyo3_asyncio::tokio::scope_local(event_loop, async {
222+
let completed = Arc::new(Mutex::new(false));
223+
let py_future = Python::with_gil(|py| -> PyResult<PyObject> {
224+
let completed = Arc::clone(&completed);
225+
Ok(
226+
pyo3_asyncio::tokio::local_cancellable_future_into_py(py, async move {
227+
tokio::time::sleep(Duration::from_secs(1)).await;
228+
*completed.lock().unwrap() = true;
229+
Ok(Python::with_gil(|py| py.None()))
230+
})?
231+
.into(),
232+
)
233+
})?;
234+
235+
if let Err(e) = Python::with_gil(|py| -> PyResult<_> {
236+
py_future.as_ref(py).call_method0("cancel")?;
237+
pyo3_asyncio::tokio::into_future(py_future.as_ref(py))
238+
})?
239+
.await
240+
{
241+
Python::with_gil(|py| -> PyResult<()> {
242+
assert!(py
243+
.import("asyncio")?
244+
.getattr("CancelledError")?
245+
.downcast::<PyType>()
246+
.unwrap()
247+
.is_instance(e.pvalue(py))?);
248+
Ok(())
249+
})?;
250+
} else {
251+
panic!("expected CancelledError");
252+
}
253+
254+
tokio::time::sleep(Duration::from_secs(1)).await;
255+
256+
if *completed.lock().unwrap() {
257+
panic!("future still completed")
258+
}
259+
260+
Ok(())
261+
}),
262+
)
263+
}
264+
217265
/// This module is implemented in Rust.
218266
#[pymodule]
219267
fn test_mod(_py: Python, m: &PyModule) -> PyResult<()> {

src/async_std.rs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ where
278278
/// Unlike [`future_into_py_with_loop`], this function will stop the Rust future from running when
279279
/// the `asyncio.Future` is cancelled from Python.
280280
///
281-
/// __This function will be deprecated in favor of [`future_into_py_with_loop`] in `v0.15` because
282-
/// it will become the default behaviour. In `v0.15`, any calls to this function can be seamlessly
281+
/// __This function will be deprecated in favor of [`future_into_py_with_loop`] in `v0.15` because
282+
/// it will become the default behaviour. In `v0.15`, any calls to this function can be seamlessly
283283
/// replaced with [`future_into_py_with_loop`].__
284284
///
285285
/// # Arguments
@@ -348,8 +348,8 @@ where
348348
/// Unlike [`future_into_py`], this function will stop the Rust future from running when
349349
/// the `asyncio.Future` is cancelled from Python.
350350
///
351-
/// __This function will be deprecated in favor of [`future_into_py`] in `v0.15` because
352-
/// it will become the default behaviour. In `v0.15`, any calls to this function can be seamlessly
351+
/// __This function will be deprecated in favor of [`future_into_py`] in `v0.15` because
352+
/// it will become the default behaviour. In `v0.15`, any calls to this function can be seamlessly
353353
/// replaced with [`future_into_py`].__
354354
///
355355
/// # Arguments
@@ -428,6 +428,61 @@ where
428428
generic::local_future_into_py_with_loop::<AsyncStdRuntime, _>(event_loop, fut)
429429
}
430430

431+
/// Convert a `!Send` Rust Future into a Python awaitable
432+
///
433+
/// Unlike [`local_future_into_py_with_loop`], this function will stop the Rust future from running when
434+
/// the `asyncio.Future` is cancelled from Python.
435+
///
436+
/// __This function will be deprecated in favor of [`local_future_into_py_with_loop`] in `v0.15` because
437+
/// it will become the default behaviour. In `v0.15`, any calls to this function can be seamlessly
438+
/// replaced with [`local_future_into_py_with_loop`].__
439+
///
440+
/// # Arguments
441+
/// * `event_loop` - The Python event loop that the awaitable should be attached to
442+
/// * `fut` - The Rust future to be converted
443+
///
444+
/// # Examples
445+
///
446+
/// ```
447+
/// use std::{rc::Rc, time::Duration};
448+
///
449+
/// use pyo3::prelude::*;
450+
///
451+
/// /// Awaitable non-send sleep function
452+
/// #[pyfunction]
453+
/// fn sleep_for(py: Python, secs: u64) -> PyResult<&PyAny> {
454+
/// // Rc is non-send so it cannot be passed into pyo3_asyncio::async_std::future_into_py
455+
/// let secs = Rc::new(secs);
456+
/// Ok(pyo3_asyncio::async_std::local_cancellable_future_into_py_with_loop(
457+
/// pyo3_asyncio::async_std::get_current_loop(py)?,
458+
/// async move {
459+
/// async_std::task::sleep(Duration::from_secs(*secs)).await;
460+
/// Python::with_gil(|py| Ok(py.None()))
461+
/// }
462+
/// )?.into())
463+
/// }
464+
///
465+
/// # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
466+
/// #[pyo3_asyncio::async_std::main]
467+
/// async fn main() -> PyResult<()> {
468+
/// Python::with_gil(|py| {
469+
/// let py_future = sleep_for(py, 1)?;
470+
/// pyo3_asyncio::async_std::into_future(py_future)
471+
/// })?
472+
/// .await?;
473+
///
474+
/// Ok(())
475+
/// }
476+
/// # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))]
477+
/// # fn main() {}
478+
/// ```
479+
pub fn local_cancellable_future_into_py_with_loop<F>(event_loop: &PyAny, fut: F) -> PyResult<&PyAny>
480+
where
481+
F: Future<Output = PyResult<PyObject>> + 'static,
482+
{
483+
generic::local_cancellable_future_into_py_with_loop::<AsyncStdRuntime, _>(event_loop, fut)
484+
}
485+
431486
/// Convert a `!Send` Rust Future into a Python awaitable
432487
///
433488
/// # Arguments
@@ -473,6 +528,58 @@ where
473528
generic::local_future_into_py::<AsyncStdRuntime, _>(py, fut)
474529
}
475530

531+
/// Convert a `!Send` Rust Future into a Python awaitable
532+
///
533+
/// Unlike [`local_future_into_py`], this function will stop the Rust future from running when
534+
/// the `asyncio.Future` is cancelled from Python.
535+
///
536+
/// __This function will be deprecated in favor of [`local_future_into_py`] in `v0.15` because
537+
/// it will become the default behaviour. In `v0.15`, any calls to this function can be seamlessly
538+
/// replaced with [`local_future_into_py`].__
539+
///
540+
/// # Arguments
541+
/// * `py` - The current PyO3 GIL guard
542+
/// * `fut` - The Rust future to be converted
543+
///
544+
/// # Examples
545+
///
546+
/// ```
547+
/// use std::{rc::Rc, time::Duration};
548+
///
549+
/// use pyo3::prelude::*;
550+
///
551+
/// /// Awaitable non-send sleep function
552+
/// #[pyfunction]
553+
/// fn sleep_for(py: Python, secs: u64) -> PyResult<&PyAny> {
554+
/// // Rc is non-send so it cannot be passed into pyo3_asyncio::async_std::future_into_py
555+
/// let secs = Rc::new(secs);
556+
/// pyo3_asyncio::async_std::local_cancellable_future_into_py(py, async move {
557+
/// async_std::task::sleep(Duration::from_secs(*secs)).await;
558+
/// Python::with_gil(|py| Ok(py.None()))
559+
/// })
560+
/// }
561+
///
562+
/// # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
563+
/// #[pyo3_asyncio::async_std::main]
564+
/// async fn main() -> PyResult<()> {
565+
/// Python::with_gil(|py| {
566+
/// let py_future = sleep_for(py, 1)?;
567+
/// pyo3_asyncio::async_std::into_future(py_future)
568+
/// })?
569+
/// .await?;
570+
///
571+
/// Ok(())
572+
/// }
573+
/// # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))]
574+
/// # fn main() {}
575+
/// ```
576+
pub fn local_cancellable_future_into_py<F>(py: Python, fut: F) -> PyResult<&PyAny>
577+
where
578+
F: Future<Output = PyResult<PyObject>> + 'static,
579+
{
580+
generic::local_cancellable_future_into_py::<AsyncStdRuntime, _>(py, fut)
581+
}
582+
476583
/// Convert a Python `awaitable` into a Rust Future
477584
///
478585
/// This function converts the `awaitable` into a Python Task using `run_coroutine_threadsafe`. A

0 commit comments

Comments
 (0)