use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::time::Duration;
use bytes::Bytes;
use futures::future::BoxFuture;
use velo_ext::Transport;
use super::arena::{ArenaSet, Budget, GRANULE, RdmaPoolConfig, pool_arena_target};
use super::backend::{BackendGet, BackendRegion, RdmaBackend, RdmaError, UcxBackend};
use super::region::{Deregistered, RegionInner, RegionParts};
use super::{RdmaConfig, RdmaRegistry};
const T: Duration = Duration::from_secs(10);
static LEAK_COUNTER: std::sync::LazyLock<tokio::sync::Mutex<()>> =
std::sync::LazyLock::new(|| tokio::sync::Mutex::new(()));
struct MockBackend {
next_id: AtomicU64,
mapped: dashmap::DashMap<u64, (usize, usize)>,
unmapped: AtomicUsize,
refuse_unmap: AtomicBool,
map_delay: parking_lot::Mutex<Option<Duration>>,
extra_effective: AtomicU64,
}
impl MockBackend {
fn new() -> Arc<Self> {
Arc::new(Self {
next_id: AtomicU64::new(1),
mapped: dashmap::DashMap::new(),
unmapped: AtomicUsize::new(0),
refuse_unmap: AtomicBool::new(false),
map_delay: parking_lot::Mutex::new(None),
extra_effective: AtomicU64::new(0),
})
}
fn live(&self) -> usize {
self.mapped.len()
}
fn unmap_calls(&self) -> usize {
self.unmapped.load(Ordering::SeqCst)
}
fn force_teardown(&self) {
self.mapped.clear();
}
}
impl RdmaBackend for MockBackend {
fn key(&self) -> &str {
"mock"
}
fn map(&self, ptr: usize, len: usize) -> BoxFuture<'_, Result<BackendRegion, RdmaError>> {
Box::pin(async move {
let delay = *self.map_delay.lock();
if let Some(delay) = delay {
tokio::time::sleep(delay).await;
}
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
self.mapped.insert(id, (ptr, len));
Ok(BackendRegion {
backend_region_id: id,
effective_addr: (ptr & !(GRANULE - 1)) as u64,
effective_len: len.next_multiple_of(GRANULE) as u64
+ self.extra_effective.load(Ordering::SeqCst),
packed_key: Bytes::from_static(b"mock-packed-key"),
})
})
}
fn unmap(&self, backend_region_id: u64) -> BoxFuture<'_, Result<(), RdmaError>> {
Box::pin(async move {
self.unmapped.fetch_add(1, Ordering::SeqCst);
if self.refuse_unmap.load(Ordering::SeqCst) {
return Err(RdmaError::ShuttingDown);
}
self.mapped.remove(&backend_region_id);
Ok(())
})
}
fn live_registrations(&self) -> Option<usize> {
Some(self.mapped.len())
}
fn get(&self, _req: BackendGet) -> BoxFuture<'_, Result<(), RdmaError>> {
Box::pin(async move { Ok(()) })
}
}
fn mock_pool(cfg: RdmaPoolConfig) -> (Arc<MockBackend>, ArenaSet) {
let backend = MockBackend::new();
let budget = Arc::new(Budget::new(cfg.registered_bytes_budget, None));
let pool = ArenaSet::new(
Arc::clone(&backend) as Arc<dyn RdmaBackend>,
cfg,
Arc::clone(&budget),
Arc::new(AtomicU64::new(1)),
None,
);
(backend, pool)
}
fn small_pool_config() -> RdmaPoolConfig {
RdmaPoolConfig {
initial_arena_bytes: 64 * GRANULE as u64,
max_arena_bytes: 256 * GRANULE as u64,
dedicated_arena_min: 128 * GRANULE as u64,
registered_bytes_budget: 1024 * GRANULE as u64,
arena_reclaim_after: None,
retain_arena_bytes: 64 * GRANULE as u64,
}
}
fn mock_registry(cfg: RdmaConfig) -> (Arc<MockBackend>, Arc<RdmaRegistry>) {
let backend = MockBackend::new();
let registry = Arc::new(RdmaRegistry::new(
Arc::clone(&backend) as Arc<dyn RdmaBackend>,
cfg,
tokio::runtime::Handle::current(),
None,
));
(backend, registry)
}
#[tokio::test]
async fn pool_alloc_roundtrip() {
let (backend, pool) = mock_pool(small_pool_config());
let mut buf = pool.alloc(4000).await.expect("alloc");
assert_eq!(
buf.len(),
4000,
"the exact requested length is what is handed out"
);
buf.fill(0xAB);
assert!(buf.iter().all(|b| *b == 0xAB));
let remote = buf.remote();
assert_eq!(remote.addr, buf.addr());
assert_eq!(remote.len, 4000);
assert_eq!(&remote.packed_key[..], b"mock-packed-key");
assert_eq!(
backend.live(),
1,
"one arena maps once, however many buffers come out of it"
);
assert_eq!(pool.arena_count(), 1);
assert_eq!(pool.live_allocations(), 1);
drop(buf);
assert_eq!(pool.live_allocations(), 0);
assert_eq!(
backend.unmap_calls(),
0,
"returning a suballocation must never touch the backend; the arena stays registered"
);
}
#[tokio::test]
async fn pool_rounds_to_granules() {
let (_backend, pool) = mock_pool(small_pool_config());
let a = pool.alloc(1).await.expect("alloc a");
let b = pool.alloc(1).await.expect("alloc b");
assert_eq!(a.len(), 1);
assert_eq!(b.len(), 1);
let gap = b.addr().abs_diff(a.addr());
assert!(
gap >= GRANULE as u64,
"two live buffers shared a granule: {gap} bytes apart"
);
}
#[tokio::test]
async fn pool_refuses_zero_length() {
let (_backend, pool) = mock_pool(small_pool_config());
assert_eq!(pool.alloc(0).await.err(), Some(RdmaError::OutOfRange));
}
#[tokio::test]
async fn pool_grows_geometrically() {
let cfg = small_pool_config();
let (backend, pool) = mock_pool(cfg.clone());
let quarter = cfg.initial_arena_bytes as usize / 4;
let mut held = Vec::new();
for _ in 0..12 {
held.push(pool.alloc(quarter).await.expect("alloc"));
}
let arenas = pool.arena_count();
assert!(arenas >= 2, "the pool never grew: {arenas} arenas");
assert!(
arenas < 12,
"the pool mapped an arena per allocation ({arenas}); growth is not geometric"
);
assert_eq!(
backend.live(),
arenas,
"every arena is one backend registration"
);
}
#[test]
fn pool_growth_saturates_instead_of_wrapping() {
const INITIAL: u64 = 64 << 20;
const MAX: u64 = 1 << 30;
for pooled in 0..=80usize {
let want = ((INITIAL as u128) << pooled).min(MAX as u128) as u64;
assert_eq!(
pool_arena_target(INITIAL, MAX, pooled),
want,
"growth target is wrong at {pooled} pooled arenas"
);
}
}
#[tokio::test]
async fn pool_dedicates_an_arena_to_oversize_requests() {
let cfg = small_pool_config();
let (backend, pool) = mock_pool(cfg.clone());
let small = pool.alloc(GRANULE).await.expect("small alloc");
let arenas_before = pool.arena_count();
let big = pool
.alloc(cfg.dedicated_arena_min as usize)
.await
.expect("oversize alloc");
assert_eq!(
pool.arena_count(),
arenas_before + 1,
"an oversize request must get its own arena"
);
assert_eq!(backend.live(), arenas_before + 1);
let another_small = pool.alloc(GRANULE).await.expect("second small alloc");
let big_end = big.addr() + big.len() as u64;
assert!(
another_small.addr() < big.addr() || another_small.addr() >= big_end,
"a pooled allocation landed inside the dedicated arena"
);
drop((small, big, another_small));
}
#[tokio::test]
async fn pool_budget_exhaustion_is_a_refusal() {
let cfg = RdmaPoolConfig {
initial_arena_bytes: 16 * GRANULE as u64,
max_arena_bytes: 16 * GRANULE as u64,
dedicated_arena_min: 1024 * GRANULE as u64,
registered_bytes_budget: 32 * GRANULE as u64,
..small_pool_config()
};
let (backend, pool) = mock_pool(cfg);
let mut held = Vec::new();
let mut refusal = None;
for _ in 0..64 {
match pool.alloc(8 * GRANULE).await {
Ok(buf) => held.push(buf),
Err(e) => {
refusal = Some(e);
break;
}
}
}
match refusal {
Some(RdmaError::BudgetExceeded {
registered, budget, ..
}) => {
assert_eq!(budget, 32 * GRANULE as u64);
assert!(
registered <= budget,
"the budget was overshot before it was enforced: {registered} over {budget}"
);
}
other => panic!("expected a budget refusal, got {other:?}"),
}
assert_eq!(
backend.live(),
2,
"the pool mapped past its own ceiling before refusing"
);
}
#[tokio::test]
async fn pool_reuses_space_after_drop() {
let cfg = RdmaPoolConfig {
initial_arena_bytes: 16 * GRANULE as u64,
max_arena_bytes: 16 * GRANULE as u64,
dedicated_arena_min: 1024 * GRANULE as u64,
registered_bytes_budget: 32 * GRANULE as u64,
..small_pool_config()
};
let (backend, pool) = mock_pool(cfg);
let mut held = Vec::new();
while let Ok(buf) = pool.alloc(4 * GRANULE).await {
held.push(buf);
assert!(held.len() < 64, "the pool never filled up");
}
let filled = held.len();
assert!(filled > 0, "nothing could be allocated at all");
let arenas_when_full = pool.arena_count();
drop(held);
assert_eq!(pool.live_allocations(), 0);
let again = pool.alloc(4 * GRANULE).await;
assert!(
again.is_ok(),
"space returned by a dropped PinnedBuf was not reusable: {:?}",
again.err()
);
assert_eq!(
pool.arena_count(),
arenas_when_full,
"reuse mapped a new arena instead of using the space that came back"
);
assert_eq!(backend.live(), arenas_when_full);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn pool_concurrent_alloc_and_free() {
const TASKS: usize = 16;
const ROUNDS: usize = 24;
let cfg = small_pool_config();
let (backend, pool) = mock_pool(cfg);
let pool = Arc::new(pool);
let mut tasks = Vec::new();
for task in 0..TASKS {
let pool = Arc::clone(&pool);
tasks.push(tokio::spawn(async move {
let tag = (task % 251) as u8;
for round in 0..ROUNDS {
let len = GRANULE * (1 + (round % 3));
let mut buf = pool.alloc(len).await.expect("concurrent alloc");
buf.fill(tag);
tokio::task::yield_now().await;
assert!(
buf.iter().all(|b| *b == tag),
"another allocation wrote into this range: task {task}, round {round}"
);
}
}));
}
for task in tasks {
task.await.expect("task panicked");
}
assert_eq!(pool.live_allocations(), 0, "a suballocation leaked");
assert_eq!(
backend.live(),
pool.arena_count(),
"the arena set and the backend disagree about what is mapped"
);
assert_eq!(
backend.unmap_calls(),
0,
"no arena should have been unmapped"
);
}
fn reclaiming_pool_config(retain_arena_bytes: u64) -> RdmaPoolConfig {
RdmaPoolConfig {
arena_reclaim_after: Some(Duration::ZERO),
retain_arena_bytes,
..small_pool_config()
}
}
#[tokio::test]
async fn reclaim_is_off_by_default_for_pooled_arenas() {
let (backend, pool) = mock_pool(small_pool_config());
assert_eq!(small_pool_config().arena_reclaim_after, None);
let mut held = Vec::new();
for _ in 0..3 {
held.push(pool.alloc(32 * GRANULE).await.expect("alloc"));
}
assert!(pool.arena_count() >= 2, "the pool did not grow");
let arenas = pool.arena_count();
held.clear();
assert_eq!(pool.reclaim_idle().await, 0, "the sweep ran unconfigured");
assert_eq!(pool.arena_count(), arenas);
assert_eq!(backend.unmap_calls(), 0);
}
#[tokio::test]
async fn empty_pooled_arenas_above_the_floor_are_reclaimed() {
let cfg = reclaiming_pool_config(64 * GRANULE as u64);
let (backend, pool) = mock_pool(cfg);
let mut held = Vec::new();
while let Ok(buf) = pool.alloc(32 * GRANULE).await {
held.push(buf);
if held.len() > 256 {
panic!("the budget never refused an allocation");
}
}
let grown = pool.arena_count();
assert!(grown >= 3, "expected several arenas, got {grown}");
let peak = pool.registered_bytes();
assert!(peak > 0);
assert_eq!(
pool.reclaim_idle().await,
0,
"an arena with live suballocations was reclaimed"
);
assert_eq!(pool.registered_bytes(), peak);
held.clear();
let reclaimed = pool.reclaim_idle().await;
assert!(
reclaimed > 0,
"nothing was reclaimed after the pool emptied"
);
assert_eq!(backend.unmap_calls(), reclaimed);
assert_eq!(
backend.live(),
pool.arena_count(),
"the backend and the pool disagree about what is mapped"
);
assert!(
pool.registered_bytes() < peak,
"the budget was not released: {} vs {peak}",
pool.registered_bytes()
);
}
#[tokio::test]
async fn retention_keeps_a_warm_arena_mapped() {
const RETAIN: u64 = 64 * GRANULE as u64;
let (backend, pool) = mock_pool(reclaiming_pool_config(RETAIN));
let mut held = Vec::new();
while let Ok(buf) = pool.alloc(32 * GRANULE).await {
held.push(buf);
if held.len() > 256 {
panic!("the budget never refused an allocation");
}
}
assert!(
pool.arena_count() >= 3,
"the pool did not grow enough to test retention"
);
held.clear();
pool.reclaim_idle().await;
assert!(pool.arena_count() >= 1, "retention kept nothing at all");
assert!(
pool.registered_bytes() >= RETAIN,
"the pool fell below its retention floor: {} < {RETAIN}",
pool.registered_bytes()
);
let after = pool.registered_bytes();
assert_eq!(pool.reclaim_idle().await, 0);
assert_eq!(pool.registered_bytes(), after);
let before = backend.live();
let _buf = pool.alloc(4 * GRANULE).await.expect("alloc from the floor");
assert_eq!(
backend.live(),
before,
"the retained arena did not serve the next allocation"
);
}
#[tokio::test]
async fn zero_retention_reclaims_every_empty_arena() {
let (backend, pool) = mock_pool(reclaiming_pool_config(0));
let mut held = Vec::new();
for _ in 0..4 {
held.push(pool.alloc(32 * GRANULE).await.expect("alloc"));
}
held.clear();
pool.reclaim_idle().await;
assert_eq!(
pool.arena_count(),
0,
"an empty arena survived zero retention"
);
assert_eq!(backend.live(), 0);
assert_eq!(
pool.registered_bytes(),
0,
"the budget was not fully released"
);
}
#[tokio::test]
async fn a_dedicated_arena_is_reclaimed_when_its_buffer_drops() {
let cfg = small_pool_config();
assert_eq!(cfg.arena_reclaim_after, None);
let dedicated_min = cfg.dedicated_arena_min as usize;
let (backend, pool) = mock_pool(cfg);
let mut peak_arenas = 0;
for cycle in 0..24 {
let buf = pool
.alloc(dedicated_min)
.await
.unwrap_or_else(|e| panic!("cycle {cycle}: oversize alloc refused: {e}"));
peak_arenas = peak_arenas.max(pool.arena_count());
drop(buf);
pool.reclaim_idle().await;
assert_eq!(
pool.registered_bytes(),
0,
"cycle {cycle}: the dedicated arena's budget was not released"
);
assert_eq!(backend.live(), 0, "cycle {cycle}: it stayed mapped");
}
assert_eq!(
peak_arenas, 1,
"dedicated arenas accumulated instead of being reclaimed each cycle"
);
}
#[tokio::test]
async fn a_hold_outliving_its_buffer_blocks_reclaim() {
let cfg = reclaiming_pool_config(0);
let (backend, pool) = mock_pool(cfg);
let buf = pool.alloc(8 * GRANULE).await.expect("alloc");
let hold = buf.hold();
drop(buf);
assert_eq!(
pool.reclaim_idle().await,
0,
"an arena with a transfer still holding it was reclaimed"
);
assert_eq!(backend.live(), 1);
drop(hold);
assert_eq!(pool.reclaim_idle().await, 1);
assert_eq!(backend.live(), 0);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn reclaim_racing_alloc_never_hands_out_a_reclaimed_arena() {
const TASKS: usize = 8;
const ROUNDS: usize = 40;
let (backend, pool) = mock_pool(reclaiming_pool_config(0));
let pool = Arc::new(pool);
let mut tasks = Vec::new();
for task in 0..TASKS {
let pool = Arc::clone(&pool);
tasks.push(tokio::spawn(async move {
for round in 0..ROUNDS {
if let Ok(mut buf) = pool.alloc(4 * GRANULE).await {
let byte = (task * ROUNDS + round) as u8;
buf.fill(byte);
tokio::task::yield_now().await;
assert!(
buf.iter().all(|b| *b == byte),
"task {task} round {round}: the buffer was overwritten"
);
}
}
}));
}
let sweeper = {
let pool = Arc::clone(&pool);
tokio::spawn(async move {
for _ in 0..ROUNDS * 4 {
pool.reclaim_idle().await;
tokio::task::yield_now().await;
}
})
};
for task in tasks {
task.await.expect("allocator task must not panic");
}
sweeper.await.expect("sweeper must not panic");
assert_eq!(
backend.live(),
pool.arena_count(),
"the backend and the pool disagree about what is mapped"
);
let mapped: u64 = pool.registered_bytes();
assert_eq!(
mapped == 0,
pool.arena_count() == 0,
"registered bytes and arena count disagree: {mapped} B over {} arenas",
pool.arena_count()
);
}
#[tokio::test]
async fn reclaim_is_refused_once_shutdown_has_gated() {
let cfg = RdmaConfig {
pool: reclaiming_pool_config(0),
..RdmaConfig::default()
};
let (backend, registry) = mock_registry(cfg);
let buf = registry.alloc_pinned(8 * GRANULE).await.expect("alloc");
drop(buf);
assert_eq!(backend.live(), 1);
registry.shutdown(Duration::from_secs(5)).await;
assert_eq!(backend.live(), 0, "the sweep left an arena mapped");
let calls = backend.unmap_calls();
assert_eq!(registry.reclaim_idle_arenas().await, 0);
assert_eq!(
backend.unmap_calls(),
calls,
"a reclaim ran after the shutdown gate closed"
);
}
#[tokio::test]
async fn an_unconfirmed_reclaim_holds_its_budget() {
let (backend, pool) = mock_pool(reclaiming_pool_config(0));
let buf = pool.alloc(8 * GRANULE).await.expect("alloc");
let charged = pool.registered_bytes();
assert!(charged > 0);
drop(buf);
backend.refuse_unmap.store(true, Ordering::SeqCst);
assert_eq!(
pool.reclaim_idle().await,
0,
"an unconfirmed unmap was counted as a reclaim"
);
assert_eq!(
pool.registered_bytes(),
charged,
"the budget was credited for pages that may still be pinned"
);
assert_eq!(pool.arena_count(), 0, "the arena stayed in the live set");
pool.release_unconfirmed();
assert_eq!(pool.registered_bytes(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn the_rendezvous_tick_reclaims_arenas_without_being_asked() {
let transport = Arc::new(
crate::transports::ucx::UcxTransportBuilder::new()
.tls("tcp")
.build()
.expect("build ucx transport"),
);
let velo = crate::Velo::builder()
.add_ucx_transport(Arc::clone(&transport))
.rdma_config(RdmaConfig {
pool: RdmaPoolConfig {
initial_arena_bytes: 64 * GRANULE as u64,
max_arena_bytes: 64 * GRANULE as u64,
dedicated_arena_min: 16 * GRANULE as u64,
registered_bytes_budget: 1024 * GRANULE as u64,
arena_reclaim_after: Some(Duration::from_millis(10)),
retain_arena_bytes: 0,
},
..RdmaConfig::default()
})
.build()
.await
.expect("build velo");
let registry = velo.rdma().expect("the ucx transport gives us a registry");
let small = registry
.alloc_pinned(4 * GRANULE)
.await
.expect("pooled alloc");
let big = registry
.alloc_pinned(16 * GRANULE)
.await
.expect("dedicated alloc");
assert!(velo.rdma_registered_bytes() > 0);
assert!(transport.live_regions() >= 2, "expected two arenas");
drop(small);
drop(big);
let deadline = std::time::Instant::now() + T;
while std::time::Instant::now() < deadline {
if velo.rdma_registered_bytes() == 0 && transport.live_regions() == 0 {
break;
}
tokio::time::sleep(Duration::from_millis(5)).await;
}
assert_eq!(
velo.rdma_registered_bytes(),
0,
"the rendezvous tick never reclaimed the empty arenas"
);
assert_eq!(transport.live_regions(), 0);
velo.graceful_shutdown(velo_ext::ShutdownPolicy::Timeout(T))
.await;
assert_eq!(transport.live_rkeys(), 0);
}
#[tokio::test]
async fn pool_lifecycle_soak() {
const CYCLES: usize = 200;
let cfg = reclaiming_pool_config(0);
let dedicated_min = cfg.dedicated_arena_min as usize;
let (backend, pool) = mock_pool(cfg);
for cycle in 0..CYCLES {
let mut small = pool
.alloc(4 * GRANULE)
.await
.unwrap_or_else(|e| panic!("cycle {cycle}: pooled alloc: {e}"));
let big = pool
.alloc(dedicated_min)
.await
.unwrap_or_else(|e| panic!("cycle {cycle}: dedicated alloc: {e}"));
let hold = small.hold();
small.fill(cycle as u8);
assert!(small.iter().all(|b| *b == cycle as u8));
drop(hold);
drop(small);
drop(big);
pool.reclaim_idle().await;
assert_eq!(
pool.registered_bytes(),
0,
"cycle {cycle}: registered bytes did not return to zero"
);
assert_eq!(
pool.arena_count(),
0,
"cycle {cycle}: an arena survived the sweep"
);
assert_eq!(
backend.live(),
0,
"cycle {cycle}: the backend still holds a registration"
);
}
}
#[tokio::test]
async fn region_unregister_latches_deregistered() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 8192].into_boxed_slice())
.await
.expect("register");
assert_eq!(backend.live(), 1);
assert_eq!(registry.region_count(), 1);
let charged = registry.registered_bytes();
assert!(
charged >= 8192 && charged % GRANULE as u64 == 0,
"expected a page-enclosing charge, got {charged}"
);
assert!(
!guard.is_deregistered(),
"a live registration must not claim to be deregistered"
);
let watch = guard.watch();
assert!(
tokio::time::timeout(Duration::from_millis(50), watch.deregistered())
.await
.is_err(),
"deregistered() resolved while the memory was still registered"
);
assert_eq!(
guard.unregister(T).await.expect("unregister"),
Deregistered::Drained
);
assert_eq!(backend.live(), 0);
assert_eq!(registry.region_count(), 0);
assert_eq!(
registry.registered_bytes(),
0,
"the budget was not credited back"
);
assert!(watch.is_deregistered());
tokio::time::timeout(T, watch.deregistered())
.await
.expect("the latch must be resolved for every observer, not just the unregisterer");
}
#[tokio::test]
async fn unconfirmed_unmap_does_not_latch() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
let charged = registry.registered_bytes();
backend.refuse_unmap.store(true, Ordering::SeqCst);
let err = guard
.unregister(T)
.await
.expect_err("the unmap was refused");
assert_eq!(err, RdmaError::ShuttingDown);
assert!(
!watch.is_deregistered(),
"an unconfirmed unmap latched deregistered(): a caller would now free pinned memory"
);
assert_eq!(
registry.region_count(),
1,
"an unconfirmed region must stay tracked so the shutdown sweep asks again"
);
assert_eq!(
registry.registered_bytes(),
charged,
"the budget was credited back for memory that may still be pinned"
);
backend.refuse_unmap.store(false, Ordering::SeqCst);
registry.shutdown(T).await;
assert!(watch.is_deregistered());
assert_eq!(registry.registered_bytes(), 0);
}
#[tokio::test]
async fn dropped_guard_deregisters_in_the_background() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
drop(guard);
tokio::time::timeout(T, watch.deregistered())
.await
.expect("a dropped guard must still deregister");
assert_eq!(
backend.live(),
0,
"the backend still holds the registration"
);
assert_eq!(registry.region_count(), 0);
assert_eq!(registry.registered_bytes(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn guard_dropped_off_runtime_still_deregisters() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
std::thread::spawn(move || drop(guard))
.join()
.expect("dropper thread panicked");
tokio::time::timeout(T, watch.deregistered())
.await
.expect("a guard dropped off the runtime must still deregister");
assert_eq!(backend.live(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn unregister_waits_for_in_flight() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect("register");
let lease = guard.in_flight().acquire();
let watch = guard.watch();
let mut unregistering = tokio::spawn(async move { guard.unregister(T).await });
assert!(
tokio::time::timeout(Duration::from_millis(100), &mut unregistering)
.await
.is_err(),
"unregister completed while an operation was still in flight"
);
assert_eq!(
backend.unmap_calls(),
0,
"the backend was asked to unmap before the region had drained"
);
drop(lease);
let outcome = tokio::time::timeout(T, unregistering)
.await
.expect("unregister must resolve once the last in-flight guard is released")
.expect("task panicked")
.expect("unregister");
assert_eq!(
outcome,
Deregistered::Drained,
"the drain completed, so this is not a timed-out deregistration"
);
assert!(watch.is_deregistered());
assert_eq!(backend.live(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn unregister_timeout_still_unmaps() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
let _stuck = guard.in_flight().acquire();
let outcome = guard
.unregister(Duration::from_millis(100))
.await
.expect("a confirmed unmap is Ok even when the drain was cut short");
assert_eq!(
outcome,
Deregistered::DrainTimedOut,
"the caller must be able to tell that in-flight work was not waited for"
);
assert!(
watch.is_deregistered(),
"the unmap was confirmed, so the latch must resolve even though the drain timed out"
);
assert_eq!(
backend.live(),
0,
"a timed-out drain must still force the unmap"
);
}
#[tokio::test]
async fn watch_observes_without_owning() {
let (_backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
let second = watch.clone();
assert!(!watch.is_shutting_down());
assert!(!watch.is_deregistered());
registry.shutdown(T).await;
assert!(watch.is_shutting_down(), "a watch must see shutdown begin");
assert!(second.is_deregistered());
tokio::time::timeout(T, second.shutdown_initiated())
.await
.expect("shutdown_initiated must resolve once shutdown has begun");
drop(guard);
}
#[tokio::test]
async fn register_owned_returns_the_buffer() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let mut buf = vec![0u8; 4096].into_boxed_slice();
buf[0] = 0x5A;
let guard = registry.register_owned(buf).await.expect("register");
assert_eq!(guard.len(), 4096);
let (returned, outcome) = guard.unregister_owned(T).await.expect("unregister_owned");
assert_eq!(outcome, Deregistered::Drained);
assert_eq!(returned.len(), 4096);
assert_eq!(
returned[0], 0x5A,
"the buffer that came back is not the one that went in"
);
assert_eq!(backend.live(), 0);
}
#[tokio::test]
async fn register_external_memory_smoke() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let leaked: &'static mut [u8] = Box::leak(vec![7u8; 8192].into_boxed_slice());
let ptr = std::ptr::NonNull::new(leaked.as_mut_ptr()).expect("non-null");
let guard = unsafe { registry.register_external(ptr, leaked.len()) }
.await
.expect("register external");
assert_eq!(guard.addr(), ptr.as_ptr() as u64);
assert_eq!(guard.len(), 8192);
let (eff_addr, eff_len) = guard.effective_range();
assert!(
eff_addr <= guard.addr() && eff_len >= guard.len(),
"the effective range must cover the requested one"
);
assert_ne!(
guard.generation(),
0,
"every registration gets a generation"
);
assert_eq!(
guard.unregister(T).await.expect("unregister"),
Deregistered::Drained
);
assert_eq!(backend.live(), 0);
}
#[tokio::test]
async fn register_external_refuses_degenerate_ranges() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let mut byte = 0u8;
let ptr = std::ptr::NonNull::new(&mut byte as *mut u8).expect("non-null");
let err = unsafe { registry.register_external(ptr, 0) }
.await
.unwrap_err();
assert_eq!(err, RdmaError::OutOfRange);
assert_eq!(
backend.live(),
0,
"a refused registration must not reach the backend"
);
}
#[tokio::test]
async fn shutdown_deregisters_regions_and_arenas() {
let cfg = RdmaConfig {
pool: small_pool_config(),
..RdmaConfig::default()
};
let (backend, registry) = mock_registry(cfg);
let guard = registry
.register_owned(vec![0u8; 8192].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
let buf = registry.alloc_pinned(4096).await.expect("alloc pinned");
assert!(
backend.live() >= 2,
"expected an external region and an arena"
);
registry.shutdown(T).await;
assert_eq!(backend.live(), 0, "shutdown left something registered");
assert!(
watch.is_deregistered(),
"shutdown did not resolve the latch"
);
assert_eq!(registry.registered_bytes(), 0);
assert_eq!(registry.pool().arena_count(), 0);
drop(buf);
drop(guard);
}
#[tokio::test]
async fn shutdown_gates_new_registrations() {
let (_backend, registry) = mock_registry(RdmaConfig::default());
registry.shutdown(T).await;
let refused = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect_err("a gated registry must refuse");
assert_eq!(refused.cause, RdmaError::ShuttingDown);
assert_eq!(
refused.buffer.map(|b| b.len()),
Some(4096),
"a refused registration must hand the caller buffer back"
);
assert_eq!(
registry.alloc_pinned(4096).await.err(),
Some(RdmaError::ShuttingDown),
"pool allocation must go through the same gate as external registration"
);
}
#[tokio::test]
async fn shutdown_is_idempotent() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect("register");
drop(guard);
registry.shutdown(T).await;
let calls = backend.unmap_calls();
registry.shutdown(T).await;
assert_eq!(
backend.unmap_calls(),
calls,
"a second shutdown re-issued unmaps"
);
assert_eq!(registry.registered_bytes(), 0);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn registration_in_flight_is_not_missed_by_shutdown() {
let (backend, registry) = mock_registry(RdmaConfig::default());
*backend.map_delay.lock() = Some(Duration::from_millis(200));
let registering = {
let registry = Arc::clone(®istry);
tokio::spawn(async move {
registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
})
};
tokio::time::sleep(Duration::from_millis(50)).await;
registry.shutdown(T).await;
let outcome = tokio::time::timeout(T, registering)
.await
.expect("the registration must resolve")
.expect("task panicked");
match outcome {
Ok(guard) => {
assert!(
guard.is_deregistered(),
"a registration admitted before the gate closed was missed by the sweep"
);
}
Err(e) if e.cause == RdmaError::ShuttingDown => {
assert!(e.buffer.is_some(), "a refusal must return the buffer");
}
Err(e) => panic!("unexpected registration failure: {}", e.cause),
}
assert_eq!(
backend.live(),
0,
"shutdown returned with memory still registered"
);
assert_eq!(registry.registered_bytes(), 0);
}
struct UcxHarness {
transport: Arc<crate::transports::ucx::UcxTransport>,
registry: Arc<RdmaRegistry>,
_streams: crate::transports::DataStreams,
}
impl UcxHarness {
async fn start(cfg: RdmaConfig) -> Self {
use velo_ext::InstanceId;
let transport = Arc::new(
crate::transports::ucx::UcxTransportBuilder::new()
.tls("tcp")
.build()
.expect("build ucx transport"),
);
let (adapter, streams) = crate::transports::make_channels();
tokio::time::timeout(
T,
transport.start(
InstanceId::new_v4(),
adapter,
tokio::runtime::Handle::current(),
),
)
.await
.expect("ucx startup must not hang")
.expect("start ucx transport");
let registry = Arc::new(RdmaRegistry::new(
UcxBackend::new(transport.rdma_endpoint()),
cfg,
tokio::runtime::Handle::current(),
None,
));
Self {
transport,
registry,
_streams: streams,
}
}
fn live_regions(&self) -> usize {
self.transport.live_regions()
}
}
#[tokio::test(flavor = "multi_thread")]
async fn ucx_backend_maps_and_unmaps() {
let harness = UcxHarness::start(RdmaConfig::default()).await;
assert_eq!(harness.live_regions(), 0);
assert_eq!(harness.registry.backend_key(), "ucx");
let guard = harness
.registry
.register_owned(vec![0u8; 256 * 1024].into_boxed_slice())
.await
.expect("register with ucx");
assert_eq!(harness.live_regions(), 1, "ucx did not register the range");
let remote = guard.remote();
assert!(
!remote.packed_key.is_empty(),
"a real registration must produce a packed key"
);
let (eff_addr, eff_len) = guard.effective_range();
assert!(
eff_addr <= guard.addr() && eff_len >= guard.len(),
"ucx reported an effective range that does not cover the request"
);
assert_eq!(
guard.unregister(T).await.expect("unregister"),
Deregistered::Drained
);
assert_eq!(harness.live_regions(), 0, "ucx still holds the region");
assert_eq!(harness.registry.registered_bytes(), 0);
harness.transport.shutdown();
}
#[tokio::test(flavor = "multi_thread")]
async fn ucx_pool_arena_is_one_registration() {
let cfg = RdmaConfig {
pool: small_pool_config(),
..RdmaConfig::default()
};
let harness = UcxHarness::start(cfg).await;
let a = harness.registry.alloc_pinned(4096).await.expect("alloc a");
let b = harness.registry.alloc_pinned(4096).await.expect("alloc b");
assert_eq!(
harness.live_regions(),
1,
"two suballocations from one arena must be one ucx registration"
);
assert_eq!(a.backend_region_id(), b.backend_region_id());
assert_ne!(a.arena_offset(), b.arena_offset());
drop((a, b));
harness.registry.shutdown(T).await;
assert_eq!(harness.live_regions(), 0, "the arena was not unmapped");
harness.transport.shutdown();
}
#[tokio::test(flavor = "multi_thread")]
async fn velo_graceful_shutdown_deregisters_before_transport_teardown() {
let transport = Arc::new(
crate::transports::ucx::UcxTransportBuilder::new()
.tls("tcp")
.build()
.expect("build ucx transport"),
);
let velo = crate::Velo::builder()
.add_ucx_transport(Arc::clone(&transport))
.build()
.await
.expect("build velo");
let guard = velo
.register_owned(vec![0u8; 128 * 1024].into_boxed_slice())
.await
.expect("register through the velo facade");
let watch = guard.watch();
assert_eq!(transport.live_regions(), 1);
assert!(
velo.rdma_registered_bytes() >= 128 * 1024,
"the budget must charge at least the requested length"
);
velo.graceful_shutdown(velo_ext::ShutdownPolicy::Timeout(T))
.await;
assert!(
watch.is_deregistered(),
"graceful_shutdown returned without resolving the deregistered() latch"
);
assert_eq!(
transport.live_regions(),
0,
"graceful_shutdown returned with memory still registered with ucx"
);
assert_eq!(
transport.live_rkeys(),
0,
"an unpacked rkey outlived shutdown"
);
assert_eq!(velo.rdma_registered_bytes(), 0);
drop(guard);
}
#[tokio::test(flavor = "multi_thread")]
async fn ucx_dropped_guard_deregisters() {
let harness = UcxHarness::start(RdmaConfig::default()).await;
let guard = harness
.registry
.register_owned(vec![0u8; 64 * 1024].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
assert_eq!(harness.live_regions(), 1);
drop(guard);
tokio::time::timeout(T, watch.deregistered())
.await
.expect("a dropped guard must deregister against ucx too");
assert_eq!(
harness.live_regions(),
0,
"the warn claimed a background deregistration that never reached ucx"
);
harness.transport.shutdown();
}
#[tokio::test(flavor = "multi_thread")]
async fn velo_without_ucx_transport_refuses_registration() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
let transport = Arc::new(
crate::transports::tcp::TcpTransportBuilder::new()
.from_listener(listener)
.expect("listener")
.build()
.expect("build tcp transport"),
);
let velo = crate::Velo::builder()
.add_transport(transport)
.build()
.await
.expect("build velo");
let err = velo
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect_err("registration must be refused without a ucx transport");
assert_eq!(
err.cause,
RdmaError::NotConfigured,
"a missing backend is a permanent configuration fact, not a retryable backend error"
);
assert!(
err.buffer.is_some(),
"the caller buffer must come back from a refusal"
);
assert_eq!(velo.rdma_registered_bytes(), 0);
velo.graceful_shutdown(velo_ext::ShutdownPolicy::Timeout(T))
.await;
}
#[tokio::test(flavor = "multi_thread")]
async fn cancelled_registration_returns_its_budget() {
let (backend, registry) = mock_registry(RdmaConfig::default());
*backend.map_delay.lock() = Some(Duration::from_millis(500));
for _ in 0..4 {
let attempt = registry.register_owned(vec![0u8; 64 * 1024].into_boxed_slice());
assert!(
tokio::time::timeout(Duration::from_millis(50), attempt)
.await
.is_err(),
"the registration was supposed to be cancelled mid-map"
);
}
assert_eq!(
registry.registered_bytes(),
0,
"cancelled registrations leaked their budget claim"
);
*backend.map_delay.lock() = None;
let guard = registry
.register_owned(vec![0u8; 64 * 1024].into_boxed_slice())
.await
.expect("a registration after cancellations must still be admitted");
assert_eq!(
guard.unregister(T).await.expect("unregister"),
Deregistered::Drained
);
assert_eq!(registry.registered_bytes(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn cancelled_pool_alloc_returns_its_budget() {
let cfg = RdmaConfig {
pool: small_pool_config(),
..RdmaConfig::default()
};
let (backend, registry) = mock_registry(cfg);
*backend.map_delay.lock() = Some(Duration::from_millis(500));
for _ in 0..4 {
let attempt = registry.alloc_pinned(4096);
assert!(
tokio::time::timeout(Duration::from_millis(50), attempt)
.await
.is_err(),
"the allocation was supposed to be cancelled mid-map"
);
}
assert_eq!(
registry.registered_bytes(),
0,
"cancelled pool allocations leaked their budget claim"
);
assert_eq!(
registry.pool().arena_count(),
0,
"a cancelled map left an arena in the set"
);
*backend.map_delay.lock() = None;
let buf = registry
.alloc_pinned(4096)
.await
.expect("the pool must still be usable after cancellations");
drop(buf);
}
#[tokio::test]
async fn unaligned_arena_sizes_balance_the_budget() {
let cfg = RdmaConfig {
pool: RdmaPoolConfig {
initial_arena_bytes: 100_000,
max_arena_bytes: 300_000,
dedicated_arena_min: 1 << 30,
registered_bytes_budget: 4_000_000,
..small_pool_config()
},
..RdmaConfig::default()
};
let (_backend, registry) = mock_registry(cfg);
let mut held = Vec::new();
for _ in 0..6 {
held.push(registry.alloc_pinned(30_000).await.expect("alloc"));
}
let registered = registry.registered_bytes();
assert!(registered > 0, "nothing was accounted as registered");
assert_eq!(
registered % GRANULE as u64,
0,
"the budget claim must be the page-rounded length that is actually mapped"
);
drop(held);
registry.shutdown(T).await;
assert_eq!(
registry.registered_bytes(),
0,
"reserve and release disagreed on the length; the budget is now permanently skewed"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn concurrent_registration_never_overshoots_the_budget() {
const TASKS: usize = 24;
const ROUNDS: usize = 8;
const CHUNK: usize = 16 * GRANULE;
const BUDGET: u64 = (8 * 16 * GRANULE) as u64;
let cfg = RdmaConfig {
pool: RdmaPoolConfig {
registered_bytes_budget: BUDGET,
..small_pool_config()
},
..RdmaConfig::default()
};
let (_backend, registry) = mock_registry(cfg);
let admitted = Arc::new(parking_lot::Mutex::new(Vec::new()));
let mut tasks = Vec::new();
for _ in 0..TASKS {
let registry = Arc::clone(®istry);
let admitted = Arc::clone(&admitted);
tasks.push(tokio::spawn(async move {
for _ in 0..ROUNDS {
if let Ok(guard) = registry
.register_owned(vec![0u8; CHUNK].into_boxed_slice())
.await
{
admitted.lock().push(guard);
}
tokio::task::yield_now().await;
}
}));
}
for task in tasks {
tokio::time::timeout(T, task)
.await
.expect("a registration task did not finish; the budget CAS loop may be livelocked")
.expect("task panicked");
}
let live = admitted.lock().len();
assert!(
live > 0,
"nothing was admitted at all; the test proves nothing"
);
assert!(
(live * CHUNK) as u64 <= BUDGET,
"{live} concurrent registrations of {CHUNK} B were admitted against a {BUDGET} B \
budget: the ceiling did not hold under concurrency"
);
admitted.lock().clear();
registry.shutdown(T).await;
assert_eq!(registry.registered_bytes(), 0, "the budget did not balance");
}
#[tokio::test]
async fn external_registration_charges_page_enclosing_bytes() {
let (_backend, registry) = mock_registry(RdmaConfig::default());
let leaked: &'static mut [u8] = Box::leak(vec![0u8; 4097].into_boxed_slice());
let ptr = std::ptr::NonNull::new(leaked.as_mut_ptr()).expect("non-null");
let guard = unsafe { registry.register_external(ptr, leaked.len()) }
.await
.expect("register");
let charged = registry.registered_bytes();
assert!(
charged >= 4097,
"the charge must cover at least the requested range: {charged}"
);
assert_eq!(
charged % GRANULE as u64,
0,
"the charge must be a whole number of pages: {charged}"
);
assert_eq!(
guard.unregister(T).await.expect("unregister"),
Deregistered::Drained
);
assert_eq!(
registry.registered_bytes(),
0,
"reserve and release disagreed; the budget is now permanently skewed"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn shutdown_latches_regions_whose_unmap_was_never_confirmed() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 8192].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
backend.refuse_unmap.store(true, Ordering::SeqCst);
registry.shutdown(Duration::from_millis(200)).await;
assert!(
!watch.is_deregistered(),
"an unconfirmed unmap must not latch during the sweep; that is the point of the sweep \
being honest about what it knows"
);
backend.force_teardown();
registry.latch_all_deregistered();
assert!(
watch.is_deregistered(),
"the end of velo shutdown must resolve every surviving latch"
);
tokio::time::timeout(T, watch.deregistered())
.await
.expect("deregistered() must resolve once shutdown has completed");
assert_eq!(
registry.registered_bytes(),
0,
"regions released at the end of shutdown must give their budget back"
);
drop(guard);
}
#[test]
fn abandoned_runtime_leaks_owned_buffers_rather_than_freeing_them() {
let _serialised = LEAK_COUNTER.blocking_lock();
let before = super::region::LEAKED_BUFFERS.load(Ordering::SeqCst);
let backend = MockBackend::new();
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("runtime");
runtime.block_on({
let backend = Arc::clone(&backend);
async move {
let registry = Arc::new(RdmaRegistry::new(
backend as Arc<dyn RdmaBackend>,
RdmaConfig::default(),
tokio::runtime::Handle::current(),
None,
));
let guard = registry
.register_owned(vec![0xC5u8; 8192].into_boxed_slice())
.await
.expect("register");
drop(guard);
drop(registry);
}
});
drop(runtime);
assert_eq!(
backend.unmap_calls(),
0,
"the deregistration was supposed to never run; the scenario is not what it claims"
);
assert_eq!(
backend.live(),
1,
"the backend still holds the registration, so the pages are still pinned"
);
assert!(
super::region::LEAKED_BUFFERS.load(Ordering::SeqCst) > before,
"the owned buffer was freed while its pages were still pinned"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn wait_deregistered_survives_a_latch_at_the_check() {
const ITERATIONS: usize = 4096;
const SPIN_SWEEP: usize = 512;
const WAITER_LEAD: usize = 600;
const GRACE: Duration = Duration::from_secs(2);
fn burn(rounds: usize) {
let mut sink = 0usize;
for k in 0..rounds {
sink = std::hint::black_box(sink.wrapping_add(k));
}
}
let (_backend, registry) = mock_registry(RdmaConfig::default());
for iteration in 0..ITERATIONS {
let guard = registry
.register_owned(vec![0u8; GRANULE].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
let inner = guard.watch();
let armed = Arc::new(AtomicBool::new(false));
let spins = iteration % SPIN_SWEEP;
let latcher_armed = Arc::clone(&armed);
let latcher = std::thread::spawn(move || {
while !latcher_armed.load(Ordering::Acquire) {
std::hint::spin_loop();
}
burn(spins);
inner.latch_for_test();
});
let mut waiter = tokio::spawn(async move {
armed.store(true, Ordering::Release);
burn(WAITER_LEAD);
watch.deregistered().await;
});
let finished = tokio::time::timeout(Duration::from_millis(200), &mut waiter).await;
latcher.join().expect("latcher thread panicked");
let joined = match finished {
Ok(joined) => joined,
Err(_) => tokio::time::timeout(GRACE, &mut waiter)
.await
.unwrap_or_else(|_| {
panic!(
"wait_deregistered lost the latch wakeup (iteration {iteration}, \
spins {spins})"
)
}),
};
joined.expect("waiter task panicked");
std::mem::forget(guard);
}
registry.shutdown(T).await;
}
#[tokio::test]
async fn dropping_a_buffer_does_not_itself_unmap_its_arena() {
let cfg = small_pool_config();
let (backend, pool) = mock_pool(cfg.clone());
let size = cfg.dedicated_arena_min as usize;
let first = pool.alloc(size).await.expect("first oversize");
assert_eq!(pool.arena_count(), 1);
let charged = pool.registered_bytes();
drop(first);
assert_eq!(
pool.live_allocations(),
0,
"the suballocation was returned to its arena"
);
assert_eq!(
backend.live(),
1,
"dropping a PinnedBuf unmapped its arena; reclamation must happen on the sweep, \
where the shutdown gate can order it"
);
assert_eq!(
pool.registered_bytes(),
charged,
"the budget was released without an unmap having been confirmed"
);
let second = pool.alloc(size).await.expect("second oversize");
assert_eq!(
pool.arena_count(),
2,
"an unreclaimed dedicated arena was handed to another request"
);
assert_eq!(
backend.live(),
2,
"each oversize request maps its own arena"
);
drop(second);
assert_eq!(pool.reclaim_idle().await, 2);
assert_eq!(pool.registered_bytes(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn latch_refuses_while_the_backend_still_holds_registrations() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 8192].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
backend.refuse_unmap.store(true, Ordering::SeqCst);
registry.shutdown(Duration::from_millis(100)).await;
assert_eq!(backend.live_registrations(), Some(1));
registry.latch_all_deregistered();
assert!(
!watch.is_deregistered(),
"the latch opened while the backend still had the region pinned; a caller would now \
free live memory, and the owned buffer would be freed by drop glue"
);
backend.force_teardown();
registry.latch_all_deregistered();
assert!(
watch.is_deregistered(),
"the latch must open once nothing is pinned"
);
drop(guard);
}
#[tokio::test(flavor = "multi_thread")]
async fn latched_regions_free_their_buffers_normally() {
let _serialised = LEAK_COUNTER.lock().await;
let before = super::region::LEAKED_BUFFERS.load(Ordering::SeqCst);
let (backend, registry) = mock_registry(RdmaConfig::default());
let guard = registry
.register_owned(vec![0u8; 8192].into_boxed_slice())
.await
.expect("register");
backend.refuse_unmap.store(true, Ordering::SeqCst);
registry.shutdown(Duration::from_millis(100)).await;
backend.force_teardown();
registry.latch_all_deregistered();
assert!(guard.is_deregistered());
drop(guard);
drop(registry);
assert_eq!(
super::region::LEAKED_BUFFERS.load(Ordering::SeqCst),
before,
"an orderly shutdown leaked an owned buffer; the leak gate never closes"
);
}
#[tokio::test(flavor = "current_thread")]
async fn unregister_owned_refuses_before_deregistering() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let leaked: &'static mut [u8] = Box::leak(vec![0u8; 4096].into_boxed_slice());
let ptr = std::ptr::NonNull::new(leaked.as_mut_ptr()).expect("non-null");
let guard = unsafe { registry.register_external(ptr, leaked.len()) }
.await
.expect("register");
let watch = guard.watch();
let err = guard
.unregister_owned(T)
.await
.expect_err("a caller-owned region has no buffer to hand back");
assert_eq!(err, RdmaError::NotOwned);
assert_eq!(
backend.unmap_calls(),
0,
"the region was deregistered before the ownership check; the error described the \
opposite of what happened"
);
assert!(!watch.is_deregistered());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn concurrent_graceful_shutdowns_are_serialised() {
const CALLERS: usize = 8;
let transport = Arc::new(
crate::transports::ucx::UcxTransportBuilder::new()
.tls("tcp")
.build()
.expect("build ucx transport"),
);
let velo = crate::Velo::builder()
.add_ucx_transport(Arc::clone(&transport))
.build()
.await
.expect("build velo");
let guard = velo
.register_owned(vec![0u8; 128 * 1024].into_boxed_slice())
.await
.expect("register");
let watch = guard.watch();
assert_eq!(transport.live_regions(), 1);
let mut callers = Vec::new();
for _ in 0..CALLERS {
let velo = crate::Velo::clone(&velo);
callers.push(tokio::spawn(async move {
velo.graceful_shutdown(velo_ext::ShutdownPolicy::Timeout(T))
.await
}));
}
for caller in callers {
tokio::time::timeout(T, caller)
.await
.expect("every shutdown caller must return; a serialised one must not deadlock")
.expect("task panicked");
}
assert_eq!(
velo.rdma().expect("registry").sweep_count(),
1,
"the shutdown sequence ran more than once; concurrent callers were not serialised"
);
assert!(watch.is_deregistered());
assert_eq!(
transport.live_regions(),
0,
"shutdown returned with memory still registered"
);
assert_eq!(transport.live_rkeys(), 0);
assert_eq!(velo.rdma_registered_bytes(), 0);
drop(guard);
}
#[test]
fn budget_charge_reports_whether_it_stayed_within_the_ceiling() {
let budget = Arc::new(Budget::new(1000, None));
assert!(budget.charge(400), "400 of 1000 is within the ceiling");
assert_eq!(budget.registered(), 400);
assert!(
budget.charge(600),
"exactly at the ceiling is still within it"
);
assert_eq!(budget.registered(), 1000);
assert!(
!budget.charge(1),
"past the ceiling must report the overshoot"
);
assert_eq!(
budget.registered(),
1001,
"charge adds unconditionally: the pages are already pinned"
);
budget.release(u64::MAX);
assert_eq!(budget.registered(), 0);
}
#[tokio::test]
async fn a_backend_that_pins_more_than_estimated_tops_up_the_claim() {
let (backend, registry) = mock_registry(RdmaConfig::default());
backend
.extra_effective
.store(2 * GRANULE as u64, Ordering::SeqCst);
let guard = registry
.register_owned(vec![0u8; 4096].into_boxed_slice())
.await
.expect("register");
let charged = registry.registered_bytes();
let (_, effective_len) = guard.effective_range();
assert_eq!(
charged, effective_len,
"the claim must match what the backend says it pinned"
);
assert_eq!(
guard.unregister(T).await.expect("unregister"),
Deregistered::Drained
);
assert_eq!(
registry.registered_bytes(),
0,
"the top-up was not released; the budget is now permanently skewed"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_refused_latch_retains_pool_arenas() {
let cfg = RdmaConfig {
pool: small_pool_config(),
..RdmaConfig::default()
};
let (backend, registry) = mock_registry(cfg);
let buf = registry.alloc_pinned(4096).await.expect("alloc");
let charged = registry.registered_bytes();
assert!(charged > 0);
drop(buf);
backend.refuse_unmap.store(true, Ordering::SeqCst);
registry.shutdown(Duration::from_millis(100)).await;
assert_eq!(
registry.registered_bytes(),
charged,
"an unconfirmed arena must keep its budget charge; the pages may still be pinned"
);
assert_eq!(backend.live_registrations(), Some(1));
registry.latch_all_deregistered();
assert_eq!(
registry.registered_bytes(),
charged,
"the pool was released while the backend still held the arena"
);
backend.force_teardown();
registry.latch_all_deregistered();
assert_eq!(
registry.registered_bytes(),
0,
"a successful latch must release the arenas the sweep could not confirm"
);
}
fn bare_region(buffer: Box<[u8]>) -> Arc<RegionInner> {
let ptr = buffer.as_ptr() as usize;
let len = buffer.len();
Arc::new(RegionInner::new(RegionParts {
id: 1,
generation: 1,
backend_region_id: 1,
ptr,
len,
packed_key: Bytes::from_static(b"key"),
effective_addr: ptr as u64,
effective_len: len as u64,
owned: Some(buffer),
charged: len as u64,
shutdown: tokio_util::sync::CancellationToken::new(),
}))
}
#[test]
fn a_copy_never_overlaps_the_deregistration_latch() {
const READERS: usize = 8;
const ROUNDS: usize = 400;
for _ in 0..16 {
let region = bare_region(vec![0u8; 4096].into_boxed_slice());
let start = Arc::new(std::sync::Barrier::new(READERS + 1));
let readers: Vec<_> = (0..READERS)
.map(|_| {
let region = Arc::clone(®ion);
let start = Arc::clone(&start);
std::thread::spawn(move || {
start.wait();
for _ in 0..ROUNDS {
region.with_live(|| {
assert!(
!region.is_deregistered(),
"the latch closed while a copy was in progress; the region's \
owner may already have freed these bytes"
);
});
}
})
})
.collect();
start.wait();
region.latch_deregistered();
for reader in readers {
reader.join().expect("a reader observed the latch mid-copy");
}
assert!(region.is_deregistered());
assert!(
region.with_live(|| ()).is_none(),
"every read after the latch must refuse"
);
drop(region);
}
}
#[test]
fn an_anchor_refuses_to_read_a_region_that_has_been_released() {
use super::region::RegionWatch;
use crate::rendezvous::descriptor::DescriptorBackend;
use crate::rendezvous::pinned::PinnedSlot;
const LEN: u64 = 3 * 512 * 1024 + 977;
let region = bare_region(vec![0x5Au8; LEN as usize].into_boxed_slice());
let slot = PinnedSlot::from_region(
region.in_flight.acquire(),
RegionWatch::for_test(Arc::clone(®ion)),
DescriptorBackend::Ucx,
region.ptr as u64,
LEN,
1,
Bytes::from_static(b"packed-key"),
);
let read = slot
.read_at(0, LEN as usize)
.expect("read while registered");
assert_eq!(read.len(), LEN as usize);
assert!(read.iter().all(|b| *b == 0x5A));
assert!(slot.descriptor().is_some());
assert!(slot.is_live());
region.latch_deregistered();
assert!(
slot.read_at(0, LEN as usize).is_none(),
"the anchor read a region whose owner has been told it may free it"
);
assert!(slot.to_bytes().is_none());
assert!(
slot.descriptor().is_none(),
"a descriptor for a released region would send a peer's NIC at freed memory"
);
assert!(!slot.is_live());
drop(slot);
drop(region);
}
fn chunk_pattern(i: usize) -> u8 {
((i % 251) ^ ((i / 251) % 257)) as u8
}
#[test]
fn a_copy_spanning_several_gate_chunks_reassembles_in_order() {
const LEN: usize = 2 * 512 * 1024 + 1_237;
let backing: Vec<u8> = (0..LEN).map(chunk_pattern).collect();
let region = bare_region(backing.clone().into_boxed_slice());
let slot = crate::rendezvous::pinned::PinnedSlot::from_region(
region.in_flight.acquire(),
super::region::RegionWatch::for_test(Arc::clone(®ion)),
crate::rendezvous::descriptor::DescriptorBackend::Ucx,
region.ptr as u64,
LEN as u64,
1,
Bytes::from_static(b"packed-key"),
);
let whole = slot.to_bytes().expect("copy the whole anchor");
assert_eq!(whole.len(), LEN);
assert_eq!(
&whole[..],
&backing[..],
"the chunk loop reassembled the anchor out of order"
);
let straddle = slot
.read_at(512 * 1024 - 7, 64)
.expect("read across a chunk boundary");
assert_eq!(&straddle[..], &backing[512 * 1024 - 7..512 * 1024 - 7 + 64]);
region.latch_deregistered();
drop(slot);
drop(region);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_transfer_is_refused_once_the_registration_gate_is_closed() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let buf = registry.alloc_pinned(4096).await.expect("alloc");
let req = BackendGet {
peer: velo_ext::InstanceId::new_v4(),
remote_addr: 0x1000,
packed_key: Bytes::from_static(b"key"),
local_region_id: buf.backend_region_id(),
local_offset: buf.arena_offset(),
len: 4096,
};
registry
.get(req.clone())
.await
.expect("get before shutdown");
registry.shutdown(T).await;
assert_eq!(
registry.get(req).await,
Err(RdmaError::ShuttingDown),
"a transfer submitted after the gate closed would make the arena sweep's in-flight \
sample a check-then-act"
);
drop(buf);
let _ = backend;
}
#[tokio::test(flavor = "multi_thread")]
async fn the_arena_sweep_waits_for_a_transfer_hold() {
let (backend, registry) = mock_registry(RdmaConfig::default());
let buf = registry.alloc_pinned(4096).await.expect("alloc");
let hold = buf.hold();
let releaser = tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(300)).await;
drop(hold);
});
let started = std::time::Instant::now();
registry.shutdown(T).await;
let waited = started.elapsed();
releaser.await.expect("releaser");
assert!(
waited >= Duration::from_millis(200),
"the sweep unmapped after {waited:?}, without waiting for the transfer holding one of \
its arenas"
);
assert_eq!(
backend.live(),
0,
"the arena should still have been unmapped"
);
drop(buf);
}
#[test]
fn shutdown_demotes_a_pinned_slot_rather_than_dropping_it() {
use super::region::RegionWatch;
use crate::rendezvous::pinned::PinnedSlot;
use crate::rendezvous::store::{DEFAULT_CHUNK_SIZE, DataStore, SlotBody, StageMode};
const LEN: usize = 3 * 512 * 1024 + 41;
let backing: Vec<u8> = (0..LEN).map(|i| (i.wrapping_mul(17)) as u8).collect();
let region = bare_region(backing.clone().into_boxed_slice());
let store = DataStore::new();
let local_id = store.register_body(
SlotBody::Pinned(PinnedSlot::from_region(
region.in_flight.acquire(),
RegionWatch::for_test(Arc::clone(®ion)),
crate::rendezvous::descriptor::DescriptorBackend::Ucx,
region.ptr as u64,
LEN as u64,
1,
Bytes::from_static(b"packed-key"),
)),
None,
);
assert_eq!(store.stage_mode(local_id), Some(StageMode::Pinned));
assert_eq!(
region.in_flight.in_flight_count(),
1,
"the anchor should be holding the region open"
);
let lease = store.acquire_read_lock(local_id).expect("lease");
let (transfer, chunk_size, chunks) = store
.create_transfer(local_id, lease, DEFAULT_CHUNK_SIZE)
.expect("transfer");
let first = store.get_chunk(transfer, 0).expect("the first chunk");
assert_eq!(&first[..], &backing[..chunk_size as usize]);
let (demoted, dropped) = store.demote_pinned_slots();
assert_eq!((demoted, dropped), (1, 0));
assert_eq!(
store.stage_mode(local_id),
Some(StageMode::InMemory),
"the slot should have moved to the heap, not vanished"
);
assert_eq!(
region.in_flight.in_flight_count(),
0,
"demotion did not release the region guard the sweep is waiting on"
);
for index in 1..chunks {
let at = index as usize * chunk_size as usize;
let chunk = store
.get_chunk(transfer, index)
.unwrap_or_else(|| panic!("chunk {index} of an admitted pull was dropped"));
assert_eq!(&chunk[..], &backing[at..at + chunk.len()]);
}
assert_eq!(
&store.get_data(local_id).expect("the whole slot")[..],
&backing[..]
);
region.latch_deregistered();
drop(store);
drop(region);
}
#[test]
fn shutdown_drops_a_slot_whose_region_has_already_gone() {
use super::region::RegionWatch;
use crate::rendezvous::pinned::PinnedSlot;
use crate::rendezvous::store::{DataStore, SlotBody};
let region = bare_region(vec![3u8; 4096].into_boxed_slice());
let store = DataStore::new();
let local_id = store.register_body(
SlotBody::Pinned(PinnedSlot::from_region(
region.in_flight.acquire(),
RegionWatch::for_test(Arc::clone(®ion)),
crate::rendezvous::descriptor::DescriptorBackend::Ucx,
region.ptr as u64,
4096,
1,
Bytes::from_static(b"packed-key"),
)),
None,
);
region.latch_deregistered();
assert_eq!(store.demote_pinned_slots(), (0, 1));
assert!(
store.metadata(local_id).is_none(),
"a slot over a released region has nothing to demote to"
);
drop(region);
}