use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock, Weak};
use dashmap::DashMap;
use tensor_wasm_core::types::TenantId;
use thiserror::Error;
use crate::context::{TenantCapability, TenantContext};
#[derive(Debug, Error, PartialEq, Eq)]
pub enum RegistryError {
#[error("tenant {0} already registered")]
AlreadyRegistered(TenantId),
#[error(
"tenant {0} cannot be re-registered while an orphan Arc<TenantContext> is still alive"
)]
OrphanStillAlive(TenantId),
#[error(
"capability was minted by a different TenantRegistry; refusing cross-registry operation"
)]
CapabilityFromForeignRegistry,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MpsDecision {
Mps(PathBuf),
Fallback,
}
pub const MPS_CONTROL_PATH: &str = "/tmp/nvidia-mps";
pub const MPS_PIPE_DIRECTORY_ENV: &str = "CUDA_MPS_PIPE_DIRECTORY";
#[derive(Debug)]
pub struct RegistryAdminCapability {
_seal: (),
pub(crate) registry_token: std::sync::Arc<()>,
}
impl RegistryAdminCapability {
pub(crate) fn mint(registry_token: std::sync::Arc<()>) -> Self {
Self {
_seal: (),
registry_token,
}
}
}
#[derive(Debug, Clone)]
pub struct TenantRegistry {
inner: Arc<DashMap<TenantId, Arc<TenantContext>>>,
tombstones: Arc<DashMap<TenantId, Weak<TenantContext>>>,
registry_token: Arc<()>,
prune_op_counter: Arc<AtomicUsize>,
}
impl TenantRegistry {
pub fn new() -> (Self, RegistryAdminCapability) {
let registry_token: Arc<()> = Arc::new(());
let reg = Self {
inner: Arc::new(DashMap::new()),
tombstones: Arc::new(DashMap::new()),
registry_token: Arc::clone(®istry_token),
prune_op_counter: Arc::new(AtomicUsize::new(0)),
};
let cap = RegistryAdminCapability::mint(registry_token);
(reg, cap)
}
pub fn register(&self, ctx: TenantContext) -> Result<Arc<TenantContext>, RegistryError> {
self.register_with_capability(ctx).map(|(arc, _cap)| arc)
}
pub fn register_with_capability(
&self,
mut ctx: TenantContext,
) -> Result<(Arc<TenantContext>, TenantCapability), RegistryError> {
let id = ctx.id();
let registry_token = Arc::clone(&self.registry_token);
let outcome = match self.inner.entry(id) {
dashmap::mapref::entry::Entry::Occupied(_) => Err(RegistryError::AlreadyRegistered(id)),
dashmap::mapref::entry::Entry::Vacant(slot) => {
match self.tombstones.entry(id) {
dashmap::mapref::entry::Entry::Occupied(tomb) => {
if tomb.get().strong_count() > 0 {
return Err(RegistryError::OrphanStillAlive(id));
}
tomb.remove();
}
dashmap::mapref::entry::Entry::Vacant(_) => {
}
}
ctx.registry_token = Some(Arc::clone(®istry_token));
let arc = Arc::new(ctx);
slot.insert(Arc::clone(&arc));
let cap = TenantCapability::mint(id, registry_token);
Ok((arc, cap))
}
};
if outcome.is_ok() {
self.maybe_prune_dead_tombstones_except(id);
}
outcome
}
fn prune_dead_tombstones_except(&self, skip_id: TenantId) {
self.tombstones
.retain(|id, weak| *id == skip_id || weak.strong_count() > 0);
}
fn maybe_prune_dead_tombstones_except(&self, skip_id: TenantId) {
let n = self.prune_op_counter.fetch_add(1, Ordering::Relaxed) + 1;
if n % Self::PRUNE_EVERY_N_OPS == 0
|| self.tombstones.len() > Self::PRUNE_TOMBSTONE_THRESHOLD
{
self.prune_dead_tombstones_except(skip_id);
}
}
const PRUNE_EVERY_N_OPS: usize = 64;
const PRUNE_TOMBSTONE_THRESHOLD: usize = 1024;
fn check_admin_cap(&self, cap: &RegistryAdminCapability) -> Result<(), RegistryError> {
if Arc::ptr_eq(&self.registry_token, &cap.registry_token) {
Ok(())
} else {
Err(RegistryError::CapabilityFromForeignRegistry)
}
}
pub fn get(
&self,
tenant_id: TenantId,
cap: &RegistryAdminCapability,
) -> Option<Arc<TenantContext>> {
self.check_admin_cap(cap).ok()?;
self.inner.get(&tenant_id).map(|r| Arc::clone(r.value()))
}
pub fn unregister(
&self,
tenant_id: TenantId,
cap: &RegistryAdminCapability,
) -> Option<Arc<TenantContext>> {
self.check_admin_cap(cap).ok()?;
let removed = match self.inner.entry(tenant_id) {
dashmap::mapref::entry::Entry::Vacant(_) => None,
dashmap::mapref::entry::Entry::Occupied(occ) => {
let arc = Arc::clone(occ.get());
self.tombstones.insert(tenant_id, Arc::downgrade(&arc));
let (_, removed) = occ.remove_entry();
Some(removed)
}
};
if removed.is_some() {
self.maybe_prune_dead_tombstones_except(tenant_id);
}
removed
}
pub fn collect_tombstones(&self, cap: &RegistryAdminCapability) -> usize {
if self.check_admin_cap(cap).is_err() {
return 0;
}
let mut pruned = 0;
self.tombstones.retain(|_id, weak| {
if weak.strong_count() == 0 {
pruned += 1;
false
} else {
true
}
});
pruned
}
pub fn tombstone_count(&self) -> usize {
self.tombstones.len()
}
pub fn len(&self, cap: &RegistryAdminCapability) -> usize {
if self.check_admin_cap(cap).is_err() {
return 0;
}
self.inner.len()
}
#[cfg(feature = "strict-cap-binding")]
pub fn get_strict(
&self,
tenant_id: TenantId,
cap: &RegistryAdminCapability,
) -> Result<Option<Arc<TenantContext>>, RegistryError> {
self.check_admin_cap(cap)?;
Ok(self.inner.get(&tenant_id).map(|r| Arc::clone(r.value())))
}
#[cfg(feature = "strict-cap-binding")]
pub fn unregister_strict(
&self,
tenant_id: TenantId,
cap: &RegistryAdminCapability,
) -> Result<Option<Arc<TenantContext>>, RegistryError> {
self.check_admin_cap(cap)?;
let removed = match self.inner.entry(tenant_id) {
dashmap::mapref::entry::Entry::Vacant(_) => None,
dashmap::mapref::entry::Entry::Occupied(occ) => {
let arc = Arc::clone(occ.get());
self.tombstones.insert(tenant_id, Arc::downgrade(&arc));
let (_, removed) = occ.remove_entry();
Some(removed)
}
};
if removed.is_some() {
self.maybe_prune_dead_tombstones_except(tenant_id);
}
Ok(removed)
}
#[cfg(feature = "strict-cap-binding")]
pub fn len_strict(&self, cap: &RegistryAdminCapability) -> Result<usize, RegistryError> {
self.check_admin_cap(cap)?;
Ok(self.inner.len())
}
#[cfg(feature = "strict-cap-binding")]
pub fn tenants_strict(
&self,
cap: &RegistryAdminCapability,
) -> Result<Vec<Arc<TenantContext>>, RegistryError> {
self.check_admin_cap(cap)?;
Ok(self.inner.iter().map(|r| Arc::clone(r.value())).collect())
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn tenants(&self, cap: &RegistryAdminCapability) -> Vec<Arc<TenantContext>> {
if self.check_admin_cap(cap).is_err() {
return Vec::new();
}
self.inner.iter().map(|r| Arc::clone(r.value())).collect()
}
pub fn mps_or_fallback() -> &'static MpsDecision {
MPS_DECISION.get_or_init(probe_mps_env_and_disk)
}
}
static MPS_DECISION: OnceLock<MpsDecision> = OnceLock::new();
fn probe_mps_env_and_disk() -> MpsDecision {
let env_dir = std::env::var_os(MPS_PIPE_DIRECTORY_ENV).map(PathBuf::from);
let dir = match env_dir {
Some(d) if !d.is_absolute() => {
tracing::warn!(
target: "tensor_wasm_tenant::registry",
env_var = %MPS_PIPE_DIRECTORY_ENV,
value = %d.display(),
"ignoring non-absolute MPS pipe directory; falling back to per-context isolation"
);
let decision = MpsDecision::Fallback;
tracing::info!(
target: "tensor_wasm_tenant::registry",
"MPS decision: {:?}",
decision
);
return decision;
}
Some(d) => d,
None => PathBuf::from(MPS_CONTROL_PATH),
};
let pipe = dir.join("control");
let decision = if pipe.exists() {
MpsDecision::Mps(dir)
} else {
MpsDecision::Fallback
};
tracing::info!(
target: "tensor_wasm_tenant::registry",
"MPS decision: {:?}",
decision
);
decision
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::IsolationKind;
fn ctx(id: u64) -> TenantContext {
TenantContext::builder(TenantId(id))
.with_isolation(IsolationKind::StreamIsolated)
.with_memory_quota_bytes(4096)
.build()
}
#[test]
fn register_lookup_unregister() {
let (reg, cap) = TenantRegistry::new();
assert!(reg.is_empty());
let arc = reg.register(ctx(1)).unwrap();
assert_eq!(reg.len(&cap), 1);
assert_eq!(arc.id(), TenantId(1));
let found = reg.get(TenantId(1), &cap).unwrap();
assert_eq!(found.id(), TenantId(1));
let removed = reg.unregister(TenantId(1), &cap).unwrap();
assert_eq!(removed.id(), TenantId(1));
assert!(reg.is_empty());
assert!(reg.get(TenantId(1), &cap).is_none());
}
#[test]
fn double_register_is_rejected() {
let (reg, cap) = TenantRegistry::new();
reg.register(ctx(2)).unwrap();
let err = reg.register(ctx(2)).unwrap_err();
assert_eq!(err, RegistryError::AlreadyRegistered(TenantId(2)));
assert_eq!(reg.len(&cap), 1);
}
#[test]
fn unregister_missing_returns_none() {
let (reg, cap) = TenantRegistry::new();
assert!(reg.unregister(TenantId(404), &cap).is_none());
}
#[test]
fn tenants_snapshot_lists_all() {
let (reg, cap) = TenantRegistry::new();
for i in 0..5u64 {
reg.register(ctx(i)).unwrap();
}
let mut ids: Vec<u64> = reg.tenants(&cap).iter().map(|c| c.id().get()).collect();
ids.sort_unstable();
assert_eq!(ids, vec![0, 1, 2, 3, 4]);
}
#[test]
fn registry_clone_shares_state() {
let (a, cap) = TenantRegistry::new();
let b = a.clone();
a.register(ctx(10)).unwrap();
assert_eq!(b.len(&cap), 1);
assert!(b.get(TenantId(10), &cap).is_some());
}
#[test]
fn capability_from_one_tenant_cannot_mutate_another() {
let (reg, _admin_cap) = TenantRegistry::new();
let (a_ctx, a_cap) = reg.register_with_capability(ctx(1001)).unwrap();
let (b_ctx, b_cap) = reg.register_with_capability(ctx(1002)).unwrap();
assert_eq!(a_ctx.bytes_in_use(), 0);
assert_eq!(b_ctx.bytes_in_use(), 0);
a_ctx.consume_bytes_with_capability(&a_cap, 128).unwrap();
assert_eq!(a_ctx.bytes_in_use(), 128);
let err = a_ctx
.consume_bytes_with_capability(&b_cap, 256)
.expect_err("cross-tenant consume must be rejected");
match err {
tensor_wasm_core::error::TensorWasmError::TenantIsolationViolation {
tenant_id,
..
} => {
assert_eq!(tenant_id, TenantId(1002));
}
other => panic!("expected TenantIsolationViolation, got {other:?}"),
}
assert_eq!(a_ctx.bytes_in_use(), 128);
a_ctx
.release_bytes_with_capability(&b_cap, 128)
.expect_err("cross-tenant release must be rejected");
assert_eq!(a_ctx.bytes_in_use(), 128);
assert_eq!(b_ctx.bytes_in_use(), 0);
}
#[test]
fn mps_decision_uses_filesystem_probe() {
let dir = std::env::var_os(MPS_PIPE_DIRECTORY_ENV)
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::PathBuf::from(MPS_CONTROL_PATH));
let probe_says_mps = dir.join("control").exists();
match TenantRegistry::mps_or_fallback() {
MpsDecision::Mps(_) => {
let _ = probe_says_mps;
}
MpsDecision::Fallback => {
let _ = probe_says_mps;
}
}
}
#[test]
fn race_register_after_unregister_with_orphan_alive_all_see_orphan() {
use std::sync::Barrier;
use std::thread;
const THREADS: usize = 16;
const ATTEMPTS_PER_THREAD: usize = 8;
const RACE_ID: u64 = 9001;
let (reg, cap) = TenantRegistry::new();
let (orphan_arc, _orphan_cap) = reg.register_with_capability(ctx(RACE_ID)).unwrap();
let returned = reg.unregister(TenantId(RACE_ID), &cap).unwrap();
assert!(Arc::ptr_eq(&returned, &orphan_arc));
drop(returned);
let barrier = Arc::new(Barrier::new(THREADS));
let mut handles = Vec::with_capacity(THREADS);
for _ in 0..THREADS {
let reg = reg.clone();
let barrier = Arc::clone(&barrier);
handles.push(thread::spawn(move || {
barrier.wait();
let mut local_ok = 0usize;
let mut local_orphan = 0usize;
let mut local_already = 0usize;
for _ in 0..ATTEMPTS_PER_THREAD {
match reg.register_with_capability(ctx(RACE_ID)) {
Ok(_) => local_ok += 1,
Err(RegistryError::OrphanStillAlive(_)) => local_orphan += 1,
Err(RegistryError::AlreadyRegistered(_)) => local_already += 1,
Err(RegistryError::CapabilityFromForeignRegistry) => {
panic!("unexpected CapabilityFromForeignRegistry from register")
}
}
}
(local_ok, local_orphan, local_already)
}));
}
let mut total_ok = 0usize;
let mut total_orphan = 0usize;
let mut total_already = 0usize;
for h in handles {
let (ok, orphan, already) = h.join().unwrap();
total_ok += ok;
total_orphan += orphan;
total_already += already;
}
assert_eq!(
total_ok, 0,
"no register may succeed while orphan is alive (ok={total_ok}, orphan={total_orphan}, already={total_already})"
);
assert_eq!(
total_already, 0,
"AlreadyRegistered cannot occur while orphan is alive and slot is empty"
);
assert_eq!(total_orphan, THREADS * ATTEMPTS_PER_THREAD);
assert_eq!(Arc::strong_count(&orphan_arc), 1);
assert!(reg.tombstones.contains_key(&TenantId(RACE_ID)));
drop(orphan_arc);
let _ = reg.register_with_capability(ctx(RACE_ID)).unwrap();
assert!(!reg.tombstones.contains_key(&TenantId(RACE_ID)));
}
#[test]
fn race_register_fresh_id_at_most_one_succeeds() {
use std::sync::Barrier;
use std::thread;
const THREADS: usize = 32;
const RACE_ID: u64 = 9100;
let (reg, cap) = TenantRegistry::new();
let barrier = Arc::new(Barrier::new(THREADS));
let mut handles = Vec::with_capacity(THREADS);
for _ in 0..THREADS {
let reg = reg.clone();
let barrier = Arc::clone(&barrier);
handles.push(thread::spawn(move || {
barrier.wait();
reg.register_with_capability(ctx(RACE_ID)).map(|_| ())
}));
}
let mut ok_count = 0usize;
let mut already = 0usize;
for h in handles {
match h.join().unwrap() {
Ok(()) => ok_count += 1,
Err(RegistryError::AlreadyRegistered(_)) => already += 1,
Err(RegistryError::OrphanStillAlive(_)) => {
panic!("no prior registration in this test — OrphanStillAlive impossible")
}
Err(RegistryError::CapabilityFromForeignRegistry) => {
panic!("register cannot return CapabilityFromForeignRegistry")
}
}
}
assert_eq!(ok_count, 1, "exactly one register must win the race");
assert_eq!(already, THREADS - 1);
assert_eq!(reg.len(&cap), 1);
}
#[test]
fn held_orphan_blocks_concurrent_register() {
use std::sync::Barrier;
use std::thread;
const ORPHAN_ID: u64 = 9200;
let (reg, cap) = TenantRegistry::new();
let (orphan_arc, _orphan_cap) = reg.register_with_capability(ctx(ORPHAN_ID)).unwrap();
let _ = reg.unregister(TenantId(ORPHAN_ID), &cap).unwrap();
let barrier = Arc::new(Barrier::new(2));
let reg_for_thread = reg.clone();
let barrier_for_thread = Arc::clone(&barrier);
let join = thread::spawn(move || {
barrier_for_thread.wait();
reg_for_thread.register_with_capability(ctx(ORPHAN_ID))
});
barrier.wait();
let res = join.join().unwrap();
match res {
Err(RegistryError::OrphanStillAlive(id)) => {
assert_eq!(id, TenantId(ORPHAN_ID));
}
other => panic!("expected OrphanStillAlive, got {other:?}"),
}
assert_eq!(Arc::strong_count(&orphan_arc), 1);
}
#[test]
fn race_register_vs_unregister_preserves_ordering() {
use std::sync::Barrier;
use std::thread;
const ID: u64 = 9300;
const ROUNDS: usize = 64;
for _ in 0..ROUNDS {
let (reg, cap) = TenantRegistry::new();
let barrier = Arc::new(Barrier::new(2));
let reg_r = reg.clone();
let barrier_r = Arc::clone(&barrier);
let t_register = thread::spawn(move || {
barrier_r.wait();
reg_r.register_with_capability(ctx(ID))
});
let reg_u = reg.clone();
let barrier_u = Arc::clone(&barrier);
let t_unregister = thread::spawn(move || {
barrier_u.wait();
reg_u.unregister(TenantId(ID), &cap)
});
let r_res = t_register.join().unwrap();
let u_res = t_unregister.join().unwrap();
let (r_arc, _r_cap) =
r_res.expect("register against fresh id with only unregister racing must succeed");
assert_eq!(r_arc.id(), TenantId(ID));
match u_res {
Some(removed) => {
assert!(Arc::ptr_eq(&removed, &r_arc));
assert!(
reg.tombstones.contains_key(&TenantId(ID)),
"unregister-after-register must record tombstone"
);
drop(removed);
drop(r_arc);
}
None => {
assert!(
!reg.tombstones.contains_key(&TenantId(ID)),
"register-after-empty-unregister must leave no tombstone"
);
drop(r_arc);
}
}
}
}
#[test]
fn unregister_prunes_dead_tombstones_when_op_boundary_reached() {
let (reg, cap) = TenantRegistry::new();
let _ = reg.register(ctx(1)).unwrap();
reg.unregister(TenantId(1), &cap).unwrap();
assert!(reg.tombstones.contains_key(&TenantId(1)));
assert_eq!(
reg.tombstones.get(&TenantId(1)).unwrap().strong_count(),
0,
"tombstone 1's Weak should be dead — both Arcs were dropped"
);
for _ in 0..TenantRegistry::PRUNE_EVERY_N_OPS {
let _ = reg.register(ctx(2)).unwrap();
reg.unregister(TenantId(2), &cap).unwrap();
}
assert!(
!reg.tombstones.contains_key(&TenantId(1)),
"amortized prune must eventually drop dead tombstone 1"
);
}
#[test]
fn register_prune_is_amortized_collect_reclaims() {
let (reg, cap) = TenantRegistry::new();
let _ = reg.register(ctx(1)).unwrap();
reg.unregister(TenantId(1), &cap).unwrap();
assert!(reg.tombstones.contains_key(&TenantId(1)));
let _ = reg.register_with_capability(ctx(2)).unwrap();
assert!(
reg.tombstones.contains_key(&TenantId(1)),
"amortized policy: a single register must not prune dead tombstone 1"
);
let pruned = reg.collect_tombstones(&cap);
assert_eq!(
pruned, 1,
"collect_tombstones must reclaim dead tombstone 1"
);
assert!(!reg.tombstones.contains_key(&TenantId(1)));
}
#[test]
fn register_orphan_rejected_does_not_prune() {
let (reg, cap) = TenantRegistry::new();
let _ = reg.register(ctx(1)).unwrap();
reg.unregister(TenantId(1), &cap).unwrap();
assert!(reg.tombstones.contains_key(&TenantId(1)));
let (orphan_arc, _orphan_cap) = reg.register_with_capability(ctx(2)).unwrap();
reg.unregister(TenantId(2), &cap).unwrap();
assert_eq!(Arc::strong_count(&orphan_arc), 1);
assert!(reg.tombstones.contains_key(&TenantId(2)));
let before: std::collections::BTreeSet<u64> =
reg.tombstones.iter().map(|e| e.key().get()).collect();
let err = reg
.register_with_capability(ctx(2))
.expect_err("re-register against live orphan must fail");
assert_eq!(err, RegistryError::OrphanStillAlive(TenantId(2)));
let after: std::collections::BTreeSet<u64> =
reg.tombstones.iter().map(|e| e.key().get()).collect();
assert_eq!(
before, after,
"refusal path must not prune or otherwise mutate tombstones"
);
drop(orphan_arc);
}
}