use std::sync::Arc;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::time::{Duration, Instant};
use bytes::Bytes;
use dashmap::DashMap;
use crate::observability::VeloMetrics;
use crate::rendezvous::protocol::DataMetadata;
pub const DEFAULT_CHUNK_SIZE: u32 = 512 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StageMode {
InMemory,
Pinned,
}
pub(crate) enum SlotBody {
InMemory(Bytes),
#[cfg(all(target_os = "linux", feature = "ucx"))]
Pinned(super::pinned::PinnedSlot),
}
impl SlotBody {
pub(crate) fn stage_mode(&self) -> StageMode {
match self {
Self::InMemory(_) => StageMode::InMemory,
#[cfg(all(target_os = "linux", feature = "ucx"))]
Self::Pinned(slot) => slot.stage_mode(),
}
}
pub(crate) fn total_len(&self) -> u64 {
match self {
Self::InMemory(data) => data.len() as u64,
#[cfg(all(target_os = "linux", feature = "ucx"))]
Self::Pinned(slot) => slot.len(),
}
}
fn read_at(&self, offset: u64, len: usize) -> Option<Bytes> {
match self {
Self::InMemory(data) => {
let start = usize::try_from(offset).ok()?;
let end = start.checked_add(len)?;
if end > data.len() {
return None;
}
Some(data.slice(start..end))
}
#[cfg(all(target_os = "linux", feature = "ucx"))]
Self::Pinned(slot) => slot.read_at(offset, len),
}
}
fn to_bytes(&self) -> Option<Bytes> {
match self {
Self::InMemory(data) => Some(data.clone()),
#[cfg(all(target_os = "linux", feature = "ucx"))]
Self::Pinned(slot) => slot.to_bytes(),
}
}
}
pub(crate) struct DataSlot {
pub body: SlotBody,
pub refcount: AtomicU32,
pub read_lock_count: AtomicU32,
pub total_len: u64,
#[allow(dead_code)]
pub created_at: Instant,
#[allow(dead_code)]
pub ttl: Option<Duration>,
}
#[allow(dead_code)]
pub(crate) struct TransferState {
pub slot_local_id: u64,
pub lease_id: u64,
pub chunk_size: u32,
pub chunk_count: u32,
pub created_at: Instant,
}
#[derive(Clone, Copy, Debug)]
struct LeaseDeadline {
#[cfg_attr(not(all(target_os = "linux", feature = "ucx")), allow(dead_code))]
local_id: u64,
expires_at: Instant,
timeout: Duration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LeaseOutcome {
Consumed,
Mismatch {
actual: u64,
},
Unknown,
}
#[derive(Debug, Clone)]
pub struct RegisterOptions {
pub ttl: Option<Duration>,
}
impl RegisterOptions {
pub fn new() -> Self {
Self { ttl: None }
}
pub fn ttl(mut self, ttl: Duration) -> Self {
self.ttl = Some(ttl);
self
}
}
impl Default for RegisterOptions {
fn default() -> Self {
Self::new()
}
}
pub struct DataStore {
next_id: AtomicU64,
pub(crate) slots: DashMap<u64, DataSlot>,
pub(crate) transfers: DashMap<u64, TransferState>,
next_transfer_id: AtomicU64,
next_lease_id: AtomicU64,
active_leases: DashMap<u64, u64>,
lease_deadlines: DashMap<u64, LeaseDeadline>,
#[cfg_attr(not(all(target_os = "linux", feature = "ucx")), allow(dead_code))]
metrics: Option<Arc<VeloMetrics>>,
#[cfg(all(target_os = "linux", feature = "ucx"))]
rdma: std::sync::OnceLock<super::RdmaContext>,
}
impl DataStore {
pub fn new() -> Self {
Self {
next_id: AtomicU64::new(1),
slots: DashMap::new(),
transfers: DashMap::new(),
next_transfer_id: AtomicU64::new(1),
next_lease_id: AtomicU64::new(1),
active_leases: DashMap::new(),
lease_deadlines: DashMap::new(),
metrics: None,
#[cfg(all(target_os = "linux", feature = "ucx"))]
rdma: std::sync::OnceLock::new(),
}
}
pub(crate) fn with_metrics(metrics: Option<Arc<VeloMetrics>>) -> Self {
Self {
metrics,
..Self::new()
}
}
#[cfg_attr(not(all(target_os = "linux", feature = "ucx")), allow(dead_code))]
pub(crate) fn metrics(&self) -> Option<&Arc<VeloMetrics>> {
self.metrics.as_ref()
}
#[cfg(all(target_os = "linux", feature = "ucx"))]
pub(crate) fn set_rdma(&self, ctx: super::RdmaContext) -> Result<(), super::RdmaContext> {
self.rdma.set(ctx)
}
#[cfg(all(target_os = "linux", feature = "ucx"))]
pub(crate) fn rdma(&self) -> Option<&super::RdmaContext> {
self.rdma.get()
}
#[cfg(all(target_os = "linux", feature = "ucx"))]
pub(crate) fn record_path(&self, reason: crate::observability::RdmaPathReason) {
if let Some(m) = &self.metrics {
m.record_rendezvous_rdma_path(reason);
}
}
pub fn register(&self, data: Bytes, opts: Option<RegisterOptions>) -> u64 {
self.register_body(SlotBody::InMemory(data), opts)
}
pub(crate) fn register_body(&self, body: SlotBody, opts: Option<RegisterOptions>) -> u64 {
let local_id = self.next_id.fetch_add(1, Ordering::Relaxed);
let total_len = body.total_len();
let ttl = opts.as_ref().and_then(|o| o.ttl);
self.slots.insert(
local_id,
DataSlot {
body,
refcount: AtomicU32::new(1),
read_lock_count: AtomicU32::new(0),
total_len,
created_at: Instant::now(),
ttl,
},
);
local_id
}
pub fn metadata(&self, local_id: u64) -> Option<DataMetadata> {
self.slots.get(&local_id).map(|slot| DataMetadata {
total_len: slot.total_len,
refcount: slot.refcount.load(Ordering::Relaxed),
pinned: slot.body.stage_mode() == StageMode::Pinned,
})
}
pub fn stage_mode(&self, local_id: u64) -> Option<StageMode> {
self.slots.get(&local_id).map(|slot| slot.body.stage_mode())
}
#[cfg(all(target_os = "linux", feature = "ucx"))]
pub(crate) fn with_pinned<R>(
&self,
local_id: u64,
f: impl FnOnce(&super::pinned::PinnedSlot) -> R,
) -> Option<R> {
let slot = self.slots.get(&local_id)?;
match &slot.body {
SlotBody::Pinned(pinned) => Some(f(pinned)),
SlotBody::InMemory(_) => None,
}
}
pub fn acquire_read_lock(&self, local_id: u64) -> Option<u64> {
let slot = self.slots.get(&local_id)?;
slot.read_lock_count.fetch_add(1, Ordering::Relaxed);
let lease_id = self.next_lease_id.fetch_add(1, Ordering::Relaxed);
self.active_leases.insert(lease_id, local_id);
Some(lease_id)
}
pub fn consume_lease(&self, lease_id: u64, expected_local_id: u64) -> LeaseOutcome {
if self
.active_leases
.remove_if(&lease_id, |_, held| *held == expected_local_id)
.is_some()
{
self.lease_deadlines.remove(&lease_id);
return LeaseOutcome::Consumed;
}
match self
.active_leases
.get(&lease_id)
.map(|entry| *entry.value())
{
Some(actual) => LeaseOutcome::Mismatch { actual },
None => LeaseOutcome::Unknown,
}
}
pub(crate) fn lease_slot(&self, lease_id: u64) -> Option<u64> {
self.active_leases
.get(&lease_id)
.map(|entry| *entry.value())
}
#[cfg_attr(not(all(target_os = "linux", feature = "ucx")), allow(dead_code))]
pub(crate) fn set_lease_deadline(&self, lease_id: u64, local_id: u64, timeout: Duration) {
self.lease_deadlines.insert(
lease_id,
LeaseDeadline {
local_id,
expires_at: Instant::now() + timeout,
timeout,
},
);
}
pub(crate) fn renew_lease(&self, lease_id: u64) -> bool {
match self.lease_deadlines.get_mut(&lease_id) {
Some(mut entry) => {
entry.expires_at = Instant::now() + entry.timeout;
true
}
None => false,
}
}
#[cfg_attr(not(all(target_os = "linux", feature = "ucx")), allow(dead_code))]
pub(crate) fn expired_leases(&self, now: Instant) -> Vec<(u64, u64)> {
self.lease_deadlines
.iter()
.filter(|entry| entry.value().expires_at <= now)
.map(|entry| (*entry.key(), entry.value().local_id))
.collect()
}
#[cfg(test)]
pub(crate) fn deadline_count(&self) -> usize {
self.lease_deadlines.len()
}
#[cfg_attr(not(all(target_os = "linux", feature = "ucx")), allow(dead_code))]
pub(crate) fn force_release_lease(&self, lease_id: u64, local_id: u64) -> bool {
if self.consume_lease(lease_id, local_id) != LeaseOutcome::Consumed {
return false;
}
self.release_read_lock(local_id);
self.remove_transfers_by_lease(lease_id);
if self.ref_decrement(local_id) {
self.try_free(local_id);
}
true
}
pub fn release_read_lock(&self, local_id: u64) -> bool {
if let Some(slot) = self.slots.get(&local_id) {
let result =
slot.read_lock_count
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
if v > 0 { Some(v - 1) } else { None }
});
match result {
Ok(prev) => {
let read_locks = prev - 1;
let refcount = slot.refcount.load(Ordering::Relaxed);
read_locks == 0 && refcount == 0
}
Err(_) => {
tracing::warn!(
"release_read_lock: read_lock_count already 0 for slot {local_id}"
);
false
}
}
} else {
false
}
}
pub fn ref_increment(&self, local_id: u64) -> bool {
if let Some(slot) = self.slots.get(&local_id) {
slot.refcount.fetch_add(1, Ordering::Relaxed);
true
} else {
false
}
}
pub fn ref_decrement(&self, local_id: u64) -> bool {
if let Some(slot) = self.slots.get(&local_id) {
let result = slot
.refcount
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
if v > 0 { Some(v - 1) } else { None }
});
match result {
Ok(prev) => {
let refcount = prev - 1;
let read_locks = slot.read_lock_count.load(Ordering::Relaxed);
refcount == 0 && read_locks == 0
}
Err(_) => {
tracing::warn!("ref_decrement: refcount already 0 for slot {local_id}");
false
}
}
} else {
false
}
}
pub fn remove(&self, local_id: u64) -> Option<Bytes> {
self.slots
.remove(&local_id)
.and_then(|(_, slot)| slot.body.to_bytes())
}
pub fn try_free(&self, local_id: u64) {
self.slots.remove_if(&local_id, |_, slot| {
slot.refcount.load(Ordering::Relaxed) == 0
&& slot.read_lock_count.load(Ordering::Relaxed) == 0
});
}
pub fn get_data(&self, local_id: u64) -> Option<Bytes> {
self.slots
.get(&local_id)
.and_then(|slot| slot.body.to_bytes())
}
pub fn get_total_len(&self, local_id: u64) -> Option<u64> {
self.slots.get(&local_id).map(|slot| slot.total_len)
}
pub fn create_transfer(
&self,
local_id: u64,
lease_id: u64,
max_chunk_size: u32,
) -> Option<(u64, u32, u32)> {
let slot = self.slots.get(&local_id)?;
let total_len = slot.total_len;
let chunk_size = max_chunk_size.min(DEFAULT_CHUNK_SIZE);
let chunk_count = total_len.div_ceil(chunk_size as u64) as u32;
let transfer_id = self.next_transfer_id.fetch_add(1, Ordering::Relaxed);
self.transfers.insert(
transfer_id,
TransferState {
slot_local_id: local_id,
lease_id,
chunk_size,
chunk_count,
created_at: Instant::now(),
},
);
Some((transfer_id, chunk_size, chunk_count))
}
pub fn get_chunk(&self, transfer_id: u64, chunk_index: u32) -> Option<Bytes> {
let transfer = self.transfers.get(&transfer_id)?;
let slot = self.slots.get(&transfer.slot_local_id)?;
let offset = chunk_index as u64 * transfer.chunk_size as u64;
if offset >= slot.total_len {
return None;
}
let end = (offset + transfer.chunk_size as u64).min(slot.total_len);
slot.body.read_at(offset, (end - offset) as usize)
}
pub fn remove_transfer(&self, transfer_id: u64) {
self.transfers.remove(&transfer_id);
}
#[cfg(all(target_os = "linux", feature = "ucx"))]
pub(crate) fn demote_pinned_slots(&self) -> (usize, usize) {
let pinned: Vec<u64> = self
.slots
.iter()
.filter(|entry| matches!(entry.value().body, SlotBody::Pinned(_)))
.map(|entry| *entry.key())
.collect();
let (mut demoted, mut dropped) = (0usize, 0usize);
for local_id in pinned {
let copied = self.slots.get(&local_id).and_then(|slot| match &slot.body {
SlotBody::Pinned(pinned) => Some(pinned.to_bytes()),
SlotBody::InMemory(_) => None,
});
match copied {
Some(Some(bytes)) => {
if let Some(mut slot) = self.slots.get_mut(&local_id)
&& matches!(slot.body, SlotBody::Pinned(_))
{
slot.body = SlotBody::InMemory(bytes);
demoted += 1;
}
}
Some(None) => {
if self.slots.remove(&local_id).is_some() {
dropped += 1;
}
}
None => {}
}
}
(demoted, dropped)
}
pub fn remove_transfers_by_lease(&self, lease_id: u64) {
self.transfers.retain(|_, state| state.lease_id != lease_id);
}
}
impl Default for DataStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_and_get() {
let store = DataStore::new();
let data = Bytes::from(vec![1u8, 2, 3, 4]);
let id = store.register(data.clone(), None);
assert_eq!(id, 1);
assert_eq!(store.get_data(id).unwrap(), data);
}
#[test]
fn test_metadata() {
let store = DataStore::new();
let data = Bytes::from(vec![0u8; 1024]);
let id = store.register(data, None);
let meta = store.metadata(id).unwrap();
assert_eq!(meta.total_len, 1024);
assert_eq!(meta.refcount, 1);
assert!(!meta.pinned);
assert_eq!(store.stage_mode(id), Some(StageMode::InMemory));
}
#[test]
fn test_ref_counting() {
let store = DataStore::new();
let id = store.register(Bytes::from("hello"), None);
assert_eq!(store.metadata(id).unwrap().refcount, 1);
assert!(store.ref_increment(id));
assert_eq!(store.metadata(id).unwrap().refcount, 2);
assert!(!store.ref_decrement(id));
assert!(store.ref_decrement(id));
store.try_free(id);
assert!(store.metadata(id).is_none());
}
#[test]
fn test_read_lock_prevents_free() {
let store = DataStore::new();
let id = store.register(Bytes::from("data"), None);
let _lease = store.acquire_read_lock(id).unwrap();
let should_free = store.ref_decrement(id);
assert!(!should_free);
let should_free = store.release_read_lock(id);
assert!(should_free);
}
#[test]
fn test_chunked_transfer() {
let store = DataStore::new();
let data = Bytes::from(vec![0xAA; 2000]);
let id = store.register(data, None);
let lease_id = store.acquire_read_lock(id).unwrap();
let (transfer_id, chunk_size, chunk_count) =
store.create_transfer(id, lease_id, 1024).unwrap();
assert_eq!(chunk_size, 1024);
assert_eq!(chunk_count, 2);
let chunk0 = store.get_chunk(transfer_id, 0).unwrap();
assert_eq!(chunk0.len(), 1024);
assert!(chunk0.iter().all(|&b| b == 0xAA));
let chunk1 = store.get_chunk(transfer_id, 1).unwrap();
assert_eq!(chunk1.len(), 976); assert!(chunk1.iter().all(|&b| b == 0xAA));
assert!(store.get_chunk(transfer_id, 2).is_none());
store.remove_transfer(transfer_id);
assert!(store.transfers.get(&transfer_id).is_none());
}
#[test]
fn chunked_leases_never_expire() {
let store = DataStore::new();
let id = store.register(Bytes::from("data"), None);
let lease = store.acquire_read_lock(id).unwrap();
assert_eq!(store.deadline_count(), 0);
assert!(
store
.expired_leases(Instant::now() + Duration::from_secs(3600))
.is_empty(),
"a lease with no deadline must never be reported expired"
);
assert!(!store.renew_lease(lease));
}
#[test]
fn a_deadline_expires_and_renewal_pushes_it_out() {
let store = DataStore::new();
let id = store.register(Bytes::from("data"), None);
let lease = store.acquire_read_lock(id).unwrap();
store.set_lease_deadline(lease, id, Duration::from_secs(3600));
assert!(
store.expired_leases(Instant::now()).is_empty(),
"a fresh deadline is not expired"
);
store.set_lease_deadline(lease, id, Duration::from_millis(0));
assert_eq!(store.expired_leases(Instant::now()), vec![(lease, id)]);
store.set_lease_deadline(lease, id, Duration::from_secs(3600));
assert!(store.renew_lease(lease));
assert!(
store.expired_leases(Instant::now()).is_empty(),
"renewal must push the deadline past now"
);
}
#[test]
fn consuming_a_lease_drops_its_deadline() {
let store = DataStore::new();
let id = store.register(Bytes::from("data"), None);
let lease = store.acquire_read_lock(id).unwrap();
store.set_lease_deadline(lease, id, Duration::from_secs(30));
assert_eq!(store.deadline_count(), 1);
assert_eq!(store.consume_lease(lease, id), LeaseOutcome::Consumed);
assert_eq!(store.deadline_count(), 0);
assert!(!store.renew_lease(lease));
}
#[test]
fn a_mismatched_lease_is_not_consumed_and_stays_reapable() {
let store = DataStore::new();
let mine = store.register(Bytes::from(vec![0u8; 4096]), None);
let theirs = store.register(Bytes::from(vec![1u8; 4096]), None);
let lease = store.acquire_read_lock(mine).unwrap();
store.set_lease_deadline(lease, mine, Duration::from_millis(0));
assert_eq!(
store.consume_lease(lease, theirs),
LeaseOutcome::Mismatch { actual: mine },
"a lease held by another slot must not be consumed"
);
assert_eq!(
store.deadline_count(),
1,
"the mismatch discarded the deadline, blinding the reaper"
);
assert_eq!(
store.lease_slot(lease),
Some(mine),
"the lease must survive"
);
assert_eq!(store.expired_leases(Instant::now()), vec![(lease, mine)]);
assert!(store.force_release_lease(lease, mine));
assert!(
store.metadata(mine).is_none(),
"the reaper must free the slot"
);
assert_eq!(store.consume_lease(lease, mine), LeaseOutcome::Unknown);
assert_eq!(store.consume_lease(9_999, mine), LeaseOutcome::Unknown);
}
#[test]
fn force_release_frees_a_transparent_style_slot() {
let store = DataStore::new();
let id = store.register(Bytes::from(vec![0u8; 4096]), None);
let lease = store.acquire_read_lock(id).unwrap();
let (transfer_id, _, _) = store.create_transfer(id, lease, 1024).unwrap();
store.set_lease_deadline(lease, id, Duration::from_millis(0));
assert!(store.force_release_lease(lease, id));
assert!(store.metadata(id).is_none(), "the slot must be freed");
assert!(store.get_chunk(transfer_id, 0).is_none());
assert_eq!(store.deadline_count(), 0);
assert!(
!store.force_release_lease(lease, id),
"a lease is force-released at most once"
);
}
}