|
1 | 1 | #![cfg_attr(not(feature = "full"), allow(dead_code))] |
2 | 2 |
|
3 | | -//! Opt-in yield points for improved cooperative scheduling. |
| 3 | +//! Yield points for improved cooperative scheduling. |
4 | 4 | //! |
5 | | -//! A single call to [`poll`] on a top-level task may potentially do a lot of |
6 | | -//! work before it returns `Poll::Pending`. If a task runs for a long period of |
7 | | -//! time without yielding back to the executor, it can starve other tasks |
8 | | -//! waiting on that executor to execute them, or drive underlying resources. |
9 | | -//! Since Rust does not have a runtime, it is difficult to forcibly preempt a |
10 | | -//! long-running task. Instead, this module provides an opt-in mechanism for |
11 | | -//! futures to collaborate with the executor to avoid starvation. |
| 5 | +//! Documentation for this can be found in the [`tokio::task`] module. |
12 | 6 | //! |
13 | | -//! Consider a future like this one: |
14 | | -//! |
15 | | -//! ``` |
16 | | -//! # use tokio_stream::{Stream, StreamExt}; |
17 | | -//! async fn drop_all<I: Stream + Unpin>(mut input: I) { |
18 | | -//! while let Some(_) = input.next().await {} |
19 | | -//! } |
20 | | -//! ``` |
21 | | -//! |
22 | | -//! It may look harmless, but consider what happens under heavy load if the |
23 | | -//! input stream is _always_ ready. If we spawn `drop_all`, the task will never |
24 | | -//! yield, and will starve other tasks and resources on the same executor. With |
25 | | -//! opt-in yield points, this problem is alleviated: |
26 | | -//! |
27 | | -//! ```ignore |
28 | | -//! # use tokio_stream::{Stream, StreamExt}; |
29 | | -//! async fn drop_all<I: Stream + Unpin>(mut input: I) { |
30 | | -//! while let Some(_) = input.next().await { |
31 | | -//! tokio::coop::proceed().await; |
32 | | -//! } |
33 | | -//! } |
34 | | -//! ``` |
35 | | -//! |
36 | | -//! The `proceed` future will coordinate with the executor to make sure that |
37 | | -//! every so often control is yielded back to the executor so it can run other |
38 | | -//! tasks. |
39 | | -//! |
40 | | -//! # Placing yield points |
41 | | -//! |
42 | | -//! Voluntary yield points should be placed _after_ at least some work has been |
43 | | -//! done. If they are not, a future sufficiently deep in the task hierarchy may |
44 | | -//! end up _never_ getting to run because of the number of yield points that |
45 | | -//! inevitably appear before it is reached. In general, you will want yield |
46 | | -//! points to only appear in "leaf" futures -- those that do not themselves poll |
47 | | -//! other futures. By doing this, you avoid double-counting each iteration of |
48 | | -//! the outer future against the cooperating budget. |
49 | | -//! |
50 | | -//! [`poll`]: method@std::future::Future::poll |
51 | | -
|
52 | | -// NOTE: The doctests in this module are ignored since the whole module is (currently) private. |
| 7 | +//! [`tokio::task`]: crate::task. |
| 8 | +
|
| 9 | +// ```ignore |
| 10 | +// # use tokio_stream::{Stream, StreamExt}; |
| 11 | +// async fn drop_all<I: Stream + Unpin>(mut input: I) { |
| 12 | +// while let Some(_) = input.next().await { |
| 13 | +// tokio::coop::proceed().await; |
| 14 | +// } |
| 15 | +// } |
| 16 | +// ``` |
| 17 | +// |
| 18 | +// The `proceed` future will coordinate with the executor to make sure that |
| 19 | +// every so often control is yielded back to the executor so it can run other |
| 20 | +// tasks. |
| 21 | +// |
| 22 | +// # Placing yield points |
| 23 | +// |
| 24 | +// Voluntary yield points should be placed _after_ at least some work has been |
| 25 | +// done. If they are not, a future sufficiently deep in the task hierarchy may |
| 26 | +// end up _never_ getting to run because of the number of yield points that |
| 27 | +// inevitably appear before it is reached. In general, you will want yield |
| 28 | +// points to only appear in "leaf" futures -- those that do not themselves poll |
| 29 | +// other futures. By doing this, you avoid double-counting each iteration of |
| 30 | +// the outer future against the cooperating budget. |
53 | 31 |
|
54 | 32 | use std::cell::Cell; |
55 | 33 |
|
@@ -98,6 +76,13 @@ pub(crate) fn budget<R>(f: impl FnOnce() -> R) -> R { |
98 | 76 | with_budget(Budget::initial(), f) |
99 | 77 | } |
100 | 78 |
|
| 79 | +/// Run the given closure with an unconstrained task budget. When the function returns, the budget |
| 80 | +/// is reset to the value prior to calling the function. |
| 81 | +#[inline(always)] |
| 82 | +pub(crate) fn with_unconstrained<R>(f: impl FnOnce() -> R) -> R { |
| 83 | + with_budget(Budget::unconstrained(), f) |
| 84 | +} |
| 85 | + |
101 | 86 | #[inline(always)] |
102 | 87 | fn with_budget<R>(budget: Budget, f: impl FnOnce() -> R) -> R { |
103 | 88 | struct ResetGuard<'a> { |
|
0 commit comments