]>
Commit | Line | Data |
---|---|---|
ff7c6d11 XL |
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 std::sync::atomic::{AtomicI32, Ordering}; | |
9 | use std::time::Instant; | |
10 | use libc; | |
11 | ||
12 | const FUTEX_WAIT: i32 = 0; | |
13 | const FUTEX_WAKE: i32 = 1; | |
14 | const FUTEX_PRIVATE: i32 = 128; | |
15 | ||
16 | // Helper type for putting a thread to sleep until some other thread wakes it up | |
17 | pub struct ThreadParker { | |
18 | futex: AtomicI32, | |
19 | } | |
20 | ||
21 | impl ThreadParker { | |
22 | pub fn new() -> ThreadParker { | |
23 | ThreadParker { | |
24 | futex: AtomicI32::new(0), | |
25 | } | |
26 | } | |
27 | ||
28 | // Prepares the parker. This should be called before adding it to the queue. | |
29 | pub unsafe fn prepare_park(&self) { | |
30 | self.futex.store(1, Ordering::Relaxed); | |
31 | } | |
32 | ||
33 | // Checks if the park timed out. This should be called while holding the | |
34 | // queue lock after park_until has returned false. | |
35 | pub unsafe fn timed_out(&self) -> bool { | |
36 | self.futex.load(Ordering::Relaxed) != 0 | |
37 | } | |
38 | ||
39 | // Parks the thread until it is unparked. This should be called after it has | |
40 | // been added to the queue, after unlocking the queue. | |
41 | pub unsafe fn park(&self) { | |
42 | while self.futex.load(Ordering::Acquire) != 0 { | |
43 | let r = libc::syscall(libc::SYS_futex, &self.futex, FUTEX_WAIT | FUTEX_PRIVATE, 1, 0); | |
44 | debug_assert!(r == 0 || r == -1); | |
45 | if r == -1 { | |
46 | debug_assert!( | |
47 | *libc::__errno_location() == libc::EINTR | |
48 | || *libc::__errno_location() == libc::EAGAIN | |
49 | ); | |
50 | } | |
51 | } | |
52 | } | |
53 | ||
54 | // Parks the thread until it is unparked or the timeout is reached. This | |
55 | // should be called after it has been added to the queue, after unlocking | |
56 | // the queue. Returns true if we were unparked and false if we timed out. | |
57 | pub unsafe fn park_until(&self, timeout: Instant) -> bool { | |
58 | while self.futex.load(Ordering::Acquire) != 0 { | |
59 | let now = Instant::now(); | |
60 | if timeout <= now { | |
61 | return false; | |
62 | } | |
63 | let diff = timeout - now; | |
64 | if diff.as_secs() as libc::time_t as u64 != diff.as_secs() { | |
65 | // Timeout overflowed, just sleep indefinitely | |
66 | self.park(); | |
67 | return true; | |
68 | } | |
69 | let ts = libc::timespec { | |
70 | tv_sec: diff.as_secs() as libc::time_t, | |
71 | tv_nsec: diff.subsec_nanos() as libc::c_long, | |
72 | }; | |
73 | let r = libc::syscall(libc::SYS_futex, &self.futex, FUTEX_WAIT | FUTEX_PRIVATE, 1, &ts); | |
74 | debug_assert!(r == 0 || r == -1); | |
75 | if r == -1 { | |
76 | debug_assert!( | |
77 | *libc::__errno_location() == libc::EINTR | |
78 | || *libc::__errno_location() == libc::EAGAIN | |
79 | || *libc::__errno_location() == libc::ETIMEDOUT | |
80 | ); | |
81 | } | |
82 | } | |
83 | true | |
84 | } | |
85 | ||
86 | // Locks the parker to prevent the target thread from exiting. This is | |
87 | // necessary to ensure that thread-local ThreadData objects remain valid. | |
88 | // This should be called while holding the queue lock. | |
89 | pub unsafe fn unpark_lock(&self) -> UnparkHandle { | |
90 | // We don't need to lock anything, just clear the state | |
91 | self.futex.store(0, Ordering::Release); | |
92 | ||
93 | UnparkHandle { futex: &self.futex } | |
94 | } | |
95 | } | |
96 | ||
97 | // Handle for a thread that is about to be unparked. We need to mark the thread | |
98 | // as unparked while holding the queue lock, but we delay the actual unparking | |
99 | // until after the queue lock is released. | |
100 | pub struct UnparkHandle { | |
101 | futex: *const AtomicI32, | |
102 | } | |
103 | ||
104 | impl UnparkHandle { | |
105 | // Wakes up the parked thread. This should be called after the queue lock is | |
106 | // released to avoid blocking the queue for too long. | |
107 | pub unsafe fn unpark(self) { | |
108 | // The thread data may have been freed at this point, but it doesn't | |
109 | // matter since the syscall will just return EFAULT in that case. | |
110 | let r = libc::syscall(libc::SYS_futex, self.futex, FUTEX_WAKE | FUTEX_PRIVATE, 1); | |
111 | debug_assert!(r == 0 || r == 1 || r == -1); | |
112 | if r == -1 { | |
113 | debug_assert_eq!(*libc::__errno_location(), libc::EFAULT); | |
114 | } | |
115 | } | |
116 | } |