use std::collections::HashMap;
use std::fmt::Debug;
use std::num::NonZeroU64;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use crate::deferred::{
AllocationReleaseOutcome, AllocationReleaseState, DeferredReleaseDisposition,
DeferredReleaseQueue, PreparedAllocationRelease, PreparedReleasePins, QuarantineReason,
QuarantinedAllocation,
};
use crate::{
AllocationCommitRange, DeviceAllocator, DeviceKey, MemoryError, SharedDevicePrefix,
SharedPrefixCommitInfo,
};
static NEXT_REGISTRY_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct OpaqueIdentity {
registry: NonZeroU64,
serial: NonZeroU64,
}
impl Debug for OpaqueIdentity {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("opaque")
.field(&self.registry)
.field(&self.serial)
.finish()
}
}
macro_rules! opaque_identity {
($name:ident) => {
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct $name(OpaqueIdentity);
impl Debug for $name {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple(stringify!($name))
.field(&self.0)
.finish()
}
}
};
}
opaque_identity!(AuthorityIdentity);
opaque_identity!(ProviderContextIdentity);
opaque_identity!(MechanismIdentity);
opaque_identity!(BindingId);
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct BindingGeneration(NonZeroU64);
impl Debug for BindingGeneration {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("BindingGeneration")
.field(&self.0)
.finish()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct AllocationGeneration(NonZeroU64);
impl Debug for AllocationGeneration {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("AllocationGeneration")
.field(&self.0)
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BindingIdentity {
id: BindingId,
generation: BindingGeneration,
device: DeviceKey,
mechanism: MechanismIdentity,
provider_context: ProviderContextIdentity,
authority: AuthorityIdentity,
}
impl BindingIdentity {
pub const fn id(self) -> BindingId {
self.id
}
pub const fn generation(self) -> BindingGeneration {
self.generation
}
pub const fn device(self) -> DeviceKey {
self.device
}
pub const fn mechanism(self) -> MechanismIdentity {
self.mechanism
}
pub const fn provider_context(self) -> ProviderContextIdentity {
self.provider_context
}
pub const fn authority(self) -> AuthorityIdentity {
self.authority
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AllocationIdentity {
binding: BindingIdentity,
generation: AllocationGeneration,
}
impl AllocationIdentity {
pub const fn binding(self) -> BindingIdentity {
self.binding
}
pub const fn generation(self) -> AllocationGeneration {
self.generation
}
}
pub trait BindingResource: Send + Sync + Debug {}
impl<T> BindingResource for T where T: Send + Sync + Debug {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MechanismCoherence {
SelfContained,
TrustedComposite,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MechanismLifecycle {
Active,
Retired,
DeviceLost,
Terminated,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MechanismSnapshot {
pub identity: MechanismIdentity,
pub device: DeviceKey,
pub provider_context: ProviderContextIdentity,
pub authority: AuthorityIdentity,
pub coherence: MechanismCoherence,
pub lifecycle: MechanismLifecycle,
pub live_allocations: usize,
pub active_operations: usize,
pub queued_releases: usize,
pub quarantined_allocations: usize,
pub quarantined_bytes: u64,
}
impl MechanismSnapshot {
pub const fn retains_ownership(&self) -> bool {
self.live_allocations != 0 || self.queued_releases != 0 || self.quarantined_allocations != 0
}
}
#[derive(Debug, thiserror::Error)]
pub enum BindingError {
#[error("binding identity space is exhausted")]
IdentityExhausted,
#[error("the {kind} belongs to another binding registry")]
ForeignRegistry { kind: &'static str },
#[error("cannot register {subject} for {actual:?}; its registered device is {expected:?}")]
DeviceMismatch {
subject: &'static str,
expected: DeviceKey,
actual: DeviceKey,
},
#[error("provider context {0:?} is not registered")]
UnregisteredProviderContext(ProviderContextIdentity),
#[error("provider context {0:?} still has a registered mechanism")]
ProviderContextInUse(ProviderContextIdentity),
#[error("authority {0:?} is not registered")]
UnregisteredAuthority(AuthorityIdentity),
#[error("authority {0:?} still has a registered mechanism")]
AuthorityInUse(AuthorityIdentity),
#[error("mechanism {0:?} is not registered")]
UnregisteredMechanism(MechanismIdentity),
#[error("device {0:?} has no selected memory mechanism")]
NoSelectedMechanism(DeviceKey),
#[error("mechanism {mechanism:?} is {lifecycle:?}; {operation} is not permitted")]
InactiveMechanism {
mechanism: MechanismIdentity,
lifecycle: MechanismLifecycle,
operation: &'static str,
},
#[error("device {device:?} was lost: {reason}")]
DeviceLost { device: DeviceKey, reason: Arc<str> },
#[error("binding mismatch: expected {expected:?}, but metadata belongs to {actual:?}")]
BindingMismatch {
expected: BindingId,
actual: BindingId,
},
#[error("allocation metadata {0:?} is stale or was already explicitly released")]
StaleAllocation(AllocationIdentity),
#[error(
"allocation {identity:?} still has {views} outstanding view(s); physical release is not \
permitted while a borrowed view or alias may still be used"
)]
OutstandingViews {
identity: AllocationIdentity,
views: usize,
},
#[error(
"release of allocation {identity:?} left {retained_bytes} byte(s) in the {state} state: \
{reason}"
)]
ReleaseQuarantined {
identity: AllocationIdentity,
state: AllocationReleaseState,
reason: QuarantineReason,
retained_bytes: u64,
},
#[error(
"mechanism {mechanism:?} still owns {quarantined} quarantined allocation(s); removal \
would lose ownership that was deliberately retained"
)]
QuarantinedOwnership {
mechanism: MechanismIdentity,
quarantined: usize,
},
#[error("view range {offset}..{end} exceeds allocation size {allocation_bytes}")]
ViewOutOfBounds {
offset: usize,
end: usize,
allocation_bytes: usize,
},
#[error("binding registry lock was poisoned while {operation}")]
LockPoisoned { operation: &'static str },
#[error(
"provider context {context:?} still has {active_operations} active mechanism operation(s)"
)]
ContextNotQuiescent {
context: ProviderContextIdentity,
active_operations: usize,
},
#[error(transparent)]
Memory(#[from] MemoryError),
}
#[derive(Debug)]
struct IdentitySource {
registry: NonZeroU64,
next: AtomicU64,
}
impl IdentitySource {
fn new() -> Result<Self, BindingError> {
let registry = next_nonzero(&NEXT_REGISTRY_ID)?;
Ok(Self {
registry,
next: AtomicU64::new(1),
})
}
fn opaque(&self) -> Result<OpaqueIdentity, BindingError> {
Ok(OpaqueIdentity {
registry: self.registry,
serial: next_nonzero(&self.next)?,
})
}
fn binding_generation(&self) -> Result<BindingGeneration, BindingError> {
Ok(BindingGeneration(next_nonzero(&self.next)?))
}
fn allocation_generation(&self) -> Result<AllocationGeneration, BindingError> {
Ok(AllocationGeneration(next_nonzero(&self.next)?))
}
}
fn next_nonzero(counter: &AtomicU64) -> Result<NonZeroU64, BindingError> {
let value = counter
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
current.checked_add(1)
})
.map_err(|_| BindingError::IdentityExhausted)?;
NonZeroU64::new(value).ok_or(BindingError::IdentityExhausted)
}
#[derive(Debug)]
struct ProviderContextEntry {
identity: ProviderContextIdentity,
device: DeviceKey,
_resource: Arc<dyn BindingResource>,
}
#[derive(Debug)]
struct AuthorityEntry {
identity: AuthorityIdentity,
device: DeviceKey,
_resource: Arc<dyn BindingResource>,
}
#[derive(Clone, Copy, Debug)]
struct AllocationRecord {
identity: AllocationIdentity,
ptr: usize,
bytes: usize,
align: usize,
}
#[derive(Debug)]
struct MechanismState {
lifecycle: MechanismLifecycle,
loss_reason: Option<Arc<str>>,
allocations: HashMap<AllocationGeneration, AllocationRecord>,
queued_releases: usize,
quarantined: HashMap<AllocationGeneration, QuarantinedAllocation>,
}
#[derive(Debug)]
struct MechanismResources {
allocator: Arc<dyn DeviceAllocator>,
authority: Arc<AuthorityEntry>,
context: Arc<ProviderContextEntry>,
}
#[derive(Debug)]
pub(crate) struct MechanismEntry {
identity: MechanismIdentity,
device: DeviceKey,
coherence: MechanismCoherence,
state: Mutex<MechanismState>,
active_operations: AtomicUsize,
resources: MechanismResources,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ReleaseGate {
Allowed,
DeviceLost,
Terminated,
Poisoned,
}
impl MechanismEntry {
fn allocator(&self) -> &dyn DeviceAllocator {
self.resources.allocator.as_ref()
}
fn context_identity(&self) -> ProviderContextIdentity {
self.resources.context.identity
}
fn authority_identity(&self) -> AuthorityIdentity {
self.resources.authority.identity
}
fn lock_state(
&self,
operation: &'static str,
) -> Result<MutexGuard<'_, MechanismState>, BindingError> {
self.state
.lock()
.map_err(|_| BindingError::LockPoisoned { operation })
}
fn inactive_error(&self, state: &MechanismState, operation: &'static str) -> BindingError {
match state.lifecycle {
MechanismLifecycle::DeviceLost => BindingError::DeviceLost {
device: self.device,
reason: state
.loss_reason
.clone()
.unwrap_or_else(|| Arc::from("provider did not supply a reason")),
},
lifecycle => BindingError::InactiveMechanism {
mechanism: self.identity,
lifecycle,
operation,
},
}
}
fn begin_active(
self: &Arc<Self>,
operation: &'static str,
) -> Result<MechanismOperation, BindingError> {
let state = self.lock_state(operation)?;
if state.lifecycle != MechanismLifecycle::Active {
return Err(self.inactive_error(&state, operation));
}
self.active_operations.fetch_add(1, Ordering::AcqRel);
drop(state);
Ok(MechanismOperation {
mechanism: Arc::clone(self),
})
}
fn begin_release(
self: &Arc<Self>,
expected_binding: BindingIdentity,
allocation: &BoundAllocation,
) -> Result<MechanismOperation, BindingError> {
let operation = "preparing explicit release";
let mut state = self.lock_state(operation)?;
match state.lifecycle {
MechanismLifecycle::Active | MechanismLifecycle::Retired => {}
_ => return Err(self.inactive_error(&state, operation)),
}
validate_binding_identity(expected_binding, allocation.identity.binding)?;
let Some(record) = state.allocations.get(&allocation.identity.generation) else {
return Err(BindingError::StaleAllocation(allocation.identity));
};
if !allocation.matches_record(record) {
return Err(BindingError::StaleAllocation(allocation.identity));
}
state.allocations.remove(&allocation.identity.generation);
state.queued_releases += 1;
self.active_operations.fetch_add(1, Ordering::AcqRel);
drop(state);
Ok(MechanismOperation {
mechanism: Arc::clone(self),
})
}
pub(crate) fn release_gate(&self) -> ReleaseGate {
let Ok(state) = self.state.lock() else {
return ReleaseGate::Poisoned;
};
match state.lifecycle {
MechanismLifecycle::Active | MechanismLifecycle::Retired => ReleaseGate::Allowed,
MechanismLifecycle::DeviceLost => ReleaseGate::DeviceLost,
MechanismLifecycle::Terminated => ReleaseGate::Terminated,
}
}
pub(crate) fn settle_release(&self, identity: AllocationIdentity) {
let Ok(mut state) = self.state.lock() else {
return;
};
state.queued_releases = state.queued_releases.saturating_sub(1);
debug_assert!(
!state.allocations.contains_key(&identity.generation),
"a settled release must not leave a live record behind"
);
}
pub(crate) fn settle_quarantine(&self, record: QuarantinedAllocation) {
let Ok(mut state) = self.state.lock() else {
return;
};
state.queued_releases = state.queued_releases.saturating_sub(1);
state.quarantined.insert(record.identity.generation, record);
}
pub(crate) fn allocator_arc(&self) -> Arc<dyn DeviceAllocator> {
Arc::clone(&self.resources.allocator)
}
fn quarantined(&self) -> Result<Vec<QuarantinedAllocation>, BindingError> {
let state = self.lock_state("listing quarantined ownership")?;
Ok(state.quarantined.values().copied().collect())
}
fn record_allocation(&self, record: AllocationRecord) -> Result<(), BindingError> {
let mut state = self.lock_state("recording allocation identity")?;
state.allocations.insert(record.identity.generation, record);
Ok(())
}
fn validate_allocation(
&self,
expected_binding: BindingIdentity,
allocation: &BoundAllocation,
operation: &'static str,
) -> Result<(), BindingError> {
validate_binding_identity(expected_binding, allocation.identity.binding)?;
let state = self.lock_state(operation)?;
if state.lifecycle != MechanismLifecycle::Active {
return Err(self.inactive_error(&state, operation));
}
let Some(record) = state.allocations.get(&allocation.identity.generation) else {
return Err(BindingError::StaleAllocation(allocation.identity));
};
if !allocation.matches_record(record) {
return Err(BindingError::StaleAllocation(allocation.identity));
}
Ok(())
}
fn validate_view(
&self,
expected_binding: BindingIdentity,
view: &BoundMemoryView,
operation: &'static str,
) -> Result<(), BindingError> {
validate_binding_identity(expected_binding, view.identity.binding)?;
let state = self.lock_state(operation)?;
if state.lifecycle != MechanismLifecycle::Active {
return Err(self.inactive_error(&state, operation));
}
let Some(record) = state.allocations.get(&view.identity.generation) else {
return Err(BindingError::StaleAllocation(view.identity));
};
if record.identity != view.identity
|| record.ptr != view.allocation_ptr.as_ptr() as usize
|| record.bytes != view.allocation_bytes
|| record.align != view.align
{
return Err(BindingError::StaleAllocation(view.identity));
}
Ok(())
}
fn snapshot(&self) -> Result<MechanismSnapshot, BindingError> {
let state = self.lock_state("taking a mechanism snapshot")?;
Ok(MechanismSnapshot {
identity: self.identity,
device: self.device,
provider_context: self.context_identity(),
authority: self.authority_identity(),
coherence: self.coherence,
lifecycle: state.lifecycle,
live_allocations: state.allocations.len(),
active_operations: self.active_operations.load(Ordering::Acquire),
queued_releases: state.queued_releases,
quarantined_allocations: state.quarantined.len(),
quarantined_bytes: state
.quarantined
.values()
.map(|record| record.retained_bytes)
.sum(),
})
}
}
#[derive(Debug)]
pub(crate) struct MechanismOperation {
mechanism: Arc<MechanismEntry>,
}
impl Drop for MechanismOperation {
fn drop(&mut self) {
self.mechanism
.active_operations
.fetch_sub(1, Ordering::AcqRel);
}
}
#[derive(Debug, Default)]
struct RegistryState {
contexts: HashMap<ProviderContextIdentity, Arc<ProviderContextEntry>>,
authorities: HashMap<AuthorityIdentity, Arc<AuthorityEntry>>,
mechanisms: HashMap<MechanismIdentity, Arc<MechanismEntry>>,
selected: HashMap<DeviceKey, MechanismIdentity>,
}
#[derive(Debug)]
struct RegistryInner {
identities: Arc<IdentitySource>,
state: Mutex<RegistryState>,
#[cfg(test)]
hooks: Mutex<Vec<RegistryHook>>,
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum HookSubject {
Mechanism(MechanismIdentity),
Device(DeviceKey),
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum HookPhase {
SelectAfterValidation,
SelectAfterPublish,
RetireBetweenPhases,
InvalidateBetweenPhases,
}
#[cfg(test)]
#[derive(Clone, Debug)]
struct RegistryHook {
subject: HookSubject,
phase: HookPhase,
entered: Arc<std::sync::Barrier>,
resume: Arc<std::sync::Barrier>,
}
#[derive(Clone, Debug)]
pub struct BindingRegistry {
inner: Arc<RegistryInner>,
}
impl BindingRegistry {
pub fn new() -> Result<Self, BindingError> {
Ok(Self {
inner: Arc::new(RegistryInner {
identities: Arc::new(IdentitySource::new()?),
state: Mutex::new(RegistryState::default()),
#[cfg(test)]
hooks: Mutex::new(Vec::new()),
}),
})
}
fn lock_state(
&self,
operation: &'static str,
) -> Result<MutexGuard<'_, RegistryState>, BindingError> {
self.inner
.state
.lock()
.map_err(|_| BindingError::LockPoisoned { operation })
}
pub fn register_provider_context(
&self,
device: DeviceKey,
resource: Arc<dyn BindingResource>,
) -> Result<RegisteredProviderContext, BindingError> {
let identity = ProviderContextIdentity(self.inner.identities.opaque()?);
let entry = Arc::new(ProviderContextEntry {
identity,
device,
_resource: resource,
});
self.lock_state("registering a provider context")?
.contexts
.insert(identity, entry);
Ok(RegisteredProviderContext { identity, device })
}
pub fn register_authority(
&self,
device: DeviceKey,
resource: Arc<dyn BindingResource>,
) -> Result<RegisteredAuthority, BindingError> {
let identity = AuthorityIdentity(self.inner.identities.opaque()?);
let entry = Arc::new(AuthorityEntry {
identity,
device,
_resource: resource,
});
self.lock_state("registering an authority")?
.authorities
.insert(identity, entry);
Ok(RegisteredAuthority { identity, device })
}
pub fn register_allocator(
&self,
context: RegisteredProviderContext,
authority: RegisteredAuthority,
allocator: Arc<dyn DeviceAllocator>,
) -> Result<RegisteredMechanism, BindingError> {
let allocator_device = allocator.device();
self.register_allocator_with_device(context, authority, allocator, allocator_device)
}
#[doc(hidden)]
pub fn register_allocator_with_device(
&self,
context: RegisteredProviderContext,
authority: RegisteredAuthority,
allocator: Arc<dyn DeviceAllocator>,
allocator_device: DeviceKey,
) -> Result<RegisteredMechanism, BindingError> {
self.register_mechanism(
context,
authority,
allocator,
allocator_device,
MechanismCoherence::SelfContained,
)
}
pub unsafe fn register_trusted_composite(
&self,
context: RegisteredProviderContext,
authority: RegisteredAuthority,
allocator: Arc<dyn DeviceAllocator>,
) -> Result<RegisteredMechanism, BindingError> {
let allocator_device = allocator.device();
self.register_mechanism(
context,
authority,
allocator,
allocator_device,
MechanismCoherence::TrustedComposite,
)
}
fn register_mechanism(
&self,
context: RegisteredProviderContext,
authority: RegisteredAuthority,
allocator: Arc<dyn DeviceAllocator>,
allocator_device: DeviceKey,
coherence: MechanismCoherence,
) -> Result<RegisteredMechanism, BindingError> {
self.ensure_local(context.identity.0, "provider context")?;
self.ensure_local(authority.identity.0, "authority")?;
let (context_entry, authority_entry) = {
let state = self.lock_state("looking up mechanism resources")?;
let context_entry = state
.contexts
.get(&context.identity)
.cloned()
.ok_or(BindingError::UnregisteredProviderContext(context.identity))?;
let authority_entry = state
.authorities
.get(&authority.identity)
.cloned()
.ok_or(BindingError::UnregisteredAuthority(authority.identity))?;
(context_entry, authority_entry)
};
if context_entry.device != authority_entry.device {
return Err(BindingError::DeviceMismatch {
subject: "authority",
expected: context_entry.device,
actual: authority_entry.device,
});
}
if context_entry.device != allocator_device {
return Err(BindingError::DeviceMismatch {
subject: "allocator",
expected: context_entry.device,
actual: allocator_device,
});
}
let identity = MechanismIdentity(self.inner.identities.opaque()?);
let mut state = self.lock_state("registering a mechanism")?;
let entry = Arc::new(MechanismEntry {
identity,
device: context_entry.device,
coherence,
state: Mutex::new(MechanismState {
lifecycle: MechanismLifecycle::Active,
loss_reason: None,
allocations: HashMap::new(),
queued_releases: 0,
quarantined: HashMap::new(),
}),
active_operations: AtomicUsize::new(0),
resources: MechanismResources {
allocator,
authority: authority_entry,
context: context_entry,
},
});
state.mechanisms.insert(identity, entry);
state.selected.entry(context.device).or_insert(identity);
Ok(RegisteredMechanism {
identity,
device: context.device,
coherence,
})
}
pub fn select(&self, mechanism: RegisteredMechanism) -> Result<(), BindingError> {
self.ensure_local(mechanism.identity.0, "mechanism")?;
let entry = {
let state = self.lock_state("selecting a mechanism")?;
state
.mechanisms
.get(&mechanism.identity)
.cloned()
.ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?
};
let snapshot = entry.snapshot()?;
if snapshot.lifecycle != MechanismLifecycle::Active {
return Err(BindingError::InactiveMechanism {
mechanism: mechanism.identity,
lifecycle: snapshot.lifecycle,
operation: "selecting a mechanism",
});
}
#[cfg(test)]
self.wait_at_hook(
HookSubject::Mechanism(mechanism.identity),
HookPhase::SelectAfterValidation,
);
let prior = self
.lock_state("publishing mechanism selection")?
.selected
.insert(mechanism.device, mechanism.identity);
#[cfg(test)]
self.wait_at_hook(
HookSubject::Mechanism(mechanism.identity),
HookPhase::SelectAfterPublish,
);
let published = entry.snapshot()?;
if published.lifecycle != MechanismLifecycle::Active {
self.withdraw_failed_selection(mechanism.device, mechanism.identity, prior)?;
return Err(BindingError::InactiveMechanism {
mechanism: mechanism.identity,
lifecycle: published.lifecycle,
operation: "selecting a mechanism",
});
}
Ok(())
}
fn withdraw_failed_selection(
&self,
device: DeviceKey,
candidate: MechanismIdentity,
prior: Option<MechanismIdentity>,
) -> Result<(), BindingError> {
const OPERATION: &str = "withdrawing inactive mechanism selection";
let mut owner = candidate;
let mut replacement = prior;
loop {
let restorable = match replacement {
Some(identity) => {
let entry = {
let state = self.lock_state(OPERATION)?;
if state.selected.get(&device) != Some(&owner) {
return Ok(());
}
state.mechanisms.get(&identity).cloned()
};
match entry {
Some(entry)
if entry.snapshot()?.lifecycle == MechanismLifecycle::Active =>
{
Some(entry)
}
_ => None,
}
}
None => None,
};
let restored = {
let mut state = self.lock_state(OPERATION)?;
if state.selected.get(&device) != Some(&owner) {
return Ok(());
}
match restorable {
Some(entry) if state.mechanisms.contains_key(&entry.identity) => {
state.selected.insert(device, entry.identity);
entry
}
_ => {
state.selected.remove(&device);
return Ok(());
}
}
};
if restored.snapshot()?.lifecycle == MechanismLifecycle::Active {
return Ok(());
}
owner = restored.identity;
replacement = None;
}
}
#[cfg(test)]
fn install_hook(&self, hook: RegistryHook) {
self.inner
.hooks
.lock()
.expect("registry test hook lock")
.push(hook);
}
#[cfg(test)]
fn wait_at_hook(&self, subject: HookSubject, phase: HookPhase) {
let hook = self
.inner
.hooks
.lock()
.expect("registry test hook lock")
.iter()
.find(|hook| hook.subject == subject && hook.phase == phase)
.cloned();
if let Some(hook) = hook {
hook.entered.wait();
hook.resume.wait();
}
}
pub fn bind(&self, device: DeviceKey) -> Result<MemoryBinding, BindingError> {
let entry = {
let state = self.lock_state("looking up the selected mechanism")?;
let identity = state
.selected
.get(&device)
.copied()
.ok_or(BindingError::NoSelectedMechanism(device))?;
state
.mechanisms
.get(&identity)
.cloned()
.ok_or(BindingError::UnregisteredMechanism(identity))?
};
self.issue_binding(entry)
}
pub fn bind_registered(
&self,
mechanism: RegisteredMechanism,
) -> Result<MemoryBinding, BindingError> {
self.ensure_local(mechanism.identity.0, "mechanism")?;
let entry = self
.lock_state("looking up a registered mechanism")?
.mechanisms
.get(&mechanism.identity)
.cloned()
.ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?;
self.issue_binding(entry)
}
fn issue_binding(&self, entry: Arc<MechanismEntry>) -> Result<MemoryBinding, BindingError> {
let operation = entry.begin_active("issuing a binding")?;
let identity = BindingIdentity {
id: BindingId(self.inner.identities.opaque()?),
generation: self.inner.identities.binding_generation()?,
device: entry.device,
mechanism: entry.identity,
provider_context: entry.context_identity(),
authority: entry.authority_identity(),
};
drop(operation);
Ok(MemoryBinding {
identity,
identities: Arc::clone(&self.inner.identities),
mechanism: entry,
})
}
pub fn retire(&self, mechanism: RegisteredMechanism) -> Result<(), BindingError> {
self.ensure_local(mechanism.identity.0, "mechanism")?;
let entry = {
let state = self.lock_state("retiring a mechanism")?;
state
.mechanisms
.get(&mechanism.identity)
.cloned()
.ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?
};
{
let mut mechanism_state = entry.lock_state("retiring a mechanism")?;
if mechanism_state.lifecycle == MechanismLifecycle::Active {
mechanism_state.lifecycle = MechanismLifecycle::Retired;
}
}
#[cfg(test)]
self.wait_at_hook(
HookSubject::Mechanism(mechanism.identity),
HookPhase::RetireBetweenPhases,
);
self.drop_selection_of(mechanism.device, mechanism.identity, "retiring a mechanism")
}
fn drop_selection_of(
&self,
device: DeviceKey,
mechanism: MechanismIdentity,
operation: &'static str,
) -> Result<(), BindingError> {
let mut state = self.lock_state(operation)?;
if state.selected.get(&device) == Some(&mechanism) {
state.selected.remove(&device);
}
Ok(())
}
pub fn invalidate_device(
&self,
device: DeviceKey,
reason: impl Into<Arc<str>>,
) -> Result<(), BindingError> {
let reason = reason.into();
let entries = {
let state = self.lock_state("invalidating a device")?;
state
.mechanisms
.values()
.filter(|entry| entry.device == device)
.cloned()
.collect::<Vec<_>>()
};
for entry in &entries {
let mut state = entry.lock_state("invalidating a device binding")?;
if state.lifecycle != MechanismLifecycle::Terminated {
state.lifecycle = MechanismLifecycle::DeviceLost;
state.loss_reason = Some(Arc::clone(&reason));
}
}
#[cfg(test)]
self.wait_at_hook(
HookSubject::Device(device),
HookPhase::InvalidateBetweenPhases,
);
let mut state = self.lock_state("invalidating a device")?;
let invalidated = state
.selected
.get(&device)
.is_some_and(|selected| entries.iter().any(|entry| entry.identity == *selected));
if invalidated {
state.selected.remove(&device);
}
Ok(())
}
pub fn confirm_context_terminated(
&self,
context: RegisteredProviderContext,
) -> Result<(), BindingError> {
self.ensure_local(context.identity.0, "provider context")?;
let entries = {
let state = self.lock_state("looking up a terminated provider context")?;
if !state.contexts.contains_key(&context.identity) {
return Err(BindingError::UnregisteredProviderContext(context.identity));
}
state
.mechanisms
.values()
.filter(|entry| entry.context_identity() == context.identity)
.cloned()
.collect::<Vec<_>>()
};
for entry in &entries {
let state = entry.lock_state("checking provider context quiescence")?;
if state.lifecycle != MechanismLifecycle::DeviceLost {
return Err(entry.inactive_error(
&state,
"confirming termination before device-loss invalidation",
));
}
let active_operations = entry.active_operations.load(Ordering::Acquire);
if active_operations != 0 {
return Err(BindingError::ContextNotQuiescent {
context: context.identity,
active_operations,
});
}
}
for entry in entries {
let mut state = entry.lock_state("confirming provider context termination")?;
state.lifecycle = MechanismLifecycle::Terminated;
state.allocations.clear();
state.quarantined.clear();
}
Ok(())
}
pub fn quarantined(
&self,
mechanism: RegisteredMechanism,
) -> Result<Vec<QuarantinedAllocation>, BindingError> {
self.ensure_local(mechanism.identity.0, "mechanism")?;
let entry = self
.lock_state("looking up quarantined ownership")?
.mechanisms
.get(&mechanism.identity)
.cloned()
.ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?;
entry.quarantined()
}
pub fn remove_provider_context(
&self,
context: RegisteredProviderContext,
) -> Result<(), BindingError> {
self.ensure_local(context.identity.0, "provider context")?;
let mut state = self.lock_state("removing a provider context")?;
if !state.contexts.contains_key(&context.identity) {
return Err(BindingError::UnregisteredProviderContext(context.identity));
}
if state
.mechanisms
.values()
.any(|entry| entry.context_identity() == context.identity)
{
return Err(BindingError::ProviderContextInUse(context.identity));
}
state.contexts.remove(&context.identity);
Ok(())
}
pub fn remove_authority(&self, authority: RegisteredAuthority) -> Result<(), BindingError> {
self.ensure_local(authority.identity.0, "authority")?;
let mut state = self.lock_state("removing an authority")?;
if !state.authorities.contains_key(&authority.identity) {
return Err(BindingError::UnregisteredAuthority(authority.identity));
}
if state
.mechanisms
.values()
.any(|entry| entry.authority_identity() == authority.identity)
{
return Err(BindingError::AuthorityInUse(authority.identity));
}
state.authorities.remove(&authority.identity);
Ok(())
}
pub fn remove(&self, mechanism: RegisteredMechanism) -> Result<(), BindingError> {
self.ensure_local(mechanism.identity.0, "mechanism")?;
let entry = {
let state = self.lock_state("checking mechanism teardown")?;
state
.mechanisms
.get(&mechanism.identity)
.cloned()
.ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?
};
let snapshot = entry.snapshot()?;
if snapshot.quarantined_allocations != 0 {
return Err(BindingError::QuarantinedOwnership {
mechanism: mechanism.identity,
quarantined: snapshot.quarantined_allocations,
});
}
if snapshot.lifecycle == MechanismLifecycle::Active
|| snapshot.live_allocations != 0
|| snapshot.queued_releases != 0
|| snapshot.active_operations != 0
{
return Err(BindingError::InactiveMechanism {
mechanism: mechanism.identity,
lifecycle: snapshot.lifecycle,
operation: "removing a mechanism before it is quiescent",
});
}
let mut state = self.lock_state("removing a mechanism")?;
if state.selected.get(&mechanism.device) == Some(&mechanism.identity) {
state.selected.remove(&mechanism.device);
}
state.mechanisms.remove(&mechanism.identity);
Ok(())
}
pub fn snapshot(
&self,
mechanism: RegisteredMechanism,
) -> Result<MechanismSnapshot, BindingError> {
self.ensure_local(mechanism.identity.0, "mechanism")?;
let entry = self
.lock_state("looking up a mechanism snapshot")?
.mechanisms
.get(&mechanism.identity)
.cloned()
.ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?;
entry.snapshot()
}
pub fn snapshots(&self) -> Result<Vec<MechanismSnapshot>, BindingError> {
let entries = self
.lock_state("listing mechanism snapshots")?
.mechanisms
.values()
.cloned()
.collect::<Vec<_>>();
entries.into_iter().map(|entry| entry.snapshot()).collect()
}
fn ensure_local(
&self,
identity: OpaqueIdentity,
kind: &'static str,
) -> Result<(), BindingError> {
if identity.registry != self.inner.identities.registry {
return Err(BindingError::ForeignRegistry { kind });
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RegisteredProviderContext {
identity: ProviderContextIdentity,
device: DeviceKey,
}
impl RegisteredProviderContext {
pub const fn identity(self) -> ProviderContextIdentity {
self.identity
}
pub const fn device(self) -> DeviceKey {
self.device
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RegisteredAuthority {
identity: AuthorityIdentity,
device: DeviceKey,
}
impl RegisteredAuthority {
pub const fn identity(self) -> AuthorityIdentity {
self.identity
}
pub const fn device(self) -> DeviceKey {
self.device
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RegisteredMechanism {
identity: MechanismIdentity,
device: DeviceKey,
coherence: MechanismCoherence,
}
impl RegisteredMechanism {
pub const fn identity(self) -> MechanismIdentity {
self.identity
}
pub const fn device(self) -> DeviceKey {
self.device
}
pub const fn coherence(self) -> MechanismCoherence {
self.coherence
}
}
#[derive(Clone, Debug)]
pub struct MemoryBinding {
identity: BindingIdentity,
identities: Arc<IdentitySource>,
mechanism: Arc<MechanismEntry>,
}
impl MemoryBinding {
pub const fn identity(&self) -> BindingIdentity {
self.identity
}
pub fn allocate(&self, bytes: usize, align: usize) -> Result<BoundAllocation, BindingError> {
self.allocate_with(
"allocating bound memory",
|allocator| allocator.allocate(bytes, align),
bytes,
align,
)
}
fn allocate_with(
&self,
operation: &'static str,
allocate: impl FnOnce(&dyn DeviceAllocator) -> Result<NonNull<u8>, MemoryError>,
bytes: usize,
align: usize,
) -> Result<BoundAllocation, BindingError> {
let active = self.mechanism.begin_active(operation)?;
let ptr = allocate(self.mechanism.allocator())?;
let generation = match self.identities.allocation_generation() {
Ok(generation) => generation,
Err(error) => {
unsafe { self.mechanism.allocator().deallocate(ptr, bytes, align) };
return Err(error);
}
};
let identity = AllocationIdentity {
binding: self.identity,
generation,
};
let allocation = BoundAllocation {
binding: self.clone(),
identity,
ptr,
bytes,
align,
};
if let Err(error) = self.mechanism.record_allocation(allocation.record()) {
unsafe { self.mechanism.allocator().deallocate(ptr, bytes, align) };
return Err(error);
}
drop(active);
Ok(allocation)
}
pub fn allocate_owning(
&self,
bytes: usize,
align: usize,
) -> Result<OwningAllocation, BindingError> {
self.allocate(bytes, align).map(OwningAllocation::new)
}
pub unsafe fn adopt_allocation(
&self,
ptr: NonNull<u8>,
bytes: usize,
align: usize,
) -> Result<OwningAllocation, BindingError> {
let active = self
.mechanism
.begin_active("adopting a device allocation")?;
let generation = self.identities.allocation_generation()?;
let identity = AllocationIdentity {
binding: self.identity,
generation,
};
let allocation = BoundAllocation {
binding: self.clone(),
identity,
ptr,
bytes,
align,
};
self.mechanism.record_allocation(allocation.record())?;
drop(active);
Ok(OwningAllocation::new(allocation))
}
pub fn prepare_release(
&self,
allocation: BoundAllocation,
) -> Result<PreparedAllocationRelease, ExplicitReleaseError> {
let operation = match self.mechanism.begin_release(self.identity, &allocation) {
Ok(operation) => operation,
Err(error) => return Err(ExplicitReleaseError::unchanged(error, allocation)),
};
Ok(PreparedAllocationRelease::new(
allocation.binding.clone(),
allocation.identity,
allocation.ptr,
allocation.bytes,
allocation.align,
PreparedReleasePins {
allocator: self.mechanism.allocator_arc(),
authority: self.identity.authority,
context: self.identity.provider_context,
operation,
},
))
}
pub fn release(&self, allocation: BoundAllocation) -> Result<(), ExplicitReleaseError> {
let prepared = self.prepare_release(allocation)?;
let stale = self.stale_metadata(&prepared);
match prepared.execute() {
AllocationReleaseOutcome::Complete { .. } => Ok(()),
outcome @ (AllocationReleaseOutcome::Quarantined { .. }
| AllocationReleaseOutcome::Failed { .. }) => {
let residual = outcome.residual();
Err(ExplicitReleaseError::quarantined(
BindingError::ReleaseQuarantined {
identity: stale.identity,
state: outcome.state(),
reason: residual.map_or(QuarantineReason::AllocatorRefused, |residual| {
residual.reason
}),
retained_bytes: residual.map_or(0, |residual| residual.retained_bytes),
},
stale,
outcome,
))
}
}
}
fn stale_metadata(&self, prepared: &PreparedAllocationRelease) -> BoundAllocation {
BoundAllocation {
binding: self.clone(),
identity: prepared.identity(),
ptr: prepared.as_ptr(),
bytes: prepared.len(),
align: prepared.alignment(),
}
}
pub fn quarantined(&self) -> Result<Vec<QuarantinedAllocation>, BindingError> {
self.mechanism.quarantined()
}
pub fn mechanism_snapshot(&self) -> Result<MechanismSnapshot, BindingError> {
self.mechanism.snapshot()
}
pub(crate) fn mechanism(&self) -> &Arc<MechanismEntry> {
&self.mechanism
}
pub fn virtual_backing(&self) -> Result<Option<BoundVirtualBacking>, BindingError> {
let operation = self
.mechanism
.begin_active("discovering virtual backing capability")?;
let present = self.mechanism.allocator().as_virtual_backing().is_some();
drop(operation);
Ok(present.then(|| BoundVirtualBacking {
binding: self.clone(),
}))
}
pub fn shared_mapping(&self) -> Result<Option<BoundSharedMapping>, BindingError> {
let operation = self
.mechanism
.begin_active("discovering shared mapping capability")?;
let present = self.mechanism.allocator().as_shared_mapping().is_some();
drop(operation);
Ok(present.then(|| BoundSharedMapping {
binding: self.clone(),
}))
}
pub fn with_view<R>(
&self,
view: &BoundMemoryView,
operation: impl FnOnce(ValidatedMemoryView) -> R,
) -> Result<R, BindingError> {
let active = self
.mechanism
.begin_active("validating a view for device use")?;
self.mechanism
.validate_view(self.identity, view, "validating a view for device use")?;
let validated = ValidatedMemoryView {
ptr: view.ptr,
bytes: view.bytes,
};
let result = operation(validated);
drop(active);
Ok(result)
}
}
#[derive(Debug)]
pub struct BoundAllocation {
binding: MemoryBinding,
identity: AllocationIdentity,
ptr: NonNull<u8>,
bytes: usize,
align: usize,
}
unsafe impl Send for BoundAllocation {}
unsafe impl Sync for BoundAllocation {}
impl BoundAllocation {
pub const fn identity(&self) -> AllocationIdentity {
self.identity
}
pub const fn binding(&self) -> &MemoryBinding {
&self.binding
}
pub const fn as_ptr(&self) -> NonNull<u8> {
self.ptr
}
pub const fn len(&self) -> usize {
self.bytes
}
pub const fn is_empty(&self) -> bool {
self.bytes == 0
}
pub const fn alignment(&self) -> usize {
self.align
}
pub fn view(&self, offset: usize, bytes: usize) -> Result<BoundMemoryView, BindingError> {
let end = offset
.checked_add(bytes)
.ok_or(BindingError::ViewOutOfBounds {
offset,
end: usize::MAX,
allocation_bytes: self.bytes,
})?;
if end > self.bytes {
return Err(BindingError::ViewOutOfBounds {
offset,
end,
allocation_bytes: self.bytes,
});
}
self.binding.mechanism.validate_allocation(
self.binding.identity,
self,
"creating a bound view",
)?;
Ok(BoundMemoryView {
binding: self.binding.clone(),
identity: self.identity,
allocation_ptr: self.ptr,
ptr: NonNull::new(self.ptr.as_ptr().wrapping_add(offset))
.expect("offset within a live allocation cannot produce null"),
allocation_bytes: self.bytes,
bytes,
align: self.align,
})
}
fn record(&self) -> AllocationRecord {
AllocationRecord {
identity: self.identity,
ptr: self.ptr.as_ptr() as usize,
bytes: self.bytes,
align: self.align,
}
}
fn matches_record(&self, record: &AllocationRecord) -> bool {
record.identity == self.identity
&& record.ptr == self.ptr.as_ptr() as usize
&& record.bytes == self.bytes
&& record.align == self.align
}
}
#[derive(Clone, Debug)]
pub struct BoundMemoryView {
binding: MemoryBinding,
identity: AllocationIdentity,
allocation_ptr: NonNull<u8>,
ptr: NonNull<u8>,
allocation_bytes: usize,
bytes: usize,
align: usize,
}
unsafe impl Send for BoundMemoryView {}
unsafe impl Sync for BoundMemoryView {}
impl BoundMemoryView {
pub const fn binding(&self) -> &MemoryBinding {
&self.binding
}
pub const fn allocation_identity(&self) -> AllocationIdentity {
self.identity
}
pub const fn len(&self) -> usize {
self.bytes
}
pub const fn is_empty(&self) -> bool {
self.bytes == 0
}
}
#[derive(Clone, Copy, Debug)]
pub struct ValidatedMemoryView {
ptr: NonNull<u8>,
bytes: usize,
}
unsafe impl Send for ValidatedMemoryView {}
unsafe impl Sync for ValidatedMemoryView {}
impl ValidatedMemoryView {
pub const fn as_ptr(self) -> NonNull<u8> {
self.ptr
}
pub const fn len(self) -> usize {
self.bytes
}
pub const fn is_empty(self) -> bool {
self.bytes == 0
}
}
#[derive(Debug)]
pub struct OwningAllocation {
allocation: Option<BoundAllocation>,
views: Arc<AtomicUsize>,
}
impl OwningAllocation {
pub fn new(allocation: BoundAllocation) -> Self {
Self {
allocation: Some(allocation),
views: Arc::new(AtomicUsize::new(0)),
}
}
fn allocation(&self) -> &BoundAllocation {
self.allocation
.as_ref()
.expect("an owning allocation holds its allocation until it is consumed")
}
pub fn identity(&self) -> AllocationIdentity {
self.allocation().identity
}
pub fn binding(&self) -> &MemoryBinding {
&self.allocation().binding
}
pub fn bound(&self) -> &BoundAllocation {
self.allocation()
}
pub fn as_ptr(&self) -> NonNull<u8> {
self.allocation().ptr
}
pub fn len(&self) -> usize {
self.allocation().bytes
}
pub fn is_empty(&self) -> bool {
self.allocation().bytes == 0
}
pub fn alignment(&self) -> usize {
self.allocation().align
}
pub const fn state(&self) -> AllocationReleaseState {
AllocationReleaseState::Live
}
pub fn view(&self, offset: usize, bytes: usize) -> Result<OwnedView, BindingError> {
let view = self.allocation().view(offset, bytes)?;
self.views.fetch_add(1, Ordering::AcqRel);
Ok(OwnedView {
view,
outstanding: Arc::clone(&self.views),
})
}
pub fn outstanding_views(&self) -> usize {
self.views.load(Ordering::Acquire)
}
pub fn into_bound(self) -> Result<BoundAllocation, OwningReleaseError> {
self.take("disowning an allocation with outstanding views")
}
pub fn prepare_release(self) -> Result<PreparedAllocationRelease, OwningReleaseError> {
let views = Arc::clone(&self.views);
let allocation = self.take("preparing release with outstanding views")?;
let binding = allocation.binding.clone();
binding.prepare_release(allocation).map_err(|error| {
let (error, allocation) = error.into_parts();
OwningReleaseError {
error,
allocation: Box::new(Self {
allocation: Some(allocation),
views,
}),
}
})
}
pub fn release_now(self) -> Result<AllocationReleaseOutcome, OwningReleaseError> {
Ok(self.prepare_release()?.execute())
}
pub fn release_deferred(
self,
queue: &dyn DeferredReleaseQueue,
) -> Result<DeferredReleaseDisposition, OwningReleaseError> {
let prepared = self.prepare_release()?;
let identity = prepared.identity();
match queue.enqueue(prepared) {
Ok(()) => Ok(DeferredReleaseDisposition::Queued { identity }),
Err(error) => {
let rejection = error.rejection();
Ok(DeferredReleaseDisposition::Quarantined {
identity,
rejection,
outcome: error.quarantine(),
})
}
}
}
fn take(mut self, operation: &'static str) -> Result<BoundAllocation, OwningReleaseError> {
let _ = operation;
let views = Arc::clone(&self.views);
let outstanding = views.load(Ordering::Acquire);
let allocation = self
.allocation
.take()
.expect("an owning allocation holds its allocation until it is consumed");
if outstanding != 0 {
return Err(OwningReleaseError {
error: BindingError::OutstandingViews {
identity: allocation.identity,
views: outstanding,
},
allocation: Box::new(Self {
allocation: Some(allocation),
views,
}),
});
}
Ok(allocation)
}
}
impl Drop for OwningAllocation {
fn drop(&mut self) {
let Some(allocation) = self.allocation.take() else {
return;
};
let binding = allocation.binding.clone();
if let Ok(prepared) = binding.prepare_release(allocation) {
let _ = prepared.quarantine(QuarantineReason::OwnerDropped);
}
}
}
#[derive(Debug)]
pub struct OwnedView {
view: BoundMemoryView,
outstanding: Arc<AtomicUsize>,
}
impl Clone for OwnedView {
fn clone(&self) -> Self {
self.outstanding.fetch_add(1, Ordering::AcqRel);
Self {
view: self.view.clone(),
outstanding: Arc::clone(&self.outstanding),
}
}
}
impl Drop for OwnedView {
fn drop(&mut self) {
self.outstanding.fetch_sub(1, Ordering::AcqRel);
}
}
impl OwnedView {
pub const fn view(&self) -> &BoundMemoryView {
&self.view
}
pub const fn binding(&self) -> &MemoryBinding {
self.view.binding()
}
pub const fn allocation_identity(&self) -> AllocationIdentity {
self.view.allocation_identity()
}
pub const fn len(&self) -> usize {
self.view.len()
}
pub const fn is_empty(&self) -> bool {
self.view.is_empty()
}
}
#[derive(Debug)]
pub struct OwningReleaseError {
error: BindingError,
allocation: Box<OwningAllocation>,
}
impl OwningReleaseError {
pub const fn error(&self) -> &BindingError {
&self.error
}
pub const fn allocation(&self) -> &OwningAllocation {
&self.allocation
}
pub const fn state(&self) -> AllocationReleaseState {
AllocationReleaseState::Live
}
pub fn into_parts(self) -> (BindingError, OwningAllocation) {
(self.error, *self.allocation)
}
}
impl std::fmt::Display for OwningReleaseError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.error, formatter)
}
}
impl std::error::Error for OwningReleaseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.error)
}
}
#[derive(Debug)]
pub struct ExplicitReleaseError {
error: BindingError,
allocation: Box<BoundAllocation>,
outcome: Option<Box<AllocationReleaseOutcome>>,
}
impl ExplicitReleaseError {
fn unchanged(error: BindingError, allocation: BoundAllocation) -> Self {
Self {
error,
allocation: Box::new(allocation),
outcome: None,
}
}
fn quarantined(
error: BindingError,
allocation: BoundAllocation,
outcome: AllocationReleaseOutcome,
) -> Self {
Self {
error,
allocation: Box::new(allocation),
outcome: Some(Box::new(outcome)),
}
}
pub const fn error(&self) -> &BindingError {
&self.error
}
pub fn outcome(&self) -> Option<&AllocationReleaseOutcome> {
self.outcome.as_deref()
}
pub const fn is_quarantined(&self) -> bool {
self.outcome.is_some()
}
pub fn state(&self) -> AllocationReleaseState {
self.outcome.as_deref().map_or(
AllocationReleaseState::Live,
AllocationReleaseOutcome::state,
)
}
pub fn into_parts(self) -> (BindingError, BoundAllocation) {
(self.error, *self.allocation)
}
}
impl std::fmt::Display for ExplicitReleaseError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.error, formatter)
}
}
impl std::error::Error for ExplicitReleaseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.error)
}
}
#[derive(Clone, Debug)]
pub struct BoundVirtualBacking {
binding: MemoryBinding,
}
impl BoundVirtualBacking {
pub const fn binding_identity(&self) -> BindingIdentity {
self.binding.identity
}
pub fn allocate_committed(
&self,
bytes: usize,
align: usize,
committed_ranges: &[std::ops::Range<usize>],
) -> Result<BoundAllocation, BindingError> {
self.binding.allocate_with(
"allocating through bound virtual backing",
|allocator| {
allocator
.as_virtual_backing()
.expect("capability presence is stable for a registered allocator")
.allocate_committed(bytes, align, committed_ranges)
},
bytes,
align,
)
}
pub fn commit_allocation_range(
&self,
allocation: &BoundAllocation,
offset: usize,
bytes: usize,
) -> Result<(), BindingError> {
let active = self
.binding
.mechanism
.begin_active("committing through bound virtual backing")?;
self.binding.mechanism.validate_allocation(
self.binding.identity,
allocation,
"validating allocation for virtual commit",
)?;
let capability = self
.binding
.mechanism
.allocator()
.as_virtual_backing()
.expect("capability presence is stable for a registered allocator");
capability.commit_allocation_range(
allocation.ptr,
allocation.bytes,
allocation.align,
offset,
bytes,
)?;
drop(active);
Ok(())
}
pub fn commit_allocation_ranges(
&self,
ranges: &[(&BoundAllocation, usize, usize)],
) -> Result<(), BindingError> {
let active = self
.binding
.mechanism
.begin_active("committing ranges through bound virtual backing")?;
let mut raw = Vec::with_capacity(ranges.len());
for &(allocation, offset, bytes) in ranges {
self.binding.mechanism.validate_allocation(
self.binding.identity,
allocation,
"validating allocation ranges for virtual commit",
)?;
raw.push(AllocationCommitRange {
ptr: allocation.ptr,
allocation_bytes: allocation.bytes,
align: allocation.align,
offset,
bytes,
});
}
self.binding
.mechanism
.allocator()
.as_virtual_backing()
.expect("capability presence is stable for a registered allocator")
.commit_allocation_ranges(&raw)?;
drop(active);
Ok(())
}
pub fn mapped_bytes_for_allocation(
&self,
bytes: usize,
align: usize,
) -> Result<u64, BindingError> {
let active = self
.binding
.mechanism
.begin_active("querying bound virtual backing")?;
let mapped = self
.binding
.mechanism
.allocator()
.as_virtual_backing()
.expect("capability presence is stable for a registered allocator")
.mapped_bytes_for_allocation(bytes, align)?;
drop(active);
Ok(mapped)
}
pub fn decommit_allocation_range(
&self,
allocation: &BoundAllocation,
offset: usize,
bytes: usize,
) -> Result<u64, BindingError> {
let active = self
.binding
.mechanism
.begin_active("decommitting through bound virtual backing")?;
self.binding.mechanism.validate_allocation(
self.binding.identity,
allocation,
"validating allocation for virtual decommit",
)?;
let unmapped = self
.binding
.mechanism
.allocator()
.as_virtual_backing()
.expect("capability presence is stable for a registered allocator")
.decommit_allocation_range(
allocation.ptr,
allocation.bytes,
allocation.align,
offset,
bytes,
)?;
drop(active);
Ok(unmapped)
}
pub fn allocation_committed_bytes(
&self,
allocation: &BoundAllocation,
) -> Result<usize, BindingError> {
let active = self
.binding
.mechanism
.begin_active("querying bound allocation commitment")?;
self.binding.mechanism.validate_allocation(
self.binding.identity,
allocation,
"validating allocation commitment query",
)?;
let committed = self
.binding
.mechanism
.allocator()
.as_virtual_backing()
.expect("capability presence is stable for a registered allocator")
.allocation_committed_bytes(allocation.ptr, allocation.bytes, allocation.align);
drop(active);
Ok(committed)
}
}
#[derive(Clone, Debug)]
pub struct BoundSharedMapping {
binding: MemoryBinding,
}
impl BoundSharedMapping {
pub const fn binding_identity(&self) -> BindingIdentity {
self.binding.identity
}
pub fn create_shared_prefix(&self, bytes: usize) -> Result<BoundSharedPrefix, BindingError> {
let active = self
.binding
.mechanism
.begin_active("creating a bound shared prefix")?;
let prefix = self
.binding
.mechanism
.allocator()
.as_shared_mapping()
.expect("capability presence is stable for a registered allocator")
.create_shared_prefix(bytes)?;
drop(active);
Ok(BoundSharedPrefix {
prefix,
binding: self.binding.clone(),
})
}
pub fn incremental_owned_bytes_for_shared_prefix(
&self,
prefix: &BoundSharedPrefix,
) -> Result<u64, BindingError> {
let active = self
.binding
.mechanism
.begin_active("querying a bound shared prefix")?;
validate_binding_identity(self.binding.identity, prefix.binding.identity)?;
let bytes = self
.binding
.mechanism
.allocator()
.as_shared_mapping()
.expect("capability presence is stable for a registered allocator")
.incremental_owned_bytes_for_shared_prefix(prefix.prefix.as_ref())?;
drop(active);
Ok(bytes)
}
pub fn commit_shared_prefix(
&self,
prefix: &BoundSharedPrefix,
allocation: &BoundAllocation,
byte_offset: usize,
) -> Result<SharedPrefixCommitInfo, BindingError> {
let active = self
.binding
.mechanism
.begin_active("committing a bound shared prefix")?;
validate_binding_identity(self.binding.identity, prefix.binding.identity)?;
self.binding.mechanism.validate_allocation(
self.binding.identity,
allocation,
"validating allocation for shared prefix commit",
)?;
let info = self
.binding
.mechanism
.allocator()
.as_shared_mapping()
.expect("capability presence is stable for a registered allocator")
.commit_shared_prefix(
prefix.prefix.as_ref(),
allocation.ptr,
allocation.bytes,
byte_offset,
)?;
drop(active);
Ok(info)
}
}
#[derive(Debug)]
pub struct BoundSharedPrefix {
prefix: Box<dyn SharedDevicePrefix>,
binding: MemoryBinding,
}
impl BoundSharedPrefix {
pub const fn binding_identity(&self) -> BindingIdentity {
self.binding.identity
}
pub fn device_ptr(&self) -> u64 {
self.prefix.device_ptr()
}
pub fn committed_physical_bytes(&self) -> u64 {
self.prefix.committed_physical_bytes()
}
pub fn mapped_bytes(&self) -> usize {
self.prefix.mapped_bytes()
}
pub fn requested_bytes(&self) -> usize {
self.prefix.requested_bytes()
}
}
fn validate_binding_identity(
expected: BindingIdentity,
actual: BindingIdentity,
) -> Result<(), BindingError> {
if expected != actual {
return Err(BindingError::BindingMismatch {
expected: expected.id,
actual: actual.id,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Barrier};
use std::thread;
use crate::{BindingResource, HostAllocator};
use super::*;
struct SelectionFixture {
registry: BindingRegistry,
context: RegisteredProviderContext,
authority: RegisteredAuthority,
}
impl SelectionFixture {
fn new() -> Self {
let registry = BindingRegistry::new().expect("registry");
let context = registry
.register_provider_context(
DeviceKey::HOST,
Arc::new(()) as Arc<dyn BindingResource>,
)
.expect("context registration");
let authority = registry
.register_authority(DeviceKey::HOST, Arc::new(()) as Arc<dyn BindingResource>)
.expect("authority registration");
Self {
registry,
context,
authority,
}
}
fn mechanism(&self) -> RegisteredMechanism {
self.registry
.register_allocator(
self.context,
self.authority,
Arc::new(HostAllocator) as Arc<dyn DeviceAllocator>,
)
.expect("mechanism registration")
}
fn gate(&self, mechanism: RegisteredMechanism, phase: HookPhase) -> Gate {
self.gate_subject(HookSubject::Mechanism(mechanism.identity), phase)
}
fn gate_device(&self, phase: HookPhase) -> Gate {
self.gate_subject(HookSubject::Device(DeviceKey::HOST), phase)
}
fn gate_subject(&self, subject: HookSubject, phase: HookPhase) -> Gate {
let entered = Arc::new(Barrier::new(2));
let resume = Arc::new(Barrier::new(2));
self.registry.install_hook(RegistryHook {
subject,
phase,
entered: Arc::clone(&entered),
resume: Arc::clone(&resume),
});
Gate { entered, resume }
}
fn select_on_thread(
&self,
mechanism: RegisteredMechanism,
) -> thread::JoinHandle<Result<(), BindingError>> {
let registry = self.registry.clone();
thread::spawn(move || registry.select(mechanism))
}
fn selected_mechanism(&self) -> Result<MechanismIdentity, BindingError> {
self.registry
.bind(DeviceKey::HOST)
.map(|binding| binding.identity().mechanism())
}
fn assert_nothing_selected(&self) {
let error = self
.selected_mechanism()
.expect_err("withdrawal must leave no selection to heal from");
assert!(
matches!(error, BindingError::NoSelectedMechanism(device) if device == DeviceKey::HOST),
"selection was left pointing at a dead or unregistered mechanism: {error:?}"
);
}
}
struct Gate {
entered: Arc<Barrier>,
resume: Arc<Barrier>,
}
impl Gate {
fn wait_entered(&self) {
self.entered.wait();
}
fn resume(&self) {
self.resume.wait();
}
}
fn assert_select_failed(
result: Result<(), BindingError>,
expected: RegisteredMechanism,
expected_lifecycle: MechanismLifecycle,
) {
let error = result.expect_err("select must fail once its candidate is inactive");
assert!(
matches!(
error,
BindingError::InactiveMechanism {
mechanism,
lifecycle,
operation: "selecting a mechanism",
} if mechanism == expected.identity && lifecycle == expected_lifecycle
),
"unexpected select error: {error:?}"
);
}
fn publish_a_doomed_candidate(
fixture: &SelectionFixture,
candidate: RegisteredMechanism,
) -> (thread::JoinHandle<Result<(), BindingError>>, Gate) {
let validated = fixture.gate(candidate, HookPhase::SelectAfterValidation);
let published = fixture.gate(candidate, HookPhase::SelectAfterPublish);
let selecting = fixture.select_on_thread(candidate);
validated.wait_entered();
fixture
.registry
.retire(candidate)
.expect("retire candidate");
validated.resume();
published.wait_entered();
(selecting, published)
}
#[test]
fn failed_select_restores_the_prior_healthy_selection() {
let fixture = SelectionFixture::new();
let prior = fixture.mechanism();
let candidate = fixture.mechanism();
let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
published.resume();
assert_select_failed(
selecting.join().expect("select thread"),
candidate,
MechanismLifecycle::Retired,
);
assert_eq!(
fixture
.selected_mechanism()
.expect("healthy prior restored"),
prior.identity
);
}
#[test]
fn failed_select_clears_a_retired_prior_instead_of_restoring_it() {
let fixture = SelectionFixture::new();
let prior = fixture.mechanism();
let candidate = fixture.mechanism();
let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
fixture.registry.retire(prior).expect("retire prior");
published.resume();
assert_select_failed(
selecting.join().expect("select thread"),
candidate,
MechanismLifecycle::Retired,
);
fixture.assert_nothing_selected();
}
#[test]
fn failed_select_clears_a_removed_prior_instead_of_restoring_it() {
let fixture = SelectionFixture::new();
let prior = fixture.mechanism();
let candidate = fixture.mechanism();
let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
fixture.registry.retire(prior).expect("retire prior");
fixture.registry.remove(prior).expect("remove prior");
published.resume();
assert_select_failed(
selecting.join().expect("select thread"),
candidate,
MechanismLifecycle::Retired,
);
fixture.assert_nothing_selected();
}
#[test]
fn failed_select_does_not_overwrite_a_newer_selection() {
let fixture = SelectionFixture::new();
let _prior = fixture.mechanism();
let candidate = fixture.mechanism();
let newer = fixture.mechanism();
let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
fixture.registry.select(newer).expect("newer selection");
published.resume();
assert_select_failed(
selecting.join().expect("select thread"),
candidate,
MechanismLifecycle::Retired,
);
assert_eq!(
fixture
.selected_mechanism()
.expect("newer selection stands"),
newer.identity
);
}
#[test]
fn two_failed_selects_never_leave_a_dead_mechanism_selected() {
let fixture = SelectionFixture::new();
let prior = fixture.mechanism();
let first = fixture.mechanism();
let second = fixture.mechanism();
let first_published = fixture.gate(first, HookPhase::SelectAfterPublish);
let second_validated = fixture.gate(second, HookPhase::SelectAfterValidation);
let second_published = fixture.gate(second, HookPhase::SelectAfterPublish);
let selecting_first = fixture.select_on_thread(first);
first_published.wait_entered();
let selecting_second = fixture.select_on_thread(second);
second_validated.wait_entered();
fixture.registry.retire(second).expect("retire second");
second_validated.resume();
second_published.wait_entered();
fixture.registry.retire(first).expect("retire first");
first_published.resume();
assert_select_failed(
selecting_first.join().expect("first select thread"),
first,
MechanismLifecycle::Retired,
);
second_published.resume();
assert_select_failed(
selecting_second.join().expect("second select thread"),
second,
MechanismLifecycle::Retired,
);
fixture.assert_nothing_selected();
fixture.registry.select(prior).expect("reselect prior");
assert_eq!(
fixture.selected_mechanism().expect("prior reselected"),
prior.identity
);
}
#[test]
fn a_later_registration_heals_a_cleared_selection() {
let fixture = SelectionFixture::new();
let prior = fixture.mechanism();
let candidate = fixture.mechanism();
let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
fixture.registry.retire(prior).expect("retire prior");
fixture.registry.remove(prior).expect("remove prior");
published.resume();
assert_select_failed(
selecting.join().expect("select thread"),
candidate,
MechanismLifecycle::Retired,
);
fixture.assert_nothing_selected();
let healed = fixture.mechanism();
assert_eq!(
fixture
.selected_mechanism()
.expect("registration self-heal"),
healed.identity
);
}
#[test]
fn retire_racing_a_select_cannot_leave_the_retired_mechanism_selected() {
let fixture = SelectionFixture::new();
let prior = fixture.mechanism();
let candidate = fixture.mechanism();
let validated = fixture.gate(candidate, HookPhase::SelectAfterValidation);
let retiring_gate = fixture.gate(candidate, HookPhase::RetireBetweenPhases);
let selecting = fixture.select_on_thread(candidate);
validated.wait_entered();
let registry = fixture.registry.clone();
let retiring = thread::spawn(move || registry.retire(candidate));
retiring_gate.wait_entered();
validated.resume();
assert_select_failed(
selecting.join().expect("select thread"),
candidate,
MechanismLifecycle::Retired,
);
retiring_gate.resume();
retiring
.join()
.expect("retire thread")
.expect("retire must succeed");
assert_eq!(
fixture
.selected_mechanism()
.expect("healthy prior restored"),
prior.identity
);
}
#[test]
fn device_loss_racing_a_select_cannot_leave_a_lost_mechanism_selected() {
let fixture = SelectionFixture::new();
let _prior = fixture.mechanism();
let candidate = fixture.mechanism();
let validated = fixture.gate(candidate, HookPhase::SelectAfterValidation);
let losing = fixture.gate_device(HookPhase::InvalidateBetweenPhases);
let selecting = fixture.select_on_thread(candidate);
validated.wait_entered();
let registry = fixture.registry.clone();
let invalidating =
thread::spawn(move || registry.invalidate_device(DeviceKey::HOST, "select race"));
losing.wait_entered();
validated.resume();
assert_select_failed(
selecting.join().expect("select thread"),
candidate,
MechanismLifecycle::DeviceLost,
);
losing.resume();
invalidating
.join()
.expect("invalidate thread")
.expect("invalidate must succeed");
fixture.assert_nothing_selected();
}
#[test]
fn retirement_and_device_loss_drop_the_current_selection() {
let fixture = SelectionFixture::new();
let retired = fixture.mechanism();
assert_eq!(
fixture
.selected_mechanism()
.expect("first registration selects"),
retired.identity
);
fixture.registry.retire(retired).expect("retire");
fixture.assert_nothing_selected();
let lost = fixture.mechanism();
assert_eq!(
fixture
.selected_mechanism()
.expect("registration self-heal"),
lost.identity
);
fixture
.registry
.invalidate_device(DeviceKey::HOST, "device lost")
.expect("invalidate");
fixture.assert_nothing_selected();
}
}