@@ -21,7 +21,7 @@ macro_rules! doc {
2121 /// The supplied futures are stored inline and do not require allocating a
2222 /// `Vec`.
2323 ///
24- /// ### Runtime characteristics
24+ /// ## Runtime characteristics
2525 ///
2626 /// By running all async expressions on the current task, the expressions are
2727 /// able to run **concurrently** but not in **parallel**. This means all
@@ -32,6 +32,25 @@ macro_rules! doc {
3232 ///
3333 /// [`tokio::spawn`]: crate::spawn
3434 ///
35+ /// ## Fairness
36+ ///
37+ /// By default, `join!`'s generated future rotates which contained
38+ /// future is polled first whenever it is woken.
39+ ///
40+ /// This behavior can be overridden by adding `biased;` to the beginning of the
41+ /// macro usage. See the examples for details. This will cause `join` to poll
42+ /// the futures in the order they appear from top to bottom.
43+ ///
44+ /// You may want this if your futures may interact in a way where known polling order is significant.
45+ ///
46+ /// But there is an important caveat to this mode. It becomes your responsibility
47+ /// to ensure that the polling order of your futures is fair. If for example you
48+ /// are joining a stream and a shutdown future, and the stream has a
49+ /// huge volume of messages that takes a long time to finish processing per poll, you should
50+ /// place the shutdown future earlier in the `join!` list to ensure that it is
51+ /// always polled, and will not be delayed due to the stream future taking a long time to return
52+ /// `Poll::Pending`.
53+ ///
3554 /// # Examples
3655 ///
3756 /// Basic join with two branches
@@ -54,6 +73,30 @@ macro_rules! doc {
5473 /// // do something with the values
5574 /// }
5675 /// ```
76+ ///
77+ /// Using the `biased;` mode to control polling order.
78+ ///
79+ /// ```
80+ /// async fn do_stuff_async() {
81+ /// // async work
82+ /// }
83+ ///
84+ /// async fn more_async_work() {
85+ /// // more here
86+ /// }
87+ ///
88+ /// #[tokio::main]
89+ /// async fn main() {
90+ /// let (first, second) = tokio::join!(
91+ /// biased;
92+ /// do_stuff_async(),
93+ /// more_async_work()
94+ /// );
95+ ///
96+ /// // do something with the values
97+ /// }
98+ /// ```
99+
57100 #[ macro_export]
58101 #[ cfg_attr( docsrs, doc( cfg( feature = "macros" ) ) ) ]
59102 $join
@@ -62,12 +105,16 @@ macro_rules! doc {
62105
63106#[ cfg( doc) ]
64107doc ! { macro_rules! join {
65- ( $( $future: expr) , * ) => { unimplemented!( ) }
108+ ( $( biased ; ) ? $ ( $future: expr) , * ) => { unimplemented!( ) }
66109} }
67110
68111#[ cfg( not( doc) ) ]
69112doc ! { macro_rules! join {
70113 ( @ {
114+ // Type of rotator that controls which inner future to start with
115+ // when polling our output future.
116+ rotator=$rotator: ty;
117+
71118 // One `_` for each branch in the `join!` macro. This is not used once
72119 // normalization is complete.
73120 ( $( $count: tt) * )
@@ -96,25 +143,19 @@ doc! {macro_rules! join {
96143 // <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
97144 let mut futures = & mut futures;
98145
99- // Each time the future created by poll_fn is polled, a different future will be polled first
100- // to ensure every future passed to join! gets a chance to make progress even if
101- // one of the futures consumes the whole budget.
102- //
103- // This is number of futures that will be skipped in the first loop
104- // iteration the next time.
105- let mut skip_next_time: u32 = 0 ;
146+ const COUNT : u32 = $( $total) * ;
106147
107- poll_fn( move |cx| {
108- const COUNT : u32 = $( $total) * ;
148+ // Each time the future created by poll_fn is polled, if not using biased mode,
149+ // a different future is polled first to ensure every future passed to join!
150+ // can make progress even if one of the futures consumes the whole budget.
151+ let mut rotator = <$rotator>:: default ( ) ;
109152
153+ poll_fn( move |cx| {
110154 let mut is_pending = false ;
111-
112155 let mut to_run = COUNT ;
113156
114157 // The number of futures that will be skipped in the first loop iteration.
115- let mut skip = skip_next_time;
116-
117- skip_next_time = if skip + 1 == COUNT { 0 } else { skip + 1 } ;
158+ let mut skip = rotator. num_skip( ) ;
118159
119160 // This loop runs twice and the first `skip` futures
120161 // are not polled in the first iteration.
@@ -164,15 +205,51 @@ doc! {macro_rules! join {
164205
165206 // ===== Normalize =====
166207
167- ( @ { ( $( $s: tt) * ) ( $( $n: tt) * ) $( $t: tt) * } $e: expr, $( $r: tt) * ) => {
168- $crate :: join!( @{ ( $( $s) * _) ( $( $n) * + 1 ) $( $t) * ( $( $s) * ) $e, } $( $r) * )
208+ ( @ { rotator=$rotator : ty ; ( $( $s: tt) * ) ( $( $n: tt) * ) $( $t: tt) * } $e: expr, $( $r: tt) * ) => {
209+ $crate :: join!( @{ rotator=$rotator ; ( $( $s) * _) ( $( $n) * + 1 ) $( $t) * ( $( $s) * ) $e, } $( $r) * )
169210 } ;
170211
171212 // ===== Entry point =====
213+ ( biased; $( $e: expr) , + $( , ) ?) => {
214+ $crate :: join!( @{ rotator=$crate :: macros:: support:: BiasedRotator ; ( ) ( 0 ) } $( $e, ) * )
215+ } ;
172216
173217 ( $( $e: expr) , + $( , ) ?) => {
174- $crate :: join!( @{ ( ) ( 0 ) } $( $e, ) * )
218+ $crate :: join!( @{ rotator=$ crate :: macros :: support :: Rotator < COUNT > ; ( ) ( 0 ) } $( $e, ) * )
175219 } ;
176220
221+ ( biased; ) => { async { } . await } ;
222+
177223 ( ) => { async { } . await }
178224} }
225+
226+ /// Rotates by one each [`Self::num_skip`] call up to COUNT - 1.
227+ #[ derive( Default , Debug ) ]
228+ pub struct Rotator < const COUNT : u32 > {
229+ next : u32 ,
230+ }
231+
232+ impl < const COUNT : u32 > Rotator < COUNT > {
233+ /// Rotates by one each [`Self::num_skip`] call up to COUNT - 1
234+ #[ inline]
235+ pub fn num_skip ( & mut self ) -> u32 {
236+ let num_skip = self . next ;
237+ self . next += 1 ;
238+ if self . next == COUNT {
239+ self . next = 0 ;
240+ }
241+ num_skip
242+ }
243+ }
244+
245+ /// [`Self::num_skip`] always returns 0.
246+ #[ derive( Default , Debug ) ]
247+ pub struct BiasedRotator { }
248+
249+ impl BiasedRotator {
250+ /// Always returns 0.
251+ #[ inline]
252+ pub fn num_skip ( & mut self ) -> u32 {
253+ 0
254+ }
255+ }
0 commit comments