use super::{lock_unpoisoned, SegmentCacheShard};
use std::fmt;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, Weak};
pub struct SegmentBytes {
payload: Arc<SegmentPayload>,
}
#[derive(Debug)]
pub(super) struct SegmentPayload {
bytes: Box<[u8]>,
pin: Option<ResidentPin>,
}
#[derive(Debug)]
struct ResidentPin {
shard: Weak<Mutex<SegmentCacheShard>>,
external_handles: AtomicUsize,
accounted: AtomicBool,
}
impl SegmentPayload {
pub(super) fn cached(bytes: Vec<u8>, shard: Weak<Mutex<SegmentCacheShard>>) -> Arc<Self> {
Arc::new(Self {
bytes: bytes.into_boxed_slice(),
pin: Some(ResidentPin {
shard,
external_handles: AtomicUsize::new(0),
accounted: AtomicBool::new(false),
}),
})
}
pub(super) fn data(&self) -> &[u8] {
&self.bytes
}
pub(super) fn lease(self: &Arc<Self>) -> (SegmentBytes, u64) {
let payload = Arc::clone(self);
let pin = self.pin.as_ref().expect("resident payload has pin state");
let previous = pin.external_handles.fetch_add(1, Ordering::Relaxed);
let charge = if previous == 0 && !pin.accounted.swap(true, Ordering::Relaxed) {
self.bytes.len() as u64
} else {
0
};
(SegmentBytes { payload }, charge)
}
pub(super) fn is_pinned(&self) -> bool {
self.pin
.as_ref()
.expect("resident payload has pin state")
.accounted
.load(Ordering::Relaxed)
}
}
impl Clone for SegmentBytes {
fn clone(&self) -> Self {
let payload = Arc::clone(&self.payload);
if let Some(pin) = &payload.pin {
pin.external_handles.fetch_add(1, Ordering::Relaxed);
}
Self { payload }
}
}
impl Drop for SegmentBytes {
fn drop(&mut self) {
let Some(pin) = &self.payload.pin else {
return;
};
if pin.external_handles.fetch_sub(1, Ordering::AcqRel) != 1 {
return;
}
if let Some(shard) = pin.shard.upgrade() {
let mut shard = lock_unpoisoned(&shard);
if pin.external_handles.load(Ordering::Acquire) == 0
&& pin.accounted.swap(false, Ordering::Relaxed)
{
shard.pinned_bytes = shard
.pinned_bytes
.saturating_sub(self.payload.bytes.len() as u64);
}
}
}
}
impl From<Vec<u8>> for SegmentBytes {
fn from(bytes: Vec<u8>) -> Self {
Self {
payload: Arc::new(SegmentPayload {
bytes: bytes.into_boxed_slice(),
pin: None,
}),
}
}
}
impl From<Box<[u8]>> for SegmentBytes {
fn from(bytes: Box<[u8]>) -> Self {
Self {
payload: Arc::new(SegmentPayload { bytes, pin: None }),
}
}
}
impl Deref for SegmentBytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.payload.bytes
}
}
impl AsRef<[u8]> for SegmentBytes {
fn as_ref(&self) -> &[u8] {
self
}
}
impl PartialEq for SegmentBytes {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.payload, &other.payload) || self.as_ref() == other.as_ref()
}
}
impl Eq for SegmentBytes {}
impl fmt::Debug for SegmentBytes {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_ref().fmt(formatter)
}
}
#[cfg(test)]
mod tests;