use std::sync::atomic::{AtomicU64, Ordering};
use crate::accounting::checked_atomic_next_u64_with_order;
use crate::backend::error::BackendError;
static NEXT_RESIDENT_OWNER: AtomicU64 = AtomicU64::new(1);
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ResidentOwner(u64);
impl ResidentOwner {
pub fn new() -> Result<Self, BackendError> {
let id = checked_atomic_next_u64_with_order(
&NEXT_RESIDENT_OWNER,
Ordering::Acquire,
Ordering::AcqRel,
Ordering::Acquire,
|_| {
BackendError::InvalidProgram {
fix: "Fix: the process exhausted backend instance identities for resident buffers. Restart the process instead of reusing an identity, which would let a stale resident handle resolve against a live buffer."
.to_string(),
}
},
)?;
Ok(Self(id))
}
#[must_use]
pub fn get(self) -> u64 {
self.0
}
#[must_use]
pub fn handle(self, id: u64) -> ResidentHandle {
ResidentHandle { owner: self, id }
}
pub fn resolve(self, handle: ResidentHandle, context: &str) -> Result<u64, BackendError> {
if handle.owner != self {
return Err(BackendError::InvalidProgram {
fix: format!(
"Fix: {context} received resident handle {} owned by backend instance {}, but this instance is {}. A resident handle is only valid on the backend instance that allocated it; reallocate and re-upload the buffer on this instance, or keep the original instance alive for as long as the handle is held.",
handle.id,
handle.owner.0,
self.0
),
});
}
Ok(handle.id)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct ResidentHandle {
owner: ResidentOwner,
id: u64,
}
impl ResidentHandle {
#[must_use]
pub fn owner(self) -> ResidentOwner {
self.owner
}
#[must_use]
pub fn id(self) -> u64 {
self.id
}
}
impl std::fmt::Display for ResidentHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} on backend instance {}", self.id, self.owner.0)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Resource {
Borrowed(Vec<u8>),
Resident(ResidentHandle),
}
impl Default for Resource {
fn default() -> Self {
Resource::Borrowed(Vec::new())
}
}
impl From<Vec<u8>> for Resource {
fn from(bytes: Vec<u8>) -> Self {
Self::Borrowed(bytes)
}
}
impl From<ResidentHandle> for Resource {
fn from(handle: ResidentHandle) -> Self {
Self::Resident(handle)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distinct_owners_refuse_each_others_handles() {
let first = ResidentOwner::new().expect("owner ids are available");
let second = ResidentOwner::new().expect("owner ids are available");
assert_ne!(first, second);
let handle = first.handle(7);
assert_eq!(
first.resolve(handle, "test resolve").expect("own handle"),
7
);
let error = second.resolve(handle, "test resolve").expect_err(
"Fix: a foreign resident handle must be refused, never resolved by bare id",
);
let BackendError::InvalidProgram { fix } = error else {
panic!("Fix: foreign resident handle refusal must be BackendError::InvalidProgram");
};
assert!(
fix.contains("owned by backend instance") && fix.contains("Fix: "),
"Fix: foreign-handle refusal must name the owning instance and carry actionable text, got {fix}"
);
}
#[test]
fn same_id_in_two_namespaces_stays_distinct() {
let first = ResidentOwner::new().expect("owner ids are available");
let second = ResidentOwner::new().expect("owner ids are available");
assert_ne!(first.handle(1), second.handle(1));
assert_eq!(first.handle(1), first.handle(1));
}
}