1 // Copyright 2016 Amanieu d'Antras
2 //
3 // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4 // http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5 // http://opensource.org/licenses/MIT>, at your option. This file may not be
6 // copied, modified, or distributed except according to those terms.
7 
8 use core::{
9     ptr,
10     sync::atomic::{AtomicI32, Ordering},
11 };
12 use instant::Instant;
13 use std::thread;
14 use syscall::{
15     call::futex,
16     data::TimeSpec,
17     error::{Error, EAGAIN, EFAULT, EINTR, ETIMEDOUT},
18     flag::{FUTEX_WAIT, FUTEX_WAKE},
19 };
20 
21 const UNPARKED: i32 = 0;
22 const PARKED: i32 = 1;
23 
24 // Helper type for putting a thread to sleep until some other thread wakes it up
25 pub struct ThreadParker {
26     futex: AtomicI32,
27 }
28 
29 impl super::ThreadParkerT for ThreadParker {
30     type UnparkHandle = UnparkHandle;
31 
32     const IS_CHEAP_TO_CONSTRUCT: bool = true;
33 
34     #[inline]
new() -> ThreadParker35     fn new() -> ThreadParker {
36         ThreadParker {
37             futex: AtomicI32::new(UNPARKED),
38         }
39     }
40 
41     #[inline]
prepare_park(&self)42     unsafe fn prepare_park(&self) {
43         self.futex.store(PARKED, Ordering::Relaxed);
44     }
45 
46     #[inline]
timed_out(&self) -> bool47     unsafe fn timed_out(&self) -> bool {
48         self.futex.load(Ordering::Relaxed) != UNPARKED
49     }
50 
51     #[inline]
park(&self)52     unsafe fn park(&self) {
53         while self.futex.load(Ordering::Acquire) != UNPARKED {
54             self.futex_wait(None);
55         }
56     }
57 
58     #[inline]
park_until(&self, timeout: Instant) -> bool59     unsafe fn park_until(&self, timeout: Instant) -> bool {
60         while self.futex.load(Ordering::Acquire) != UNPARKED {
61             let now = Instant::now();
62             if timeout <= now {
63                 return false;
64             }
65             let diff = timeout - now;
66             if diff.as_secs() > i64::max_value() as u64 {
67                 // Timeout overflowed, just sleep indefinitely
68                 self.park();
69                 return true;
70             }
71             let ts = TimeSpec {
72                 tv_sec: diff.as_secs() as i64,
73                 tv_nsec: diff.subsec_nanos() as i32,
74             };
75             self.futex_wait(Some(ts));
76         }
77         true
78     }
79 
80     #[inline]
unpark_lock(&self) -> UnparkHandle81     unsafe fn unpark_lock(&self) -> UnparkHandle {
82         // We don't need to lock anything, just clear the state
83         self.futex.store(UNPARKED, Ordering::Release);
84 
85         UnparkHandle { futex: self.ptr() }
86     }
87 }
88 
89 impl ThreadParker {
90     #[inline]
futex_wait(&self, ts: Option<TimeSpec>)91     fn futex_wait(&self, ts: Option<TimeSpec>) {
92         let ts_ptr = ts
93             .as_ref()
94             .map(|ts_ref| ts_ref as *const _)
95             .unwrap_or(ptr::null());
96         let r = unsafe {
97             futex(
98                 self.ptr(),
99                 FUTEX_WAIT,
100                 PARKED,
101                 ts_ptr as usize,
102                 ptr::null_mut(),
103             )
104         };
105         match r {
106             Ok(r) => debug_assert_eq!(r, 0),
107             Err(Error { errno }) => {
108                 debug_assert!(errno == EINTR || errno == EAGAIN || errno == ETIMEDOUT);
109             }
110         }
111     }
112 
113     #[inline]
ptr(&self) -> *mut i32114     fn ptr(&self) -> *mut i32 {
115         &self.futex as *const AtomicI32 as *mut i32
116     }
117 }
118 
119 pub struct UnparkHandle {
120     futex: *mut i32,
121 }
122 
123 impl super::UnparkHandleT for UnparkHandle {
124     #[inline]
unpark(self)125     unsafe fn unpark(self) {
126         // The thread data may have been freed at this point, but it doesn't
127         // matter since the syscall will just return EFAULT in that case.
128         let r = futex(self.futex, FUTEX_WAKE, PARKED, 0, ptr::null_mut());
129         match r {
130             Ok(num_woken) => debug_assert!(num_woken == 0 || num_woken == 1),
131             Err(Error { errno }) => debug_assert_eq!(errno, EFAULT),
132         }
133     }
134 }
135 
136 #[inline]
thread_yield()137 pub fn thread_yield() {
138     thread::yield_now();
139 }
140