use std::cell::UnsafeCell;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
#[derive(Debug)]
struct Slot<T> {
data: UnsafeCell<T>,
atomic: AtomicU8,
}
const WRITE: u8 = 0b10000000; const READ: u8 = 0b01111111; const UNLOCKED: u8 = 0b00000000;
impl<T> Slot<T> {
fn new(data: T) -> Self {
Slot {
data: UnsafeCell::new(data),
atomic: AtomicU8::new(UNLOCKED), }
}
fn try_read(&self) -> Option<T>
where
T: Clone,
{
let r = self
.atomic
.fetch_update(Ordering::AcqRel, Ordering::Relaxed, |value| {
if value & WRITE != 0 {
None
} else if value == READ {
panic!("Maximum number of readers reached");
} else {
Some(value + 1) }
});
match r {
Ok(_lock_value) => {
let data = unsafe { (&*self.data.get()).clone() };
self.atomic.fetch_sub(1, Ordering::Release); Some(data)
}
Err(_) => None,
}
}
fn try_write(&self, data: &T) -> Option<T>
where
T: Clone,
{
let r = self
.atomic
.compare_exchange(UNLOCKED, WRITE, Ordering::Acquire, Ordering::Relaxed);
match r {
Ok(_) => {
let old_data = unsafe { std::ptr::replace(self.data.get(), data.clone()) };
self.atomic.store(UNLOCKED, Ordering::Release); Some(old_data)
}
Err(_) => {
None
}
}
}
}
impl<T> Slot<Option<T>> {
fn take(&self) -> Option<T>
where
T: Clone,
{
loop {
let r = self.try_write(&None);
match r {
Some(old_data) => {
return old_data;
}
None => {
std::hint::spin_loop();
}
}
}
}
}
#[derive(Debug)]
pub struct FlipCard<T> {
data0: Slot<Option<T>>,
data1: Slot<Option<T>>,
read_data_0: AtomicBool,
}
unsafe impl<T: Send> Send for FlipCard<T> {}
unsafe impl<T: Send> Sync for FlipCard<T> {}
impl<T> FlipCard<T> {
pub fn new(data0: T) -> Self {
Self {
data0: Slot::new(Some(data0)),
data1: Slot::new(None), read_data_0: AtomicBool::new(true), }
}
}
impl<T: Default> Default for FlipCard<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: Clone> Clone for FlipCard<T> {
fn clone(&self) -> Self {
Self::new(self.read())
}
}
impl<T> From<T> for FlipCard<T> {
fn from(value: T) -> Self {
Self::new(value)
}
}
impl<T> FlipCard<T> {
pub fn flip_to(&self, data: T) -> T
where
T: Clone,
{
let opt_data = Some(data);
loop {
let read_0 = self.read_data_0.load(Ordering::Relaxed);
if read_0 {
if self.data1.try_write(&opt_data).is_some() {
self.read_data_0.store(false, Ordering::Release);
return self.data0.take().expect("Prior value");
}
} else {
if self.data0.try_write(&opt_data).is_some() {
self.read_data_0.store(true, Ordering::Release);
return self.data1.take().expect("Prior value");
}
}
std::hint::spin_loop();
}
}
pub fn read(&self) -> T
where
T: Clone,
{
loop {
if self.read_data_0.load(Ordering::Acquire) {
if let Some(Some(val)) = self.data0.try_read() {
return val;
}
} else {
if let Some(Some(val)) = self.data1.try_read() {
return val;
}
}
std::hint::spin_loop(); }
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
fn test_basic_operations() {
let card = FlipCard::new(42);
assert_eq!(card.read(), 42);
let old = card.flip_to(100);
assert_eq!(old, 42);
assert_eq!(card.read(), 100);
}
#[test]
fn test_different_types() {
let card = FlipCard::new(String::from("hello"));
assert_eq!(card.read(), "hello");
card.flip_to(String::from("world"));
assert_eq!(card.read(), "world");
let card = FlipCard::new(vec![1, 2, 3]);
assert_eq!(card.read(), vec![1, 2, 3]);
let old = card.flip_to(vec![4, 5, 6]);
assert_eq!(old, vec![1, 2, 3]);
assert_eq!(card.read(), vec![4, 5, 6]);
}
#[test]
fn test_concurrent_reads() {
let card = Arc::new(FlipCard::new(42));
let barrier = Arc::new(Barrier::new(10));
let handles: Vec<_> = (0..10)
.map(|_| {
let card = Arc::clone(&card);
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
card.read()
})
})
.collect();
for handle in handles {
assert_eq!(handle.join().unwrap(), 42);
}
}
#[test]
fn test_concurrent_read_write() {
let card = Arc::new(FlipCard::new(0));
let barrier = Arc::new(Barrier::new(11));
let writer = {
let card = Arc::clone(&card);
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
for i in 1..=100 {
card.flip_to(i);
}
})
};
let readers: Vec<_> = (0..10)
.map(|_| {
let card = Arc::clone(&card);
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
let mut values = Vec::new();
for _ in 0..10 {
values.push(card.read());
}
values
})
})
.collect();
writer.join().unwrap();
for handle in readers {
let values = handle.join().unwrap();
for window in values.windows(2) {
assert!(
window[0] <= window[1],
"Values should be monotonic: {:?}",
values
);
}
}
assert_eq!(card.read(), 100);
}
#[test]
fn test_clone() {
let card1 = FlipCard::new(42);
let card2 = card1.clone();
assert_eq!(card1.read(), 42);
assert_eq!(card2.read(), 42);
card1.flip_to(100);
assert_eq!(card1.read(), 100);
assert_eq!(card2.read(), 42);
card2.flip_to(200);
assert_eq!(card1.read(), 100); assert_eq!(card2.read(), 200);
}
#[test]
fn test_from() {
let card: FlipCard<i32> = 42.into();
assert_eq!(card.read(), 42);
let card = FlipCard::from("hello");
assert_eq!(card.read(), "hello");
}
#[test]
fn test_sequential_writes() {
let card = FlipCard::new(0);
for i in 1..=1000 {
let old = card.flip_to(i);
assert_eq!(old, i - 1);
}
assert_eq!(card.read(), 1000);
}
#[test]
fn test_custom_type() {
#[derive(Clone, Debug, PartialEq)]
struct Config {
name: String,
value: i32,
}
let card = FlipCard::new(Config {
name: "initial".to_string(),
value: 0,
});
let config = card.read();
assert_eq!(config.name, "initial");
assert_eq!(config.value, 0);
let old = card.flip_to(Config {
name: "updated".to_string(),
value: 42,
});
assert_eq!(old.name, "initial");
assert_eq!(old.value, 0);
let new = card.read();
assert_eq!(new.name, "updated");
assert_eq!(new.value, 42);
}
#[test]
fn reproduce_panic() {
let card = Arc::new(FlipCard::new(0));
let card_clone = card.clone();
let writer = thread::spawn(move || {
for i in 1..1000000 {
card_clone.flip_to(i);
}
});
let reader = thread::spawn(move || {
for _ in 0..1000000 {
let _ = card.read();
}
});
writer.join().unwrap();
reader.join().unwrap();
}
}