cancel_safe_futures/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! Alternative futures adapters that are more cancellation-aware.
4//!
5//! # What is this crate?
6//!
7//! This crate solves a few related but distinct problems:
8//!
9//! ## 1. Cancel-safe futures adapters
10//!
11//! The [`futures`](https://docs.rs/futures/latest/futures/) library contains many adapters that
12//! make writing asynchronous Rust code more pleasant. However, some of those combinators make it
13//! hard to write code that can withstand cancellation in the case of timeouts, `select!` branches
14//! or similar.
15//!
16//! For a more detailed explanation, see the documentation for [`SinkExt::reserve`].
17//!
18//! ### Example
19//!
20//! Attempt to send an item in a loop with a timeout:
21//!
22//! ```
23//! use cancel_safe_futures::prelude::*;
24//! use std::time::Duration;
25//!
26//! # #[tokio::main(flavor = "current_thread")]
27//! # async fn main() -> Result<(), std::convert::Infallible> {
28//! # /*
29//! let mut my_sink = /* ... */;
30//! # */
31//! # let mut my_sink = futures_util::sink::drain();
32//!
33//! // This item is stored here and will be set to None once the loop exits successfully.
34//! let mut item = Some("hello".to_owned());
35//! let do_flush = false;
36//!
37//! while item.is_some() {
38//! match tokio::time::timeout(Duration::from_secs(10), my_sink.reserve()).await {
39//! Ok(Ok(permit)) => {
40//! let item = item.take().unwrap();
41//! if !do_flush {
42//! // permit.feed() feeds the item into the sink without flushing
43//! // the sink afterwards. This is a synchronous method.
44//! permit.feed(item)?;
45//! } else {
46//! // Alternatively, permit.send() writes the item into the sink
47//! // synchronously, then returns a future which can be awaited to
48//! // flush the sink.
49//! permit.send(item)?.await?;
50//! }
51//! }
52//! Ok(Err(error)) => return Err(error),
53//! Err(timeout_error) => continue,
54//! }
55//! }
56//!
57//! # Ok(()) }
58//! ```
59//!
60//! ## 2. `then_try` adapters that don't perform cancellations
61//!
62//! The futures and tokio libraries come with a number of `try_` adapters and macros, for example
63//! [`tokio::try_join!`]. These adapters have the property that if one of the futures under
64//! consideration fails, all other futures are cancelled.
65//!
66//! This is not always desirable and has led to correctness bugs (e.g. [omicron PR
67//! 3707](https://github.com/oxidecomputer/omicron/pull/3707)). To address this issue, this crate
68//! provides a set of `then_try` adapters and macros that behave like their `try_` counterparts,
69//! except that if one or more of the futures errors out, the others will still be run to
70//! completion.
71//!
72//! The `then_try` family includes:
73//!
74//! * [`join_then_try`]: similar to [`tokio::try_join`].
75//! * [`future::join_all_then_try`]: similar to [`futures::future::try_join_all`].
76//! * [`TryStreamExt`]: contains alternative extension methods to [`futures::stream::TryStreamExt`],
77//! such as `collect_then_try`.
78//!
79//! ### Example
80//!
81//! For a detailed example, see the documentation for the [`join_then_try`] macro.
82//!
83//! ## 3. Cancel-safe mutexes
84//!
85//! The [`tokio::sync::Mutex`] shipped with Tokio has resulted in many bugs in practice,
86//! particularly around cancellations.
87//!
88//! This crate provides an alternative mutex API, called [`RobustMutex`](sync::RobustMutex), that does not
89//! have those pitfalls. For more, see the documentation for [`RobustMutex`](sync::RobustMutex).
90//!
91//! ## 4. Cooperative cancellation
92//!
93//! Executors like Tokio support forcible cancellation for async tasks via facilities like
94//! [`tokio::task::JoinHandle::abort`]. However, this can cause cancellations at any arbitrary await
95//! point. If the future is in the middle of cancel-unsafe code, this can cause invariant violations
96//! or other issues.
97//!
98//! Instead, async cancellation can be done cooperatively: code can check for cancellation
99//! explicitly via [`tokio::select!`]. This crate provides the [`coop_cancel`] module that can be
100//! used to accomplish that goal.
101//!
102//! ### Example
103//!
104//! For a detailed example, see the documentation for [`coop_cancel`].
105//!
106//! # Notes
107//!
108//! This library is not complete: adapters and macros are added on an as-needed basis. If you need
109//! an adapter that is not yet implemented, please open an issue or a pull request.
110//!
111//! # Optional features
112//!
113//! * `macros` (enabled by default): Enables macros.
114//! * `std` (enabled by default): Enables items that depend on `std`, including items that depend on
115//! `alloc`.
116//! * `alloc` (enabled by default): Enables items that depend on `alloc`.
117//! * `parking_lot`: Switches to `parking_lot`'s mutexes.
118//!
119//! No-std users must turn off default features while importing this crate.
120
121#![warn(missing_docs)]
122#![cfg_attr(doc_cfg, feature(doc_cfg))]
123
124#[cfg(feature = "alloc")]
125extern crate alloc;
126
127// Includes re-exports used by macros.
128//
129// This module is not intended to be part of the public API. In general, any
130// `doc(hidden)` code is not part of the public and stable API.
131#[macro_use]
132#[doc(hidden)]
133pub mod macros;
134
135#[cfg(feature = "std")]
136pub mod coop_cancel;
137pub mod future;
138pub mod prelude;
139pub mod sink;
140pub mod stream;
141mod support;
142pub mod sync;
143
144pub use sink::SinkExt;
145pub use stream::TryStreamExt;