Skip to content

Commit 1f231dc

Browse files
authored
Merge pull request #48 from awestlake87/better-cancellation
Added a 'cancellable' set of conversions for proper cancellation from Python
2 parents 5b1d1c4 + a4a24b7 commit 1f231dc

File tree

8 files changed

+1069
-18
lines changed

8 files changed

+1069
-18
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ jobs:
9494
- name: Build (no features)
9595
run: cargo build --no-default-features --verbose --target ${{ matrix.platform.rust-target }}
9696

97-
# Omit async-std-runtime feature for MSRV 1.45.0
97+
# Omit async-std-runtime and testing features from MSRV 1.45.0 (See README for details)
9898
- if: matrix.rust == '1.45.0'
9999
name: Prepare 1.45.0 features
100-
run: echo features=testing,attributes,tokio-runtime >> $GITHUB_ENV
100+
run: echo features=attributes,tokio-runtime >> $GITHUB_ENV
101101

102102
# Use all features for MSRV 1.46.0 and above
103103
- if: matrix.rust != '1.45.0'

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ clap = { version = "2.33", optional = true }
106106
futures = "0.3"
107107
inventory = "0.1"
108108
once_cell = "1.5"
109+
pin-project-lite = "0.2"
109110
pyo3 = "0.14"
110111
pyo3-asyncio-macros = { path = "pyo3-asyncio-macros", version = "=0.14.0", optional = true }
111112

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ __Before you get started, I personally recommend taking a look at [Event Loop Re
622622
This library can give spurious failures during finalization prior to PyO3 release `v0.13.2`. Make sure your PyO3 dependency is up-to-date!
623623

624624
## MSRV
625-
Currently the MSRV for this library is 1.46.0, _but_ if you don't need to use the `async-std-runtime`
626-
feature, you can use rust 1.45.0.
627-
> `async-std` depends on `socket2` which fails to compile under 1.45.0.
625+
Currently the MSRV for this library is 1.46.0, _but_ if you don't need to use the `async-std-runtime`
626+
or `testing` features, you can still use rust 1.45.0.
627+
- `async-std` depends on `socket2` which fails to compile under 1.45.0.
628+
- The `testing` feature indirectly relies on `bitflags` through `clap`, which is now locked in at MSRV 1.46.0

pytests/test_async_std_asyncio.rs

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
mod common;
22

3-
use std::{rc::Rc, time::Duration};
3+
use std::{
4+
rc::Rc,
5+
sync::{Arc, Mutex},
6+
time::Duration,
7+
};
48

59
use async_std::task;
610
use pyo3::{
@@ -171,12 +175,19 @@ async fn test_local_future_into_py() -> PyResult<()> {
171175

172176
#[pyo3_asyncio::async_std::test]
173177
async fn test_cancel() -> PyResult<()> {
178+
let completed = Arc::new(Mutex::new(false));
179+
174180
let py_future = Python::with_gil(|py| -> PyResult<PyObject> {
175-
Ok(pyo3_asyncio::async_std::future_into_py(py, async {
176-
async_std::task::sleep(Duration::from_secs(1)).await;
177-
Ok(Python::with_gil(|py| py.None()))
178-
})?
179-
.into())
181+
let completed = Arc::clone(&completed);
182+
Ok(
183+
pyo3_asyncio::async_std::cancellable_future_into_py(py, async move {
184+
async_std::task::sleep(Duration::from_secs(1)).await;
185+
*completed.lock().unwrap() = true;
186+
187+
Ok(Python::with_gil(|py| py.None()))
188+
})?
189+
.into(),
190+
)
180191
})?;
181192

182193
if let Err(e) = Python::with_gil(|py| -> PyResult<_> {
@@ -198,9 +209,60 @@ async fn test_cancel() -> PyResult<()> {
198209
panic!("expected CancelledError");
199210
}
200211

212+
async_std::task::sleep(Duration::from_secs(1)).await;
213+
if *completed.lock().unwrap() {
214+
panic!("future still completed")
215+
}
216+
201217
Ok(())
202218
}
203219

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+
204266
/// This module is implemented in Rust.
205267
#[pymodule]
206268
fn test_mod(_py: Python, m: &PyModule) -> PyResult<()> {

pytests/tokio_asyncio/mod.rs

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use std::{rc::Rc, time::Duration};
1+
use std::{
2+
rc::Rc,
3+
sync::{Arc, Mutex},
4+
time::Duration,
5+
};
26

37
use pyo3::{
48
prelude::*,
@@ -168,12 +172,19 @@ async fn test_panic() -> PyResult<()> {
168172

169173
#[pyo3_asyncio::tokio::test]
170174
async fn test_cancel() -> PyResult<()> {
175+
let completed = Arc::new(Mutex::new(false));
176+
171177
let py_future = Python::with_gil(|py| -> PyResult<PyObject> {
172-
Ok(pyo3_asyncio::tokio::future_into_py(py, async {
173-
tokio::time::sleep(Duration::from_secs(1)).await;
174-
Ok(Python::with_gil(|py| py.None()))
175-
})?
176-
.into())
178+
let completed = Arc::clone(&completed);
179+
Ok(
180+
pyo3_asyncio::tokio::cancellable_future_into_py(py, async move {
181+
tokio::time::sleep(Duration::from_secs(1)).await;
182+
*completed.lock().unwrap() = true;
183+
184+
Ok(Python::with_gil(|py| py.None()))
185+
})?
186+
.into(),
187+
)
177188
})?;
178189

179190
if let Err(e) = Python::with_gil(|py| -> PyResult<_> {
@@ -195,8 +206,62 @@ async fn test_cancel() -> PyResult<()> {
195206
panic!("expected CancelledError");
196207
}
197208

209+
tokio::time::sleep(Duration::from_secs(1)).await;
210+
if *completed.lock().unwrap() {
211+
panic!("future still completed")
212+
}
213+
198214
Ok(())
199215
}
216+
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+
200265
/// This module is implemented in Rust.
201266
#[pymodule]
202267
fn test_mod(_py: Python, m: &PyModule) -> PyResult<()> {

0 commit comments

Comments
 (0)