use std::sync::{
Arc,
atomic::{AtomicU8, Ordering},
};
use crate::num::{Bytes, IdLimit};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub(crate) struct Tag(u8);
impl Tag {
pub(crate) const FROZEN: Self = Self::new(u8::MAX);
pub(crate) const PUBLISHED: Self = Self::new(u8::MAX - 1);
pub(crate) const AVAILABLE: Self = Self::new(0);
pub(crate) const OWNED: Self = Self::new(1);
pub(crate) const RETIRING: Self = Self::new(2);
const RESERVED: Self = Self::RETIRING;
#[must_use = "this function has no side-effects"]
pub(crate) fn is_reserved(self) -> bool {
(self <= Self::RESERVED) || (self == Self::FROZEN)
}
#[must_use = "this function has no side-effects"]
pub(crate) fn can_read(self) -> bool {
self >= Self::PUBLISHED
}
#[inline]
const fn new(value: u8) -> Self {
Self(value)
}
#[inline]
const fn value(self) -> u8 {
self.0
}
}
impl std::fmt::Display for Tag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let me = *self;
if me == Self::AVAILABLE {
f.write_str("Tag(AVAILABLE)")
} else if me == Self::OWNED {
f.write_str("Tag(OWNED)")
} else if me == Self::RETIRING {
f.write_str("Tag(RETIRING)")
} else if me == Self::FROZEN {
f.write_str("Tag(FROZEN)")
} else if me == Self::PUBLISHED {
f.write_str("Tag(PUBLISHED)")
} else {
write!(f, "Tag({})", me.value())
}
}
}
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct AtomicTag(AtomicU8);
impl AtomicTag {
pub(crate) const SIZE: Bytes = Bytes::size_of::<Self>();
pub(crate) const fn new(tag: Tag) -> Self {
Self(AtomicU8::new(tag.value()))
}
pub(crate) unsafe fn from_ptr<'a>(ptr: *mut AtomicTag) -> &'a Self {
unsafe { &*ptr }
}
pub(crate) fn compare_exchange(
&self,
current: Tag,
new: Tag,
success: Ordering,
failure: Ordering,
) -> Result<Tag, Tag> {
self.0
.compare_exchange(current.value(), new.value(), success, failure)
.map(Tag::new)
.map_err(Tag::new)
}
pub(crate) fn load(&self, ordering: Ordering) -> Tag {
Tag::new(self.0.load(ordering))
}
pub(crate) fn store(&self, val: Tag, ordering: Ordering) {
self.0.store(val.value(), ordering)
}
}
#[derive(Debug)]
pub(crate) struct Authoritative {
tags: Arc<[AtomicTag]>,
}
impl Authoritative {
pub(crate) fn new(id_limit: IdLimit) -> Self {
Self {
tags: std::iter::repeat_n(Tag::AVAILABLE, id_limit.as_usize())
.map(AtomicTag::new)
.collect(),
}
}
pub(crate) fn id_limit(&self) -> IdLimit {
IdLimit::new(self.len() as u32)
}
#[cfg(any(test, feature = "quantization", feature = "integration-test"))]
pub(crate) fn read_only(&self) -> ReadOnly {
ReadOnly::new(self.tags.clone())
}
}
impl std::ops::Deref for Authoritative {
type Target = [AtomicTag];
fn deref(&self) -> &[AtomicTag] {
&self.tags
}
}
#[cfg(any(test, feature = "quantization", feature = "integration-test"))]
#[derive(Debug, Clone)]
pub(crate) struct ReadOnly {
tags: Arc<[AtomicTag]>,
}
#[cfg(any(test, feature = "quantization", feature = "integration-test"))]
impl ReadOnly {
fn new(tags: Arc<[AtomicTag]>) -> Self {
Self { tags }
}
pub(crate) fn id_limit(&self) -> IdLimit {
IdLimit::new(self.tags.len() as u32)
}
#[must_use]
pub(crate) fn can_read(&self, i: usize) -> bool {
self.tags
.get(i)
.map(|tag| tag.load(Ordering::Acquire).can_read())
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{sync::Barrier, thread};
use crate::{
buffer::Buffer,
num::{Align, Bytes},
};
fn spin_decrement(m: &AtomicTag, count: usize) {
for _ in 0..count {
let mut current = m.load(Ordering::Relaxed);
while let Err(c) = m.compare_exchange(
current,
Tag::new(current.value().wrapping_sub(1)),
Ordering::Relaxed,
Ordering::Relaxed,
) {
current = c;
}
}
}
#[test]
fn test_atomic() {
let threads = 4;
let barrier = &Barrier::new(threads);
let buffer =
Buffer::new(1, Bytes::size_of::<AtomicTag>(), Align::of::<AtomicTag>()).unwrap();
let ptr = buffer.get(0).unwrap().as_mut_ptr().cast::<AtomicTag>();
{
let tag = unsafe { AtomicTag::from_ptr(ptr) };
tag.store(Tag::FROZEN, Ordering::Relaxed);
}
let count = 1000;
thread::scope(|s| {
for _ in 0..threads {
s.spawn(|| {
let p = buffer.get(0).unwrap().as_mut_ptr().cast::<AtomicTag>();
let tag = unsafe { AtomicTag::from_ptr(p) };
barrier.wait();
spin_decrement(tag, count);
});
}
});
{
let g = unsafe { AtomicTag::from_ptr(ptr) }.load(Ordering::Relaxed);
assert_eq!(g, Tag::new(u8::MAX.wrapping_sub((count * threads) as u8)));
}
}
#[test]
fn test_is_reserved() {
assert!(Tag::FROZEN.is_reserved());
assert!(!Tag::PUBLISHED.is_reserved());
assert!(Tag::AVAILABLE.is_reserved());
assert!(Tag::OWNED.is_reserved());
assert!(Tag::RETIRING.is_reserved());
}
#[test]
fn test_can_read() {
assert!(Tag::FROZEN.can_read());
assert!(Tag::PUBLISHED.can_read());
assert!(!Tag::AVAILABLE.can_read());
assert!(!Tag::OWNED.can_read());
assert!(!Tag::RETIRING.can_read());
}
#[test]
fn test_display() {
assert_eq!(Tag::AVAILABLE.to_string(), "Tag(AVAILABLE)");
assert_eq!(Tag::OWNED.to_string(), "Tag(OWNED)");
assert_eq!(Tag::RETIRING.to_string(), "Tag(RETIRING)");
assert_eq!(Tag::FROZEN.to_string(), "Tag(FROZEN)");
assert_eq!(Tag::PUBLISHED.to_string(), "Tag(PUBLISHED)");
assert_eq!(Tag::new(Tag::RETIRING.value() + 1).to_string(), "Tag(3)");
assert_eq!(Tag::new(Tag::PUBLISHED.value() - 1).to_string(), "Tag(253)");
}
#[test]
fn test_authoritative() {
let authoritative = Authoritative::new(IdLimit::new(10));
assert_eq!(authoritative.len(), 10);
assert_eq!(authoritative.id_limit(), IdLimit::new(10));
let read_only = authoritative.read_only();
assert_eq!(read_only.id_limit(), IdLimit::new(10));
for (i, tag) in authoritative.iter().enumerate() {
assert_eq!(tag.load(Ordering::Relaxed), Tag::AVAILABLE);
assert!(!read_only.can_read(i));
}
authoritative[5].store(Tag::PUBLISHED, Ordering::Relaxed);
assert!(!read_only.can_read(4));
assert!(read_only.can_read(5));
assert!(!read_only.can_read(6));
authoritative[5].store(Tag::RETIRING, Ordering::Relaxed);
assert!(!read_only.can_read(4));
assert!(!read_only.can_read(5));
assert!(!read_only.can_read(6));
}
}