Skip to main content

parking_lot_core/thread_parker/
linux.rs

1use core::{
2    ptr::{self, NonNull},
3    sync::atomic::{AtomicBool, AtomicI32, Ordering},
4};
5use std::thread;
6use std::time::{Duration, Instant};
7
8fn errno() -> libc::c_int {
9    #[cfg(target_os = "linux")]
10    unsafe {
11        *libc::__errno_location()
12    }
13    #[cfg(target_os = "android")]
14    unsafe {
15        *libc::__errno()
16    }
17}
18
19#[inline]
20fn raw_futex_syscall(
21    futex: *const AtomicI32,
22    syscall: libc::c_long,
23    operation: libc::c_int,
24    timeout: *const libc::c_void,
25) -> Result<libc::c_long, libc::c_int> {
26    let result = unsafe {
27        libc::syscall(
28            syscall,
29            futex.cast_mut().cast::<u32>(),
30            operation,
31            1,
32            timeout.cast_mut().cast::<libc::timespec>(),
33        )
34    };
35    if result == -1 {
36        Err(errno())
37    } else {
38        Ok(result)
39    }
40}
41
42// The futex timeout uses the kernel syscall ABI, which may differ from libc's
43// `timespec` ABI. Native 64-bit, x32 and futex_time64 syscalls use 64-bit
44// storage slots. Legacy 32-bit futex syscalls use two 32-bit fields.
45cfg_select! {
46    target_arch = "m68k" => {
47        const SYS_FUTEX_TIME32: Option<libc::c_long> = Some(libc::SYS_futex_time32);
48        const SYS_FUTEX_TIME64: Option<libc::c_long> = Some(libc::SYS_futex as libc::c_long);
49    }
50    any(target_arch = "mips", target_arch = "mips32r6") => {
51        const SYS_FUTEX_TIME32: Option<libc::c_long> = Some(libc::SYS_futex as libc::c_long);
52        // MIPS syscall numbers include the o32 ABI base.
53        const SYS_FUTEX_TIME64: Option<libc::c_long> = Some(4000 + 422);
54    }
55    any(target_arch = "hexagon", target_arch = "riscv32") => {
56        const SYS_FUTEX_TIME32: Option<libc::c_long> = None;
57        const SYS_FUTEX_TIME64: Option<libc::c_long> = Some(422);
58    }
59    all(target_pointer_width = "32", not(target_arch = "x86_64")) => {
60        const SYS_FUTEX_TIME32: Option<libc::c_long> = Some(libc::SYS_futex as libc::c_long);
61        const SYS_FUTEX_TIME64: Option<libc::c_long> = Some(422);
62    }
63    _ => {
64        const SYS_FUTEX_TIME32: Option<libc::c_long> = None;
65        const SYS_FUTEX_TIME64: Option<libc::c_long> = Some(libc::SYS_futex as libc::c_long);
66    }
67}
68
69const _: () = if !(SYS_FUTEX_TIME32.is_some() || SYS_FUTEX_TIME64.is_some()) {
    ::core::panicking::panic("assertion failed: SYS_FUTEX_TIME32.is_some() || SYS_FUTEX_TIME64.is_some()")
}assert!(SYS_FUTEX_TIME32.is_some() || SYS_FUTEX_TIME64.is_some());
70
71#[repr(C)]
72struct Timespec32 {
73    tv_sec: i32,
74    tv_nsec: i32,
75}
76
77#[repr(C)]
78struct Timespec64 {
79    tv_sec: i64,
80    tv_nsec: i64,
81}
82
83// Prefer the time64 syscall when both variants are available. Kernel syscall
84// support is process-wide, so cache an ENOSYS result without increasing the
85// size of each ThreadParker.
86static FUTEX_TIME64_SUPPORTED: AtomicBool = AtomicBool::new(true);
87
88#[inline]
89fn futex_syscall(
90    futex: *const AtomicI32,
91    operation: libc::c_int,
92    timeout32: *const libc::c_void,
93    timeout64: *const libc::c_void,
94) -> Result<libc::c_long, libc::c_int> {
95    match (SYS_FUTEX_TIME32, SYS_FUTEX_TIME64) {
96        (Some(time32), Some(time64)) => {
97            if FUTEX_TIME64_SUPPORTED.load(Ordering::Relaxed) {
98                match raw_futex_syscall(futex, time64, operation, timeout64) {
99                    Err(libc::ENOSYS) => {
100                        FUTEX_TIME64_SUPPORTED.store(false, Ordering::Relaxed);
101                    }
102                    result => return result,
103                }
104            }
105            raw_futex_syscall(futex, time32, operation, timeout32)
106        }
107        (Some(time32), None) => raw_futex_syscall(futex, time32, operation, timeout32),
108        (None, Some(time64)) => raw_futex_syscall(futex, time64, operation, timeout64),
109        (None, None) => ::core::panicking::unreachable_default_msg()unreachable!(),
110    }
111}
112
113// Helper type for putting a thread to sleep until some other thread wakes it up
114pub struct ThreadParker {
115    futex: AtomicI32,
116}
117
118impl super::ThreadParkerT for ThreadParker {
119    type UnparkHandle = UnparkHandle;
120
121    const IS_CHEAP_TO_CONSTRUCT: bool = true;
122
123    #[inline]
124    fn new() -> ThreadParker {
125        ThreadParker {
126            futex: AtomicI32::new(0),
127        }
128    }
129
130    #[inline]
131    unsafe fn prepare_park(&self) {
132        self.futex.store(1, Ordering::Relaxed);
133    }
134
135    #[inline]
136    unsafe fn timed_out(&self) -> bool {
137        self.futex.load(Ordering::Relaxed) != 0
138    }
139
140    #[inline]
141    unsafe fn park(&self) {
142        while self.futex.load(Ordering::Acquire) != 0 {
143            self.futex_wait(None);
144        }
145    }
146
147    #[inline]
148    unsafe fn park_until(&self, timeout: Instant) -> bool {
149        while self.futex.load(Ordering::Acquire) != 0 {
150            let now = Instant::now();
151            if timeout <= now {
152                return false;
153            }
154            let diff = timeout - now;
155            self.futex_wait(Some(diff));
156        }
157        true
158    }
159
160    // Marks the thread as unparked while holding the queue lock. A late futex
161    // wake remains harmless even if the target's ThreadData has been freed.
162    #[inline]
163    unsafe fn unpark_lock(&self) -> UnparkHandle {
164        // The target may destroy the parker as soon as the store below is
165        // observed, so construct the handle first.
166        let handle = UnparkHandle {
167            futex: NonNull::from(&self.futex),
168        };
169
170        // We don't need to lock anything, just clear the state
171        self.futex.store(0, Ordering::Release);
172        handle
173    }
174}
175
176impl ThreadParker {
177    #[inline]
178    fn futex_wait(&self, timeout: Option<Duration>) {
179        let timed = timeout.is_some();
180        let ts32 = timeout.map(|timeout| Timespec32 {
181            tv_sec: i32::try_from(timeout.as_secs()).unwrap_or(i32::MAX),
182            tv_nsec: timeout.subsec_nanos() as i32,
183        });
184        let ts64 = timeout.map(|timeout| Timespec64 {
185            tv_sec: i64::try_from(timeout.as_secs()).unwrap_or(i64::MAX),
186            tv_nsec: i64::from(timeout.subsec_nanos()),
187        });
188        let ts32_ptr = match &ts32 {
189            Some(ts) => ptr::from_ref(ts).cast(),
190            None => ptr::null(),
191        };
192        let ts64_ptr = match &ts64 {
193            Some(ts) => ptr::from_ref(ts).cast(),
194            None => ptr::null(),
195        };
196        let result = futex_syscall(
197            &self.futex,
198            libc::FUTEX_WAIT | libc::FUTEX_PRIVATE_FLAG,
199            ts32_ptr,
200            ts64_ptr,
201        );
202
203        match result {
204            Ok(0) | Err(libc::EINTR) | Err(libc::EAGAIN) => {}
205            Err(libc::ETIMEDOUT) if timed => {}
206            Ok(result) => {
    ::core::panicking::panic_fmt(format_args!("unexpected futex wait result: {0}",
            result));
}panic!("unexpected futex wait result: {result}"),
207            Err(error) => {
    ::core::panicking::panic_fmt(format_args!("futex wait failed with error {0}",
            error));
}panic!("futex wait failed with error {error}"),
208        }
209    }
210}
211
212pub struct UnparkHandle {
213    futex: NonNull<AtomicI32>,
214}
215
216impl super::UnparkHandleT for UnparkHandle {
217    #[inline]
218    unsafe fn unpark(self) {
219        // The thread data may have been freed at this point, but it doesn't
220        // matter since the syscall will just return EFAULT in that case.
221        let result = futex_syscall(
222            self.futex.as_ptr(),
223            libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG,
224            ptr::null(),
225            ptr::null(),
226        );
227
228        match result {
229            Ok(0) | Ok(1) | Err(libc::EFAULT) => {}
230            Ok(result) => {
    ::core::panicking::panic_fmt(format_args!("unexpected futex wake result: {0}",
            result));
}panic!("unexpected futex wake result: {result}"),
231            Err(error) => {
    ::core::panicking::panic_fmt(format_args!("futex wake failed with error {0}",
            error));
}panic!("futex wake failed with error {error}"),
232        }
233    }
234}
235
236#[inline]
237pub fn thread_yield() {
238    thread::yield_now();
239}