#![expect(
clippy::panic,
clippy::expect_used,
reason = "integration-test code is not production code"
)]
use std::{
assert_matches,
sync::atomic::{AtomicBool, Ordering},
};
use diskann::utils::IntoUsize;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::{epoch, num::IdLimit, tag};
use super::{Lifecycle, slots};
#[derive(Debug, Default)]
enum State {
#[default]
Available,
Published {
value: u64,
},
Frozen {
value: u64,
},
}
#[derive(Debug, Default)]
struct Entry {
readable: AtomicBool,
state: RwLock<State>,
}
impl Entry {
#[must_use]
fn is_readable(&self) -> bool {
self.readable.load(Ordering::Acquire)
}
fn try_read(&self) -> Option<ReadEntry<'_>> {
if self.is_readable() {
Some(self.expect_read())
} else {
None
}
}
fn expect_read(&self) -> ReadEntry<'_> {
let Some(guard) = self.state.try_read() else {
panic!("concurrency violation when acquiring read guard");
};
ReadEntry {
readable: &self.readable,
guard,
}
}
fn expect_write(&self) -> WriteEntry<'_> {
assert!(
!self.is_readable(),
"concurrency violation - entry should not be readable"
);
let Some(guard) = self.state.try_write() else {
panic!("concurrency violation when acquiring write guard");
};
WriteEntry {
readable: &self.readable,
guard,
}
}
}
#[derive(Debug)]
struct ReadEntry<'a> {
readable: &'a AtomicBool,
guard: RwLockReadGuard<'a, State>,
}
impl ReadEntry<'_> {
fn retire(self) {
assert_matches!(
*self.guard,
State::Published { .. },
"\"retire\" should transition out of the \"published\" state",
);
let old = self.readable.swap(false, Ordering::Release);
assert!(
old,
"\"retire\" should transition out of the \"published\" state"
);
}
fn state(&self) -> &State {
&self.guard
}
}
#[derive(Debug)]
struct WriteEntry<'a> {
readable: &'a AtomicBool,
guard: RwLockWriteGuard<'a, State>,
}
impl WriteEntry<'_> {
fn publish(mut self, value: u64) {
let old = self.replace(State::Published { value });
assert_matches!(
old,
State::Available,
"\"publish\" must transition out of the \"available\" state",
);
drop(self.guard);
self.readable.store(true, Ordering::Release);
}
fn freeze(mut self, value: u64) {
let old = self.replace(State::Frozen { value });
assert_matches!(
old,
State::Available,
"\"freeze\" must transition out of the \"available\" state",
);
drop(self.guard);
self.readable.store(true, Ordering::Release);
}
fn reclaim(mut self) {
let old = self.replace(State::Available);
assert!(
!self.readable.load(Ordering::Relaxed),
"\"reclaim\" must transition out of \"retired\"",
);
assert_matches!(
old,
State::Published { .. },
"\"reclaim\" must transition out of \"retired\"",
);
}
fn replace(&mut self, mut state: State) -> State {
std::mem::swap(&mut *self.guard, &mut state);
state
}
fn state(&self) -> &State {
&self.guard
}
}
#[derive(Debug)]
pub(crate) struct Config(());
impl Config {
pub(crate) fn new() -> Self {
Self(())
}
}
impl slots::SlotsConfig for Config {
type Slots = Checked;
type Error = diskann::error::Infallible;
unsafe fn build(
self,
handle: epoch::RegistryHandle,
tags: &tag::Authoritative,
) -> Result<Checked, diskann::error::Infallible> {
Ok(Checked::new(handle, tags.id_limit()))
}
}
#[derive(Debug)]
pub(crate) struct Checked {
entries: Vec<Entry>,
handle: epoch::RegistryHandle,
}
impl Checked {
pub(crate) fn new(handle: epoch::RegistryHandle, id_limit: IdLimit) -> Self {
Self {
entries: std::iter::repeat_with(Entry::default)
.take(id_limit.as_usize())
.collect(),
handle,
}
}
pub(crate) fn config() -> Config {
Config::new()
}
pub(crate) fn id_limit(&self) -> IdLimit {
IdLimit::new(self.entries.len() as u32)
}
pub(crate) fn reader<'a>(&'a self, guard: epoch::Guard<'a>) -> Reader<'a> {
self.handle.assert_guard_belongs(&guard);
Reader {
parent: self,
_guard: guard,
}
}
}
#[derive(Debug)]
pub(crate) struct Value<'a> {
value: u64,
_entry: ReadEntry<'a>,
}
impl Value<'_> {
pub(crate) fn get(&self) -> u64 {
self.value
}
}
#[derive(Debug)]
pub(crate) struct Reader<'a> {
parent: &'a Checked,
_guard: epoch::Guard<'a>,
}
impl Reader<'_> {
pub(crate) fn read(&self, i: u32) -> Option<Value<'_>> {
if let Some(entry) = self.parent.entries.get(i.into_usize())?.try_read() {
let value = match entry.state() {
State::Frozen { value } | State::Published { value } => value,
State::Available => panic!("concurrency violation"),
};
Some(Value {
value: *value,
_entry: entry,
})
} else {
None
}
}
}
impl slots::Slots for Checked {
type Exclusive<'a> = Exclusive<'a>;
fn id_limit(&self) -> IdLimit {
<Checked>::id_limit(self)
}
unsafe fn acquire(&self, i: u32, _: Lifecycle) -> Self::Exclusive<'_> {
Exclusive::new(self.entries[i.into_usize()].expect_write())
}
unsafe fn retire(&self, i: u32, _: Lifecycle) {
self.entries[i.into_usize()].expect_read().retire();
}
unsafe fn reclaim(&self, i: u32, _: Lifecycle) {
self.entries[i.into_usize()].expect_write().reclaim();
}
}
#[derive(Debug)]
pub(crate) struct Exclusive<'a> {
entry: WriteEntry<'a>,
value: Option<u64>,
}
impl<'a> Exclusive<'a> {
fn new(entry: WriteEntry<'a>) -> Self {
Self { entry, value: None }
}
pub(crate) fn set(&mut self, value: u64) {
self.value = Some(value)
}
}
impl slots::Exclusive for Exclusive<'_> {
fn publish(self, _: Lifecycle) {
let value = self.value.expect("`value` was not set");
self.entry.publish(value);
}
fn freeze(self, _: Lifecycle) {
let value = self.value.expect("`value` was not set");
self.entry.freeze(value);
}
fn abort(self, _: Lifecycle) {
assert_matches!(self.entry.state(), State::Available);
}
}