1
- use std:: fmt;
2
1
use std:: marker:: PhantomData ;
3
2
use std:: sync:: atomic:: AtomicUsize ;
4
3
use std:: sync:: atomic:: Ordering :: SeqCst ;
5
4
use std:: sync:: { Arc , Condvar , Mutex } ;
6
5
use std:: time:: Duration ;
6
+ use std:: { fmt, time:: Instant } ;
7
7
8
8
/// A thread parking primitive.
9
9
///
10
10
/// Conceptually, each `Parker` has an associated token which is initially not present:
11
11
///
12
12
/// * The [`park`] method blocks the current thread unless or until the token is available, at
13
- /// which point it automatically consumes the token. It may also return *spuriously*, without
14
- /// consuming the token.
13
+ /// which point it automatically consumes the token.
15
14
///
16
- /// * The [`park_timeout`] method works the same as [`park`], but blocks for a specified maximum
17
- /// time.
15
+ /// * The [`park_timeout`] and [`park_deadline`] methods work the same as [`park`], but block for
16
+ /// a specified maximum time.
18
17
///
19
18
/// * The [`unpark`] method atomically makes the token available if it wasn't already. Because the
20
19
/// token is initially absent, [`unpark`] followed by [`park`] will result in the second call
@@ -43,13 +42,13 @@ use std::time::Duration;
43
42
/// u.unpark();
44
43
/// });
45
44
///
46
- /// // Wakes up when `u.unpark()` provides the token, but may also wake up
47
- /// // spuriously before that without consuming the token.
45
+ /// // Wakes up when `u.unpark()` provides the token.
48
46
/// p.park();
49
47
/// ```
50
48
///
51
49
/// [`park`]: Parker::park
52
50
/// [`park_timeout`]: Parker::park_timeout
51
+ /// [`park_deadline`]: Parker::park_deadline
53
52
/// [`unpark`]: Unparker::unpark
54
53
pub struct Parker {
55
54
unparker : Unparker ,
@@ -90,9 +89,6 @@ impl Parker {
90
89
91
90
/// Blocks the current thread until the token is made available.
92
91
///
93
- /// A call to `park` may wake up spuriously without consuming the token, and callers should be
94
- /// prepared for this possibility.
95
- ///
96
92
/// # Examples
97
93
///
98
94
/// ```
@@ -113,9 +109,6 @@ impl Parker {
113
109
114
110
/// Blocks the current thread until the token is made available, but only for a limited time.
115
111
///
116
- /// A call to `park_timeout` may wake up spuriously without consuming the token, and callers
117
- /// should be prepared for this possibility.
118
- ///
119
112
/// # Examples
120
113
///
121
114
/// ```
@@ -127,8 +120,26 @@ impl Parker {
127
120
/// // Waits for the token to become available, but will not wait longer than 500 ms.
128
121
/// p.park_timeout(Duration::from_millis(500));
129
122
/// ```
130
- pub fn park_timeout ( & self , timeout : Duration ) {
131
- self . unparker . inner . park ( Some ( timeout) ) ;
123
+ pub fn park_timeout ( & self , timeout : Duration ) -> UnparkReason {
124
+ self . park_deadline ( Instant :: now ( ) + timeout)
125
+ }
126
+
127
+ /// Blocks the current thread until the token is made available, or until a certain deadline.
128
+ ///
129
+ /// # Examples
130
+ ///
131
+ /// ```
132
+ /// use std::time::{Duration, Instant};
133
+ /// use crossbeam_utils::sync::Parker;
134
+ ///
135
+ /// let p = Parker::new();
136
+ /// let deadline = Instant::now() + Duration::from_millis(500);
137
+ ///
138
+ /// // Waits for the token to become available, but will not wait longer than 500 ms.
139
+ /// p.park_deadline(deadline);
140
+ /// ```
141
+ pub fn park_deadline ( & self , deadline : Instant ) -> UnparkReason {
142
+ self . unparker . inner . park ( Some ( deadline) )
132
143
}
133
144
134
145
/// Returns a reference to an associated [`Unparker`].
@@ -227,8 +238,7 @@ impl Unparker {
227
238
/// u.unpark();
228
239
/// });
229
240
///
230
- /// // Wakes up when `u.unpark()` provides the token, but may also wake up
231
- /// // spuriously before that without consuming the token.
241
+ /// // Wakes up when `u.unpark()` provides the token.
232
242
/// p.park();
233
243
/// ```
234
244
///
@@ -291,6 +301,18 @@ impl Clone for Unparker {
291
301
}
292
302
}
293
303
304
+ /// An enum that reports whether a `Parker::park_timeout` or
305
+ /// `Parker::park_deadline` returned because another thread called `unpark` or
306
+ /// because of a timeout.
307
+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
308
+ pub enum UnparkReason {
309
+ /// The park method returned due to a call to `unpark`.
310
+ Unparked ,
311
+
312
+ /// The park method returned due to a timeout.
313
+ Timeout ,
314
+ }
315
+
294
316
const EMPTY : usize = 0 ;
295
317
const PARKED : usize = 1 ;
296
318
const NOTIFIED : usize = 2 ;
@@ -302,20 +324,20 @@ struct Inner {
302
324
}
303
325
304
326
impl Inner {
305
- fn park ( & self , timeout : Option < Duration > ) {
327
+ fn park ( & self , deadline : Option < Instant > ) -> UnparkReason {
306
328
// If we were previously notified then we consume this notification and return quickly.
307
329
if self
308
330
. state
309
331
. compare_exchange ( NOTIFIED , EMPTY , SeqCst , SeqCst )
310
332
. is_ok ( )
311
333
{
312
- return ;
334
+ return UnparkReason :: Unparked ;
313
335
}
314
336
315
337
// If the timeout is zero, then there is no need to actually block.
316
- if let Some ( ref dur ) = timeout {
317
- if * dur == Duration :: from_millis ( 0 ) {
318
- return ;
338
+ if let Some ( deadline ) = deadline {
339
+ if deadline <= Instant :: now ( ) {
340
+ return UnparkReason :: Timeout ;
319
341
}
320
342
}
321
343
@@ -333,41 +355,42 @@ impl Inner {
333
355
// do that we must read from the write it made to `state`.
334
356
let old = self . state . swap ( EMPTY , SeqCst ) ;
335
357
assert_eq ! ( old, NOTIFIED , "park state changed unexpectedly" ) ;
336
- return ;
358
+ return UnparkReason :: Unparked ;
337
359
}
338
360
Err ( n) => panic ! ( "inconsistent park_timeout state: {}" , n) ,
339
361
}
340
362
341
- match timeout {
342
- None => {
343
- loop {
344
- // Block the current thread on the conditional variable.
345
- m = self . cvar . wait ( m) . unwrap ( ) ;
346
-
347
- if self
348
- . state
349
- . compare_exchange ( NOTIFIED , EMPTY , SeqCst , SeqCst )
350
- . is_ok ( )
351
- {
352
- // got a notification
353
- return ;
363
+ loop {
364
+ // Block the current thread on the conditional variable.
365
+ m = match deadline {
366
+ None => self . cvar . wait ( m) . unwrap ( ) ,
367
+ Some ( deadline) => match deadline. checked_duration_since ( Instant :: now ( ) ) {
368
+ // We could check for a timeout here, but in the case that a timeout and an
369
+ // unpark arrive simultaneously, we prefer to report the former.
370
+ Some ( duration) if duration > Duration :: from_secs ( 0 ) => {
371
+ self . cvar . wait_timeout ( m, duration) . unwrap ( ) . 0
354
372
}
355
373
356
- // spurious wakeup, go back to sleep
357
- }
358
- }
359
- Some ( timeout) => {
360
- // Wait with a timeout, and if we spuriously wake up or otherwise wake up from a
361
- // notification we just want to unconditionally set `state` back to `EMPTY`, either
362
- // consuming a notification or un-flagging ourselves as parked.
363
- let ( _m, _result) = self . cvar . wait_timeout ( m, timeout) . unwrap ( ) ;
364
-
365
- match self . state . swap ( EMPTY , SeqCst ) {
366
- NOTIFIED => { } // got a notification
367
- PARKED => { } // no notification
368
- n => panic ! ( "inconsistent park_timeout state: {}" , n) ,
369
- }
374
+ // We've timed out; swap out the state back to empty on our way out
375
+ _ => match self . state . swap ( EMPTY , SeqCst ) {
376
+ NOTIFIED => return UnparkReason :: Unparked , // got a notification
377
+ PARKED => return UnparkReason :: Timeout , // no notification
378
+ n => panic ! ( "inconsistent park_timeout state: {}" , n) ,
379
+ } ,
380
+ } ,
381
+ } ;
382
+
383
+ if self
384
+ . state
385
+ . compare_exchange ( NOTIFIED , EMPTY , SeqCst , SeqCst )
386
+ . is_ok ( )
387
+ {
388
+ // got a notification
389
+ return UnparkReason :: Unparked ;
370
390
}
391
+
392
+ // Spurious wakeup, go back to sleep. Alternatively, if we timed out, it will be caught
393
+ // in the branch above, when we discover the deadline is in the past
371
394
}
372
395
}
373
396
0 commit comments