#![deny(missing_docs)]
use std::sync::atomic::{
AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16,
AtomicU32, AtomicU64, AtomicU8, AtomicUsize, Ordering,
};
#[derive(Debug, Default)]
pub struct RaceCell<T: AtomicData> {
local_contents: T::AtomicWrapper,
remote_version: Box<T::AtomicWrapper>,
}
impl<T: AtomicData> RaceCell<T> {
pub fn new(value: T) -> Self {
RaceCell {
local_contents: T::AtomicWrapper::new(value.clone()),
remote_version: Box::new(T::AtomicWrapper::new(value)),
}
}
pub fn set(&self, value: T) {
self.local_contents.relaxed_store(value.clone());
self.remote_version.relaxed_store(value);
}
pub fn get(&self) -> Racey<T> {
let local_data = self.local_contents.relaxed_load();
let remote_data = self.remote_version.relaxed_load();
if local_data == remote_data {
Racey::Consistent(local_data)
} else {
Racey::Inconsistent
}
}
}
impl<T: AtomicData> Clone for RaceCell<T> {
fn clone(&self) -> Self {
let local_copy = self.local_contents.relaxed_load();
let remote_copy = self.remote_version.relaxed_load();
RaceCell {
local_contents: T::AtomicWrapper::new(local_copy),
remote_version: Box::new(T::AtomicWrapper::new(remote_copy)),
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum Racey<U: AtomicData> {
Consistent(U),
Inconsistent,
}
pub trait AtomicData: Clone + Eq + Sized {
type AtomicWrapper: AtomicLoadStore<Content = Self>;
}
pub trait AtomicLoadStore: Sized {
type Content: AtomicData<AtomicWrapper = Self>;
fn new(v: Self::Content) -> Self;
fn relaxed_load(&self) -> Self::Content;
fn relaxed_store(&self, val: Self::Content);
}
macro_rules! impl_atomic_data {
($($data:ty => $wrapper:ty),*) => ($(
impl AtomicData for $data {
type AtomicWrapper = $wrapper;
}
impl AtomicLoadStore for $wrapper {
type Content = $data;
fn new(v: $data) -> $wrapper {
<$wrapper>::new(v)
}
fn relaxed_load(&self) -> $data {
<$wrapper>::load(self, Ordering::Relaxed)
}
fn relaxed_store(&self, val: $data) {
<$wrapper>::store(self, val, Ordering::Relaxed)
}
}
)*)
}
impl_atomic_data! {
bool => AtomicBool,
i8 => AtomicI8,
i16 => AtomicI16,
i32 => AtomicI32,
i64 => AtomicI64,
isize => AtomicIsize,
u8 => AtomicU8,
u16 => AtomicU16,
u32 => AtomicU32,
u64 => AtomicU64,
usize => AtomicUsize
}
impl<V> AtomicData for *mut V {
type AtomicWrapper = AtomicPtr<V>;
}
impl<V> AtomicLoadStore for AtomicPtr<V> {
type Content = *mut V;
fn new(v: *mut V) -> AtomicPtr<V> {
<AtomicPtr<V>>::new(v)
}
fn relaxed_load(&self) -> *mut V {
<AtomicPtr<V>>::load(self, Ordering::Relaxed)
}
fn relaxed_store(&self, val: *mut V) {
<AtomicPtr<V>>::store(self, val, Ordering::Relaxed)
}
}
#[cfg(test)]
mod tests {
use super::{AtomicLoadStore, RaceCell, Racey};
use std::sync::Mutex;
#[test]
fn initial_state() {
let cell = RaceCell::new(true);
assert!(cell.local_contents.relaxed_load());
assert!(cell.remote_version.relaxed_load());
}
#[test]
fn consistent_read() {
let cell = RaceCell::new(-42_isize);
assert_eq!(cell.get(), Racey::Consistent(-42));
}
#[test]
fn inconsistent_read() {
let cell = RaceCell::new(0xbad_usize);
cell.local_contents.relaxed_store(0xdead);
assert_eq!(cell.get(), Racey::Inconsistent);
}
#[test]
fn clone() {
let cell = RaceCell::new(0xbeef_usize);
cell.local_contents.relaxed_store(0xdeaf);
let clone = cell.clone();
assert_eq!(clone.local_contents.relaxed_load(), 0xdeaf);
assert_eq!(clone.remote_version.relaxed_load(), 0xbeef);
}
#[test]
#[ignore]
fn unprotected_race() {
const WRITES_COUNT: usize = 100_000_000;
let cell = RaceCell::new(0);
crate::concurrent_test_2(
|| {
for i in 1..=WRITES_COUNT {
cell.set(i);
}
},
|| {
let mut last_value = 0;
let mut data_race_count = 0usize;
while last_value != WRITES_COUNT {
match cell.get() {
Racey::Consistent(value) => last_value = value,
Racey::Inconsistent => data_race_count += 1,
}
}
print!("{} races detected: ", data_race_count);
assert!(data_race_count > WRITES_COUNT / 100);
},
);
}
#[test]
#[ignore]
fn protected_transaction() {
const WRITES_COUNT: usize = 10_000_000;
let cell = Mutex::new(RaceCell::new(0));
crate::concurrent_test_2(
|| {
for i in 1..=WRITES_COUNT {
cell.lock().unwrap().set(i);
}
},
|| {
let mut last_value = 0;
let mut data_race_count = 0usize;
while last_value != WRITES_COUNT {
match cell.lock().unwrap().get() {
Racey::Consistent(value) => last_value = value,
Racey::Inconsistent => data_race_count += 1,
}
}
assert_eq!(data_race_count, 0);
},
);
}
}