Skip to content

Commit

Permalink
Replace generic ThreadParker with a spin-based one
Browse files Browse the repository at this point in the history
  • Loading branch information
faern committed Dec 7, 2018
1 parent e7e01b7 commit 2d08c43
Showing 1 changed file with 25 additions and 45 deletions.
70 changes: 25 additions & 45 deletions core/src/thread_parker/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,94 +5,74 @@
// 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<bool>,
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
}

// 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) {}
}

0 comments on commit 2d08c43

Please sign in to comment.