From 2d08c43fa1dd2a405745f7c06776031a84fdbe46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20F=C3=A4rnstrand?= Date: Sat, 1 Dec 2018 16:59:50 +0100 Subject: [PATCH] Replace generic ThreadParker with a spin-based one --- core/src/thread_parker/generic.rs | 70 +++++++++++-------------------- 1 file changed, 25 insertions(+), 45 deletions(-) diff --git a/core/src/thread_parker/generic.rs b/core/src/thread_parker/generic.rs index d06406a2..c9205c66 100644 --- a/core/src/thread_parker/generic.rs +++ b/core/src/thread_parker/generic.rs @@ -5,62 +5,53 @@ // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. -use std::cell::Cell; -use std::sync::{Condvar, Mutex, MutexGuard}; +//! A simple spin lock based thread parker. Used on platforms without better +//! parking facilities available. + +use std::sync::atomic::spin_loop_hint; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Instant; // Helper type for putting a thread to sleep until some other thread wakes it up pub struct ThreadParker { - should_park: Cell, - mutex: Mutex<()>, - condvar: Condvar, + parked: AtomicBool, } impl ThreadParker { pub fn new() -> ThreadParker { ThreadParker { - should_park: Cell::new(false), - mutex: Mutex::new(()), - condvar: Condvar::new(), + parked: AtomicBool::new(false), } } // Prepares the parker. This should be called before adding it to the queue. - pub unsafe fn prepare_park(&self) { - self.should_park.set(true); + pub fn prepare_park(&self) { + self.parked.store(true, Ordering::Relaxed); } // Checks if the park timed out. This should be called while holding the // queue lock after park_until has returned false. - pub unsafe fn timed_out(&self) -> bool { - // We need to grab the mutex here because another thread may be - // concurrently executing UnparkHandle::unpark, which is done without - // holding the queue lock. - let _lock = self.mutex.lock().unwrap(); - self.should_park.get() + pub fn timed_out(&self) -> bool { + self.parked.load(Ordering::Relaxed) != false } // Parks the thread until it is unparked. This should be called after it has // been added to the queue, after unlocking the queue. - pub unsafe fn park(&self) { - let mut lock = self.mutex.lock().unwrap(); - while self.should_park.get() { - lock = self.condvar.wait(lock).unwrap(); + pub fn park(&self) { + while self.parked.load(Ordering::Acquire) != false { + spin_loop_hint(); } } // Parks the thread until it is unparked or the timeout is reached. This // should be called after it has been added to the queue, after unlocking // the queue. Returns true if we were unparked and false if we timed out. - pub unsafe fn park_until(&self, timeout: Instant) -> bool { - let mut lock = self.mutex.lock().unwrap(); - while self.should_park.get() { - let now = Instant::now(); - if timeout <= now { + pub fn park_until(&self, timeout: Instant) -> bool { + while self.parked.load(Ordering::Acquire) != false { + if Instant::now() >= timeout { return false; } - let (new_lock, _) = self.condvar.wait_timeout(lock, timeout - now).unwrap(); - lock = new_lock; + spin_loop_hint(); } true } @@ -68,31 +59,20 @@ impl ThreadParker { // Locks the parker to prevent the target thread from exiting. This is // necessary to ensure that thread-local ThreadData objects remain valid. // This should be called while holding the queue lock. - pub unsafe fn unpark_lock(&self) -> UnparkHandle { - UnparkHandle { - thread_parker: self, - _guard: self.mutex.lock().unwrap(), - } + pub fn unpark_lock(&self) -> UnparkHandle { + // We don't need to lock anything, just clear the state + self.parked.store(false, Ordering::Release); + UnparkHandle(()) } } // Handle for a thread that is about to be unparked. We need to mark the thread // as unparked while holding the queue lock, but we delay the actual unparking // until after the queue lock is released. -pub struct UnparkHandle<'a> { - thread_parker: *const ThreadParker, - _guard: MutexGuard<'a, ()>, -} +pub struct UnparkHandle(()); -impl<'a> UnparkHandle<'a> { +impl UnparkHandle { // Wakes up the parked thread. This should be called after the queue lock is // released to avoid blocking the queue for too long. - pub unsafe fn unpark(self) { - (*self.thread_parker).should_park.set(false); - - // We notify while holding the lock here to avoid races with the target - // thread. In particular, the thread could exit after we unlock the - // mutex, which would make the condvar access invalid memory. - (*self.thread_parker).condvar.notify_one(); - } + pub fn unpark(self) {} }