1use std::collections::HashMap;
19use std::fmt::Debug;
20use std::num::NonZeroU64;
21use std::ptr::NonNull;
22use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
23use std::sync::{Arc, Mutex, MutexGuard};
24
25use crate::deferred::{
26 AllocationReleaseOutcome, AllocationReleaseState, DeferredReleaseDisposition,
27 DeferredReleaseQueue, PreparedAllocationRelease, PreparedReleasePins, QuarantineReason,
28 QuarantinedAllocation,
29};
30use crate::{
31 AllocationCommitRange, DeviceAllocator, DeviceKey, MemoryError, SharedDevicePrefix,
32 SharedPrefixCommitInfo,
33};
34
35static NEXT_REGISTRY_ID: AtomicU64 = AtomicU64::new(1);
36
37#[derive(Clone, Copy, PartialEq, Eq, Hash)]
38struct OpaqueIdentity {
39 registry: NonZeroU64,
40 serial: NonZeroU64,
41}
42
43impl Debug for OpaqueIdentity {
44 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 formatter
46 .debug_tuple("opaque")
47 .field(&self.registry)
48 .field(&self.serial)
49 .finish()
50 }
51}
52
53macro_rules! opaque_identity {
54 ($name:ident) => {
55 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
56 pub struct $name(OpaqueIdentity);
57
58 impl Debug for $name {
59 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 formatter
61 .debug_tuple(stringify!($name))
62 .field(&self.0)
63 .finish()
64 }
65 }
66 };
67}
68
69opaque_identity!(AuthorityIdentity);
70opaque_identity!(ProviderContextIdentity);
71opaque_identity!(MechanismIdentity);
72opaque_identity!(BindingId);
73
74#[derive(Clone, Copy, PartialEq, Eq, Hash)]
76pub struct BindingGeneration(NonZeroU64);
77
78impl Debug for BindingGeneration {
79 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 formatter
81 .debug_tuple("BindingGeneration")
82 .field(&self.0)
83 .finish()
84 }
85}
86
87#[derive(Clone, Copy, PartialEq, Eq, Hash)]
92pub struct AllocationGeneration(NonZeroU64);
93
94impl Debug for AllocationGeneration {
95 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 formatter
97 .debug_tuple("AllocationGeneration")
98 .field(&self.0)
99 .finish()
100 }
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
105pub struct BindingIdentity {
106 id: BindingId,
107 generation: BindingGeneration,
108 device: DeviceKey,
109 mechanism: MechanismIdentity,
110 provider_context: ProviderContextIdentity,
111 authority: AuthorityIdentity,
112}
113
114impl BindingIdentity {
115 pub const fn id(self) -> BindingId {
116 self.id
117 }
118
119 pub const fn generation(self) -> BindingGeneration {
120 self.generation
121 }
122
123 pub const fn device(self) -> DeviceKey {
124 self.device
125 }
126
127 pub const fn mechanism(self) -> MechanismIdentity {
128 self.mechanism
129 }
130
131 pub const fn provider_context(self) -> ProviderContextIdentity {
132 self.provider_context
133 }
134
135 pub const fn authority(self) -> AuthorityIdentity {
136 self.authority
137 }
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
142pub struct AllocationIdentity {
143 binding: BindingIdentity,
144 generation: AllocationGeneration,
145}
146
147impl AllocationIdentity {
148 pub const fn binding(self) -> BindingIdentity {
149 self.binding
150 }
151
152 pub const fn generation(self) -> AllocationGeneration {
153 self.generation
154 }
155}
156
157pub trait BindingResource: Send + Sync + Debug {}
163
164impl<T> BindingResource for T where T: Send + Sync + Debug {}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
169pub enum MechanismCoherence {
170 SelfContained,
173 TrustedComposite,
176}
177
178#[derive(Clone, Copy, Debug, PartialEq, Eq)]
180pub enum MechanismLifecycle {
181 Active,
183 Retired,
186 DeviceLost,
189 Terminated,
192}
193
194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
196pub struct MechanismSnapshot {
197 pub identity: MechanismIdentity,
198 pub device: DeviceKey,
199 pub provider_context: ProviderContextIdentity,
200 pub authority: AuthorityIdentity,
201 pub coherence: MechanismCoherence,
202 pub lifecycle: MechanismLifecycle,
203 pub live_allocations: usize,
204 pub active_operations: usize,
205 pub queued_releases: usize,
208 pub quarantined_allocations: usize,
210 pub quarantined_bytes: u64,
212}
213
214impl MechanismSnapshot {
215 pub const fn retains_ownership(&self) -> bool {
218 self.live_allocations != 0 || self.queued_releases != 0 || self.quarantined_allocations != 0
219 }
220}
221
222#[derive(Debug, thiserror::Error)]
224pub enum BindingError {
225 #[error("binding identity space is exhausted")]
226 IdentityExhausted,
227 #[error("the {kind} belongs to another binding registry")]
228 ForeignRegistry { kind: &'static str },
229 #[error("cannot register {subject} for {actual:?}; its registered device is {expected:?}")]
230 DeviceMismatch {
231 subject: &'static str,
232 expected: DeviceKey,
233 actual: DeviceKey,
234 },
235 #[error("provider context {0:?} is not registered")]
236 UnregisteredProviderContext(ProviderContextIdentity),
237 #[error("provider context {0:?} still has a registered mechanism")]
238 ProviderContextInUse(ProviderContextIdentity),
239 #[error("authority {0:?} is not registered")]
240 UnregisteredAuthority(AuthorityIdentity),
241 #[error("authority {0:?} still has a registered mechanism")]
242 AuthorityInUse(AuthorityIdentity),
243 #[error("mechanism {0:?} is not registered")]
244 UnregisteredMechanism(MechanismIdentity),
245 #[error("device {0:?} has no selected memory mechanism")]
246 NoSelectedMechanism(DeviceKey),
247 #[error("mechanism {mechanism:?} is {lifecycle:?}; {operation} is not permitted")]
248 InactiveMechanism {
249 mechanism: MechanismIdentity,
250 lifecycle: MechanismLifecycle,
251 operation: &'static str,
252 },
253 #[error("device {device:?} was lost: {reason}")]
254 DeviceLost { device: DeviceKey, reason: Arc<str> },
255 #[error("binding mismatch: expected {expected:?}, but metadata belongs to {actual:?}")]
256 BindingMismatch {
257 expected: BindingId,
258 actual: BindingId,
259 },
260 #[error("allocation metadata {0:?} is stale or was already explicitly released")]
261 StaleAllocation(AllocationIdentity),
262 #[error(
263 "allocation {identity:?} still has {views} outstanding view(s); physical release is not \
264 permitted while a borrowed view or alias may still be used"
265 )]
266 OutstandingViews {
267 identity: AllocationIdentity,
268 views: usize,
269 },
270 #[error(
271 "release of allocation {identity:?} left {retained_bytes} byte(s) in the {state} state: \
272 {reason}"
273 )]
274 ReleaseQuarantined {
275 identity: AllocationIdentity,
276 state: AllocationReleaseState,
277 reason: QuarantineReason,
278 retained_bytes: u64,
279 },
280 #[error(
281 "mechanism {mechanism:?} still owns {quarantined} quarantined allocation(s); removal \
282 would lose ownership that was deliberately retained"
283 )]
284 QuarantinedOwnership {
285 mechanism: MechanismIdentity,
286 quarantined: usize,
287 },
288 #[error("view range {offset}..{end} exceeds allocation size {allocation_bytes}")]
289 ViewOutOfBounds {
290 offset: usize,
291 end: usize,
292 allocation_bytes: usize,
293 },
294 #[error("binding registry lock was poisoned while {operation}")]
295 LockPoisoned { operation: &'static str },
296 #[error(
297 "provider context {context:?} still has {active_operations} active mechanism operation(s)"
298 )]
299 ContextNotQuiescent {
300 context: ProviderContextIdentity,
301 active_operations: usize,
302 },
303 #[error(transparent)]
304 Memory(#[from] MemoryError),
305}
306
307#[derive(Debug)]
308struct IdentitySource {
309 registry: NonZeroU64,
310 next: AtomicU64,
311}
312
313impl IdentitySource {
314 fn new() -> Result<Self, BindingError> {
315 let registry = next_nonzero(&NEXT_REGISTRY_ID)?;
316 Ok(Self {
317 registry,
318 next: AtomicU64::new(1),
319 })
320 }
321
322 fn opaque(&self) -> Result<OpaqueIdentity, BindingError> {
323 Ok(OpaqueIdentity {
324 registry: self.registry,
325 serial: next_nonzero(&self.next)?,
326 })
327 }
328
329 fn binding_generation(&self) -> Result<BindingGeneration, BindingError> {
330 Ok(BindingGeneration(next_nonzero(&self.next)?))
331 }
332
333 fn allocation_generation(&self) -> Result<AllocationGeneration, BindingError> {
334 Ok(AllocationGeneration(next_nonzero(&self.next)?))
335 }
336}
337
338fn next_nonzero(counter: &AtomicU64) -> Result<NonZeroU64, BindingError> {
339 let value = counter
340 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
341 current.checked_add(1)
342 })
343 .map_err(|_| BindingError::IdentityExhausted)?;
344 NonZeroU64::new(value).ok_or(BindingError::IdentityExhausted)
345}
346
347#[derive(Debug)]
348struct ProviderContextEntry {
349 identity: ProviderContextIdentity,
350 device: DeviceKey,
351 _resource: Arc<dyn BindingResource>,
352}
353
354#[derive(Debug)]
355struct AuthorityEntry {
356 identity: AuthorityIdentity,
357 device: DeviceKey,
358 _resource: Arc<dyn BindingResource>,
359}
360
361#[derive(Clone, Copy, Debug)]
362struct AllocationRecord {
363 identity: AllocationIdentity,
364 ptr: usize,
365 bytes: usize,
366 align: usize,
367}
368
369#[derive(Debug)]
370struct MechanismState {
371 lifecycle: MechanismLifecycle,
372 loss_reason: Option<Arc<str>>,
373 allocations: HashMap<AllocationGeneration, AllocationRecord>,
374 queued_releases: usize,
376 quarantined: HashMap<AllocationGeneration, QuarantinedAllocation>,
379}
380
381#[derive(Debug)]
393struct MechanismResources {
394 allocator: Arc<dyn DeviceAllocator>,
396 authority: Arc<AuthorityEntry>,
398 context: Arc<ProviderContextEntry>,
400}
401
402#[derive(Debug)]
403pub(crate) struct MechanismEntry {
404 identity: MechanismIdentity,
405 device: DeviceKey,
406 coherence: MechanismCoherence,
407 state: Mutex<MechanismState>,
408 active_operations: AtomicUsize,
409 resources: MechanismResources,
412}
413
414#[derive(Clone, Copy, Debug, PartialEq, Eq)]
418pub(crate) enum ReleaseGate {
419 Allowed,
421 DeviceLost,
423 Terminated,
425 Poisoned,
427}
428
429impl MechanismEntry {
430 fn allocator(&self) -> &dyn DeviceAllocator {
431 self.resources.allocator.as_ref()
432 }
433
434 fn context_identity(&self) -> ProviderContextIdentity {
435 self.resources.context.identity
436 }
437
438 fn authority_identity(&self) -> AuthorityIdentity {
439 self.resources.authority.identity
440 }
441
442 fn lock_state(
443 &self,
444 operation: &'static str,
445 ) -> Result<MutexGuard<'_, MechanismState>, BindingError> {
446 self.state
447 .lock()
448 .map_err(|_| BindingError::LockPoisoned { operation })
449 }
450
451 fn inactive_error(&self, state: &MechanismState, operation: &'static str) -> BindingError {
452 match state.lifecycle {
453 MechanismLifecycle::DeviceLost => BindingError::DeviceLost {
454 device: self.device,
455 reason: state
456 .loss_reason
457 .clone()
458 .unwrap_or_else(|| Arc::from("provider did not supply a reason")),
459 },
460 lifecycle => BindingError::InactiveMechanism {
461 mechanism: self.identity,
462 lifecycle,
463 operation,
464 },
465 }
466 }
467
468 fn begin_active(
469 self: &Arc<Self>,
470 operation: &'static str,
471 ) -> Result<MechanismOperation, BindingError> {
472 let state = self.lock_state(operation)?;
473 if state.lifecycle != MechanismLifecycle::Active {
474 return Err(self.inactive_error(&state, operation));
475 }
476 self.active_operations.fetch_add(1, Ordering::AcqRel);
477 drop(state);
478 Ok(MechanismOperation {
479 mechanism: Arc::clone(self),
480 })
481 }
482
483 fn begin_release(
490 self: &Arc<Self>,
491 expected_binding: BindingIdentity,
492 allocation: &BoundAllocation,
493 ) -> Result<MechanismOperation, BindingError> {
494 let operation = "preparing explicit release";
495 let mut state = self.lock_state(operation)?;
496 match state.lifecycle {
497 MechanismLifecycle::Active | MechanismLifecycle::Retired => {}
498 _ => return Err(self.inactive_error(&state, operation)),
499 }
500 validate_binding_identity(expected_binding, allocation.identity.binding)?;
501 let Some(record) = state.allocations.get(&allocation.identity.generation) else {
502 return Err(BindingError::StaleAllocation(allocation.identity));
503 };
504 if !allocation.matches_record(record) {
505 return Err(BindingError::StaleAllocation(allocation.identity));
506 }
507 state.allocations.remove(&allocation.identity.generation);
508 state.queued_releases += 1;
509 self.active_operations.fetch_add(1, Ordering::AcqRel);
510 drop(state);
511 Ok(MechanismOperation {
512 mechanism: Arc::clone(self),
513 })
514 }
515
516 pub(crate) fn release_gate(&self) -> ReleaseGate {
518 let Ok(state) = self.state.lock() else {
519 return ReleaseGate::Poisoned;
520 };
521 match state.lifecycle {
522 MechanismLifecycle::Active | MechanismLifecycle::Retired => ReleaseGate::Allowed,
523 MechanismLifecycle::DeviceLost => ReleaseGate::DeviceLost,
524 MechanismLifecycle::Terminated => ReleaseGate::Terminated,
525 }
526 }
527
528 pub(crate) fn settle_release(&self, identity: AllocationIdentity) {
530 let Ok(mut state) = self.state.lock() else {
531 return;
532 };
533 state.queued_releases = state.queued_releases.saturating_sub(1);
534 debug_assert!(
535 !state.allocations.contains_key(&identity.generation),
536 "a settled release must not leave a live record behind"
537 );
538 }
539
540 pub(crate) fn settle_quarantine(&self, record: QuarantinedAllocation) {
542 let Ok(mut state) = self.state.lock() else {
543 return;
544 };
545 state.queued_releases = state.queued_releases.saturating_sub(1);
546 state.quarantined.insert(record.identity.generation, record);
547 }
548
549 pub(crate) fn allocator_arc(&self) -> Arc<dyn DeviceAllocator> {
550 Arc::clone(&self.resources.allocator)
551 }
552
553 fn quarantined(&self) -> Result<Vec<QuarantinedAllocation>, BindingError> {
554 let state = self.lock_state("listing quarantined ownership")?;
555 Ok(state.quarantined.values().copied().collect())
556 }
557
558 fn record_allocation(&self, record: AllocationRecord) -> Result<(), BindingError> {
559 let mut state = self.lock_state("recording allocation identity")?;
560 state.allocations.insert(record.identity.generation, record);
561 Ok(())
562 }
563
564 fn validate_allocation(
565 &self,
566 expected_binding: BindingIdentity,
567 allocation: &BoundAllocation,
568 operation: &'static str,
569 ) -> Result<(), BindingError> {
570 validate_binding_identity(expected_binding, allocation.identity.binding)?;
571 let state = self.lock_state(operation)?;
572 if state.lifecycle != MechanismLifecycle::Active {
573 return Err(self.inactive_error(&state, operation));
574 }
575 let Some(record) = state.allocations.get(&allocation.identity.generation) else {
576 return Err(BindingError::StaleAllocation(allocation.identity));
577 };
578 if !allocation.matches_record(record) {
579 return Err(BindingError::StaleAllocation(allocation.identity));
580 }
581 Ok(())
582 }
583
584 fn validate_view(
585 &self,
586 expected_binding: BindingIdentity,
587 view: &BoundMemoryView,
588 operation: &'static str,
589 ) -> Result<(), BindingError> {
590 validate_binding_identity(expected_binding, view.identity.binding)?;
591 let state = self.lock_state(operation)?;
592 if state.lifecycle != MechanismLifecycle::Active {
593 return Err(self.inactive_error(&state, operation));
594 }
595 let Some(record) = state.allocations.get(&view.identity.generation) else {
596 return Err(BindingError::StaleAllocation(view.identity));
597 };
598 if record.identity != view.identity
599 || record.ptr != view.allocation_ptr.as_ptr() as usize
600 || record.bytes != view.allocation_bytes
601 || record.align != view.align
602 {
603 return Err(BindingError::StaleAllocation(view.identity));
604 }
605 Ok(())
606 }
607
608 fn snapshot(&self) -> Result<MechanismSnapshot, BindingError> {
609 let state = self.lock_state("taking a mechanism snapshot")?;
610 Ok(MechanismSnapshot {
611 identity: self.identity,
612 device: self.device,
613 provider_context: self.context_identity(),
614 authority: self.authority_identity(),
615 coherence: self.coherence,
616 lifecycle: state.lifecycle,
617 live_allocations: state.allocations.len(),
618 active_operations: self.active_operations.load(Ordering::Acquire),
619 queued_releases: state.queued_releases,
620 quarantined_allocations: state.quarantined.len(),
621 quarantined_bytes: state
622 .quarantined
623 .values()
624 .map(|record| record.retained_bytes)
625 .sum(),
626 })
627 }
628}
629
630#[derive(Debug)]
631pub(crate) struct MechanismOperation {
632 mechanism: Arc<MechanismEntry>,
633}
634
635impl Drop for MechanismOperation {
636 fn drop(&mut self) {
637 self.mechanism
638 .active_operations
639 .fetch_sub(1, Ordering::AcqRel);
640 }
641}
642
643#[derive(Debug, Default)]
644struct RegistryState {
645 contexts: HashMap<ProviderContextIdentity, Arc<ProviderContextEntry>>,
646 authorities: HashMap<AuthorityIdentity, Arc<AuthorityEntry>>,
647 mechanisms: HashMap<MechanismIdentity, Arc<MechanismEntry>>,
648 selected: HashMap<DeviceKey, MechanismIdentity>,
649}
650
651#[derive(Debug)]
652struct RegistryInner {
653 identities: Arc<IdentitySource>,
654 state: Mutex<RegistryState>,
655 #[cfg(test)]
656 hooks: Mutex<Vec<RegistryHook>>,
657}
658
659#[cfg(test)]
661#[derive(Clone, Copy, Debug, PartialEq, Eq)]
662enum HookSubject {
663 Mechanism(MechanismIdentity),
664 Device(DeviceKey),
665}
666
667#[cfg(test)]
674#[derive(Clone, Copy, Debug, PartialEq, Eq)]
675enum HookPhase {
676 SelectAfterValidation,
679 SelectAfterPublish,
682 RetireBetweenPhases,
685 InvalidateBetweenPhases,
688}
689
690#[cfg(test)]
691#[derive(Clone, Debug)]
692struct RegistryHook {
693 subject: HookSubject,
694 phase: HookPhase,
695 entered: Arc<std::sync::Barrier>,
696 resume: Arc<std::sync::Barrier>,
697}
698
699#[derive(Clone, Debug)]
709pub struct BindingRegistry {
710 inner: Arc<RegistryInner>,
711}
712
713impl BindingRegistry {
714 pub fn new() -> Result<Self, BindingError> {
715 Ok(Self {
716 inner: Arc::new(RegistryInner {
717 identities: Arc::new(IdentitySource::new()?),
718 state: Mutex::new(RegistryState::default()),
719 #[cfg(test)]
720 hooks: Mutex::new(Vec::new()),
721 }),
722 })
723 }
724
725 fn lock_state(
726 &self,
727 operation: &'static str,
728 ) -> Result<MutexGuard<'_, RegistryState>, BindingError> {
729 self.inner
730 .state
731 .lock()
732 .map_err(|_| BindingError::LockPoisoned { operation })
733 }
734
735 pub fn register_provider_context(
736 &self,
737 device: DeviceKey,
738 resource: Arc<dyn BindingResource>,
739 ) -> Result<RegisteredProviderContext, BindingError> {
740 let identity = ProviderContextIdentity(self.inner.identities.opaque()?);
741 let entry = Arc::new(ProviderContextEntry {
742 identity,
743 device,
744 _resource: resource,
745 });
746 self.lock_state("registering a provider context")?
747 .contexts
748 .insert(identity, entry);
749 Ok(RegisteredProviderContext { identity, device })
750 }
751
752 pub fn register_authority(
753 &self,
754 device: DeviceKey,
755 resource: Arc<dyn BindingResource>,
756 ) -> Result<RegisteredAuthority, BindingError> {
757 let identity = AuthorityIdentity(self.inner.identities.opaque()?);
758 let entry = Arc::new(AuthorityEntry {
759 identity,
760 device,
761 _resource: resource,
762 });
763 self.lock_state("registering an authority")?
764 .authorities
765 .insert(identity, entry);
766 Ok(RegisteredAuthority { identity, device })
767 }
768
769 pub fn register_allocator(
770 &self,
771 context: RegisteredProviderContext,
772 authority: RegisteredAuthority,
773 allocator: Arc<dyn DeviceAllocator>,
774 ) -> Result<RegisteredMechanism, BindingError> {
775 let allocator_device = allocator.device();
776 self.register_allocator_with_device(context, authority, allocator, allocator_device)
777 }
778
779 #[doc(hidden)]
787 pub fn register_allocator_with_device(
788 &self,
789 context: RegisteredProviderContext,
790 authority: RegisteredAuthority,
791 allocator: Arc<dyn DeviceAllocator>,
792 allocator_device: DeviceKey,
793 ) -> Result<RegisteredMechanism, BindingError> {
794 self.register_mechanism(
795 context,
796 authority,
797 allocator,
798 allocator_device,
799 MechanismCoherence::SelfContained,
800 )
801 }
802
803 pub unsafe fn register_trusted_composite(
812 &self,
813 context: RegisteredProviderContext,
814 authority: RegisteredAuthority,
815 allocator: Arc<dyn DeviceAllocator>,
816 ) -> Result<RegisteredMechanism, BindingError> {
817 let allocator_device = allocator.device();
818 self.register_mechanism(
819 context,
820 authority,
821 allocator,
822 allocator_device,
823 MechanismCoherence::TrustedComposite,
824 )
825 }
826
827 fn register_mechanism(
828 &self,
829 context: RegisteredProviderContext,
830 authority: RegisteredAuthority,
831 allocator: Arc<dyn DeviceAllocator>,
832 allocator_device: DeviceKey,
833 coherence: MechanismCoherence,
834 ) -> Result<RegisteredMechanism, BindingError> {
835 self.ensure_local(context.identity.0, "provider context")?;
836 self.ensure_local(authority.identity.0, "authority")?;
837 let (context_entry, authority_entry) = {
838 let state = self.lock_state("looking up mechanism resources")?;
839 let context_entry = state
840 .contexts
841 .get(&context.identity)
842 .cloned()
843 .ok_or(BindingError::UnregisteredProviderContext(context.identity))?;
844 let authority_entry = state
845 .authorities
846 .get(&authority.identity)
847 .cloned()
848 .ok_or(BindingError::UnregisteredAuthority(authority.identity))?;
849 (context_entry, authority_entry)
850 };
851 if context_entry.device != authority_entry.device {
852 return Err(BindingError::DeviceMismatch {
853 subject: "authority",
854 expected: context_entry.device,
855 actual: authority_entry.device,
856 });
857 }
858 if context_entry.device != allocator_device {
859 return Err(BindingError::DeviceMismatch {
860 subject: "allocator",
861 expected: context_entry.device,
862 actual: allocator_device,
863 });
864 }
865
866 let identity = MechanismIdentity(self.inner.identities.opaque()?);
867 let mut state = self.lock_state("registering a mechanism")?;
868 let entry = Arc::new(MechanismEntry {
869 identity,
870 device: context_entry.device,
871 coherence,
872 state: Mutex::new(MechanismState {
873 lifecycle: MechanismLifecycle::Active,
874 loss_reason: None,
875 allocations: HashMap::new(),
876 queued_releases: 0,
877 quarantined: HashMap::new(),
878 }),
879 active_operations: AtomicUsize::new(0),
880 resources: MechanismResources {
881 allocator,
882 authority: authority_entry,
883 context: context_entry,
884 },
885 });
886 state.mechanisms.insert(identity, entry);
887 state.selected.entry(context.device).or_insert(identity);
888 Ok(RegisteredMechanism {
889 identity,
890 device: context.device,
891 coherence,
892 })
893 }
894
895 pub fn select(&self, mechanism: RegisteredMechanism) -> Result<(), BindingError> {
905 self.ensure_local(mechanism.identity.0, "mechanism")?;
906 let entry = {
907 let state = self.lock_state("selecting a mechanism")?;
908 state
909 .mechanisms
910 .get(&mechanism.identity)
911 .cloned()
912 .ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?
913 };
914 let snapshot = entry.snapshot()?;
915 if snapshot.lifecycle != MechanismLifecycle::Active {
916 return Err(BindingError::InactiveMechanism {
917 mechanism: mechanism.identity,
918 lifecycle: snapshot.lifecycle,
919 operation: "selecting a mechanism",
920 });
921 }
922 #[cfg(test)]
923 self.wait_at_hook(
924 HookSubject::Mechanism(mechanism.identity),
925 HookPhase::SelectAfterValidation,
926 );
927 let prior = self
928 .lock_state("publishing mechanism selection")?
929 .selected
930 .insert(mechanism.device, mechanism.identity);
931 #[cfg(test)]
932 self.wait_at_hook(
933 HookSubject::Mechanism(mechanism.identity),
934 HookPhase::SelectAfterPublish,
935 );
936 let published = entry.snapshot()?;
937 if published.lifecycle != MechanismLifecycle::Active {
938 self.withdraw_failed_selection(mechanism.device, mechanism.identity, prior)?;
939 return Err(BindingError::InactiveMechanism {
940 mechanism: mechanism.identity,
941 lifecycle: published.lifecycle,
942 operation: "selecting a mechanism",
943 });
944 }
945 Ok(())
946 }
947
948 fn withdraw_failed_selection(
970 &self,
971 device: DeviceKey,
972 candidate: MechanismIdentity,
973 prior: Option<MechanismIdentity>,
974 ) -> Result<(), BindingError> {
975 const OPERATION: &str = "withdrawing inactive mechanism selection";
976 let mut owner = candidate;
977 let mut replacement = prior;
978 loop {
979 let restorable = match replacement {
980 Some(identity) => {
981 let entry = {
982 let state = self.lock_state(OPERATION)?;
983 if state.selected.get(&device) != Some(&owner) {
984 return Ok(());
985 }
986 state.mechanisms.get(&identity).cloned()
987 };
988 match entry {
989 Some(entry)
990 if entry.snapshot()?.lifecycle == MechanismLifecycle::Active =>
991 {
992 Some(entry)
993 }
994 _ => None,
995 }
996 }
997 None => None,
998 };
999
1000 let restored = {
1001 let mut state = self.lock_state(OPERATION)?;
1002 if state.selected.get(&device) != Some(&owner) {
1003 return Ok(());
1004 }
1005 match restorable {
1009 Some(entry) if state.mechanisms.contains_key(&entry.identity) => {
1010 state.selected.insert(device, entry.identity);
1011 entry
1012 }
1013 _ => {
1014 state.selected.remove(&device);
1015 return Ok(());
1016 }
1017 }
1018 };
1019
1020 if restored.snapshot()?.lifecycle == MechanismLifecycle::Active {
1021 return Ok(());
1022 }
1023 owner = restored.identity;
1024 replacement = None;
1025 }
1026 }
1027
1028 #[cfg(test)]
1029 fn install_hook(&self, hook: RegistryHook) {
1030 self.inner
1031 .hooks
1032 .lock()
1033 .expect("registry test hook lock")
1034 .push(hook);
1035 }
1036
1037 #[cfg(test)]
1038 fn wait_at_hook(&self, subject: HookSubject, phase: HookPhase) {
1039 let hook = self
1040 .inner
1041 .hooks
1042 .lock()
1043 .expect("registry test hook lock")
1044 .iter()
1045 .find(|hook| hook.subject == subject && hook.phase == phase)
1046 .cloned();
1047 if let Some(hook) = hook {
1050 hook.entered.wait();
1051 hook.resume.wait();
1052 }
1053 }
1054
1055 pub fn bind(&self, device: DeviceKey) -> Result<MemoryBinding, BindingError> {
1056 let entry = {
1057 let state = self.lock_state("looking up the selected mechanism")?;
1058 let identity = state
1059 .selected
1060 .get(&device)
1061 .copied()
1062 .ok_or(BindingError::NoSelectedMechanism(device))?;
1063 state
1064 .mechanisms
1065 .get(&identity)
1066 .cloned()
1067 .ok_or(BindingError::UnregisteredMechanism(identity))?
1068 };
1069 self.issue_binding(entry)
1070 }
1071
1072 pub fn bind_registered(
1073 &self,
1074 mechanism: RegisteredMechanism,
1075 ) -> Result<MemoryBinding, BindingError> {
1076 self.ensure_local(mechanism.identity.0, "mechanism")?;
1077 let entry = self
1078 .lock_state("looking up a registered mechanism")?
1079 .mechanisms
1080 .get(&mechanism.identity)
1081 .cloned()
1082 .ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?;
1083 self.issue_binding(entry)
1084 }
1085
1086 fn issue_binding(&self, entry: Arc<MechanismEntry>) -> Result<MemoryBinding, BindingError> {
1087 let operation = entry.begin_active("issuing a binding")?;
1088 let identity = BindingIdentity {
1089 id: BindingId(self.inner.identities.opaque()?),
1090 generation: self.inner.identities.binding_generation()?,
1091 device: entry.device,
1092 mechanism: entry.identity,
1093 provider_context: entry.context_identity(),
1094 authority: entry.authority_identity(),
1095 };
1096 drop(operation);
1097 Ok(MemoryBinding {
1098 identity,
1099 identities: Arc::clone(&self.inner.identities),
1100 mechanism: entry,
1101 })
1102 }
1103
1104 pub fn retire(&self, mechanism: RegisteredMechanism) -> Result<(), BindingError> {
1116 self.ensure_local(mechanism.identity.0, "mechanism")?;
1117 let entry = {
1118 let state = self.lock_state("retiring a mechanism")?;
1119 state
1120 .mechanisms
1121 .get(&mechanism.identity)
1122 .cloned()
1123 .ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?
1124 };
1125 {
1126 let mut mechanism_state = entry.lock_state("retiring a mechanism")?;
1127 if mechanism_state.lifecycle == MechanismLifecycle::Active {
1128 mechanism_state.lifecycle = MechanismLifecycle::Retired;
1129 }
1130 }
1131 #[cfg(test)]
1132 self.wait_at_hook(
1133 HookSubject::Mechanism(mechanism.identity),
1134 HookPhase::RetireBetweenPhases,
1135 );
1136 self.drop_selection_of(mechanism.device, mechanism.identity, "retiring a mechanism")
1137 }
1138
1139 fn drop_selection_of(
1144 &self,
1145 device: DeviceKey,
1146 mechanism: MechanismIdentity,
1147 operation: &'static str,
1148 ) -> Result<(), BindingError> {
1149 let mut state = self.lock_state(operation)?;
1150 if state.selected.get(&device) == Some(&mechanism) {
1151 state.selected.remove(&device);
1152 }
1153 Ok(())
1154 }
1155
1156 pub fn invalidate_device(
1171 &self,
1172 device: DeviceKey,
1173 reason: impl Into<Arc<str>>,
1174 ) -> Result<(), BindingError> {
1175 let reason = reason.into();
1176 let entries = {
1177 let state = self.lock_state("invalidating a device")?;
1178 state
1179 .mechanisms
1180 .values()
1181 .filter(|entry| entry.device == device)
1182 .cloned()
1183 .collect::<Vec<_>>()
1184 };
1185 for entry in &entries {
1186 let mut state = entry.lock_state("invalidating a device binding")?;
1187 if state.lifecycle != MechanismLifecycle::Terminated {
1188 state.lifecycle = MechanismLifecycle::DeviceLost;
1189 state.loss_reason = Some(Arc::clone(&reason));
1190 }
1191 }
1192 #[cfg(test)]
1193 self.wait_at_hook(
1194 HookSubject::Device(device),
1195 HookPhase::InvalidateBetweenPhases,
1196 );
1197 let mut state = self.lock_state("invalidating a device")?;
1198 let invalidated = state
1199 .selected
1200 .get(&device)
1201 .is_some_and(|selected| entries.iter().any(|entry| entry.identity == *selected));
1202 if invalidated {
1203 state.selected.remove(&device);
1204 }
1205 Ok(())
1206 }
1207
1208 pub fn confirm_context_terminated(
1215 &self,
1216 context: RegisteredProviderContext,
1217 ) -> Result<(), BindingError> {
1218 self.ensure_local(context.identity.0, "provider context")?;
1219 let entries = {
1220 let state = self.lock_state("looking up a terminated provider context")?;
1221 if !state.contexts.contains_key(&context.identity) {
1222 return Err(BindingError::UnregisteredProviderContext(context.identity));
1223 }
1224 state
1225 .mechanisms
1226 .values()
1227 .filter(|entry| entry.context_identity() == context.identity)
1228 .cloned()
1229 .collect::<Vec<_>>()
1230 };
1231 for entry in &entries {
1232 let state = entry.lock_state("checking provider context quiescence")?;
1233 if state.lifecycle != MechanismLifecycle::DeviceLost {
1234 return Err(entry.inactive_error(
1235 &state,
1236 "confirming termination before device-loss invalidation",
1237 ));
1238 }
1239 let active_operations = entry.active_operations.load(Ordering::Acquire);
1240 if active_operations != 0 {
1241 return Err(BindingError::ContextNotQuiescent {
1242 context: context.identity,
1243 active_operations,
1244 });
1245 }
1246 }
1247 for entry in entries {
1248 let mut state = entry.lock_state("confirming provider context termination")?;
1249 state.lifecycle = MechanismLifecycle::Terminated;
1250 state.allocations.clear();
1251 state.quarantined.clear();
1255 }
1256 Ok(())
1257 }
1258
1259 pub fn quarantined(
1263 &self,
1264 mechanism: RegisteredMechanism,
1265 ) -> Result<Vec<QuarantinedAllocation>, BindingError> {
1266 self.ensure_local(mechanism.identity.0, "mechanism")?;
1267 let entry = self
1268 .lock_state("looking up quarantined ownership")?
1269 .mechanisms
1270 .get(&mechanism.identity)
1271 .cloned()
1272 .ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?;
1273 entry.quarantined()
1274 }
1275
1276 pub fn remove_provider_context(
1280 &self,
1281 context: RegisteredProviderContext,
1282 ) -> Result<(), BindingError> {
1283 self.ensure_local(context.identity.0, "provider context")?;
1284 let mut state = self.lock_state("removing a provider context")?;
1285 if !state.contexts.contains_key(&context.identity) {
1286 return Err(BindingError::UnregisteredProviderContext(context.identity));
1287 }
1288 if state
1289 .mechanisms
1290 .values()
1291 .any(|entry| entry.context_identity() == context.identity)
1292 {
1293 return Err(BindingError::ProviderContextInUse(context.identity));
1294 }
1295 state.contexts.remove(&context.identity);
1296 Ok(())
1297 }
1298
1299 pub fn remove_authority(&self, authority: RegisteredAuthority) -> Result<(), BindingError> {
1303 self.ensure_local(authority.identity.0, "authority")?;
1304 let mut state = self.lock_state("removing an authority")?;
1305 if !state.authorities.contains_key(&authority.identity) {
1306 return Err(BindingError::UnregisteredAuthority(authority.identity));
1307 }
1308 if state
1309 .mechanisms
1310 .values()
1311 .any(|entry| entry.authority_identity() == authority.identity)
1312 {
1313 return Err(BindingError::AuthorityInUse(authority.identity));
1314 }
1315 state.authorities.remove(&authority.identity);
1316 Ok(())
1317 }
1318
1319 pub fn remove(&self, mechanism: RegisteredMechanism) -> Result<(), BindingError> {
1329 self.ensure_local(mechanism.identity.0, "mechanism")?;
1330 let entry = {
1331 let state = self.lock_state("checking mechanism teardown")?;
1332 state
1333 .mechanisms
1334 .get(&mechanism.identity)
1335 .cloned()
1336 .ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?
1337 };
1338 let snapshot = entry.snapshot()?;
1339 if snapshot.quarantined_allocations != 0 {
1340 return Err(BindingError::QuarantinedOwnership {
1341 mechanism: mechanism.identity,
1342 quarantined: snapshot.quarantined_allocations,
1343 });
1344 }
1345 if snapshot.lifecycle == MechanismLifecycle::Active
1346 || snapshot.live_allocations != 0
1347 || snapshot.queued_releases != 0
1348 || snapshot.active_operations != 0
1349 {
1350 return Err(BindingError::InactiveMechanism {
1351 mechanism: mechanism.identity,
1352 lifecycle: snapshot.lifecycle,
1353 operation: "removing a mechanism before it is quiescent",
1354 });
1355 }
1356 let mut state = self.lock_state("removing a mechanism")?;
1357 if state.selected.get(&mechanism.device) == Some(&mechanism.identity) {
1358 state.selected.remove(&mechanism.device);
1359 }
1360 state.mechanisms.remove(&mechanism.identity);
1361 Ok(())
1362 }
1363
1364 pub fn snapshot(
1365 &self,
1366 mechanism: RegisteredMechanism,
1367 ) -> Result<MechanismSnapshot, BindingError> {
1368 self.ensure_local(mechanism.identity.0, "mechanism")?;
1369 let entry = self
1370 .lock_state("looking up a mechanism snapshot")?
1371 .mechanisms
1372 .get(&mechanism.identity)
1373 .cloned()
1374 .ok_or(BindingError::UnregisteredMechanism(mechanism.identity))?;
1375 entry.snapshot()
1376 }
1377
1378 pub fn snapshots(&self) -> Result<Vec<MechanismSnapshot>, BindingError> {
1384 let entries = self
1385 .lock_state("listing mechanism snapshots")?
1386 .mechanisms
1387 .values()
1388 .cloned()
1389 .collect::<Vec<_>>();
1390 entries.into_iter().map(|entry| entry.snapshot()).collect()
1391 }
1392
1393 fn ensure_local(
1394 &self,
1395 identity: OpaqueIdentity,
1396 kind: &'static str,
1397 ) -> Result<(), BindingError> {
1398 if identity.registry != self.inner.identities.registry {
1399 return Err(BindingError::ForeignRegistry { kind });
1400 }
1401 Ok(())
1402 }
1403}
1404
1405#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1407pub struct RegisteredProviderContext {
1408 identity: ProviderContextIdentity,
1409 device: DeviceKey,
1410}
1411
1412impl RegisteredProviderContext {
1413 pub const fn identity(self) -> ProviderContextIdentity {
1414 self.identity
1415 }
1416
1417 pub const fn device(self) -> DeviceKey {
1418 self.device
1419 }
1420}
1421
1422#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1424pub struct RegisteredAuthority {
1425 identity: AuthorityIdentity,
1426 device: DeviceKey,
1427}
1428
1429impl RegisteredAuthority {
1430 pub const fn identity(self) -> AuthorityIdentity {
1431 self.identity
1432 }
1433
1434 pub const fn device(self) -> DeviceKey {
1435 self.device
1436 }
1437}
1438
1439#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1441pub struct RegisteredMechanism {
1442 identity: MechanismIdentity,
1443 device: DeviceKey,
1444 coherence: MechanismCoherence,
1445}
1446
1447impl RegisteredMechanism {
1448 pub const fn identity(self) -> MechanismIdentity {
1449 self.identity
1450 }
1451
1452 pub const fn device(self) -> DeviceKey {
1453 self.device
1454 }
1455
1456 pub const fn coherence(self) -> MechanismCoherence {
1457 self.coherence
1458 }
1459}
1460
1461#[derive(Clone, Debug)]
1466pub struct MemoryBinding {
1467 identity: BindingIdentity,
1468 identities: Arc<IdentitySource>,
1469 mechanism: Arc<MechanismEntry>,
1470}
1471
1472impl MemoryBinding {
1473 pub const fn identity(&self) -> BindingIdentity {
1474 self.identity
1475 }
1476
1477 pub fn allocate(&self, bytes: usize, align: usize) -> Result<BoundAllocation, BindingError> {
1478 self.allocate_with(
1479 "allocating bound memory",
1480 |allocator| allocator.allocate(bytes, align),
1481 bytes,
1482 align,
1483 )
1484 }
1485
1486 fn allocate_with(
1487 &self,
1488 operation: &'static str,
1489 allocate: impl FnOnce(&dyn DeviceAllocator) -> Result<NonNull<u8>, MemoryError>,
1490 bytes: usize,
1491 align: usize,
1492 ) -> Result<BoundAllocation, BindingError> {
1493 let active = self.mechanism.begin_active(operation)?;
1494 let ptr = allocate(self.mechanism.allocator())?;
1495 let generation = match self.identities.allocation_generation() {
1496 Ok(generation) => generation,
1497 Err(error) => {
1498 unsafe { self.mechanism.allocator().deallocate(ptr, bytes, align) };
1501 return Err(error);
1502 }
1503 };
1504 let identity = AllocationIdentity {
1505 binding: self.identity,
1506 generation,
1507 };
1508 let allocation = BoundAllocation {
1509 binding: self.clone(),
1510 identity,
1511 ptr,
1512 bytes,
1513 align,
1514 };
1515 if let Err(error) = self.mechanism.record_allocation(allocation.record()) {
1516 unsafe { self.mechanism.allocator().deallocate(ptr, bytes, align) };
1518 return Err(error);
1519 }
1520 drop(active);
1521 Ok(allocation)
1522 }
1523
1524 pub fn allocate_owning(
1530 &self,
1531 bytes: usize,
1532 align: usize,
1533 ) -> Result<OwningAllocation, BindingError> {
1534 self.allocate(bytes, align).map(OwningAllocation::new)
1535 }
1536
1537 pub unsafe fn adopt_allocation(
1564 &self,
1565 ptr: NonNull<u8>,
1566 bytes: usize,
1567 align: usize,
1568 ) -> Result<OwningAllocation, BindingError> {
1569 let active = self
1570 .mechanism
1571 .begin_active("adopting a device allocation")?;
1572 let generation = self.identities.allocation_generation()?;
1573 let identity = AllocationIdentity {
1574 binding: self.identity,
1575 generation,
1576 };
1577 let allocation = BoundAllocation {
1578 binding: self.clone(),
1579 identity,
1580 ptr,
1581 bytes,
1582 align,
1583 };
1584 self.mechanism.record_allocation(allocation.record())?;
1585 drop(active);
1586 Ok(OwningAllocation::new(allocation))
1587 }
1588
1589 pub fn prepare_release(
1602 &self,
1603 allocation: BoundAllocation,
1604 ) -> Result<PreparedAllocationRelease, ExplicitReleaseError> {
1605 let operation = match self.mechanism.begin_release(self.identity, &allocation) {
1606 Ok(operation) => operation,
1607 Err(error) => return Err(ExplicitReleaseError::unchanged(error, allocation)),
1608 };
1609 Ok(PreparedAllocationRelease::new(
1610 allocation.binding.clone(),
1611 allocation.identity,
1612 allocation.ptr,
1613 allocation.bytes,
1614 allocation.align,
1615 PreparedReleasePins {
1616 allocator: self.mechanism.allocator_arc(),
1617 authority: self.identity.authority,
1618 context: self.identity.provider_context,
1619 operation,
1620 },
1621 ))
1622 }
1623
1624 pub fn release(&self, allocation: BoundAllocation) -> Result<(), ExplicitReleaseError> {
1650 let prepared = self.prepare_release(allocation)?;
1651 let stale = self.stale_metadata(&prepared);
1652 match prepared.execute() {
1653 AllocationReleaseOutcome::Complete { .. } => Ok(()),
1654 outcome @ (AllocationReleaseOutcome::Quarantined { .. }
1655 | AllocationReleaseOutcome::Failed { .. }) => {
1656 let residual = outcome.residual();
1657 Err(ExplicitReleaseError::quarantined(
1658 BindingError::ReleaseQuarantined {
1659 identity: stale.identity,
1660 state: outcome.state(),
1661 reason: residual.map_or(QuarantineReason::AllocatorRefused, |residual| {
1662 residual.reason
1663 }),
1664 retained_bytes: residual.map_or(0, |residual| residual.retained_bytes),
1665 },
1666 stale,
1667 outcome,
1668 ))
1669 }
1670 }
1671 }
1672
1673 fn stale_metadata(&self, prepared: &PreparedAllocationRelease) -> BoundAllocation {
1680 BoundAllocation {
1681 binding: self.clone(),
1682 identity: prepared.identity(),
1683 ptr: prepared.as_ptr(),
1684 bytes: prepared.len(),
1685 align: prepared.alignment(),
1686 }
1687 }
1688
1689 pub fn quarantined(&self) -> Result<Vec<QuarantinedAllocation>, BindingError> {
1691 self.mechanism.quarantined()
1692 }
1693
1694 pub fn mechanism_snapshot(&self) -> Result<MechanismSnapshot, BindingError> {
1696 self.mechanism.snapshot()
1697 }
1698
1699 pub(crate) fn mechanism(&self) -> &Arc<MechanismEntry> {
1700 &self.mechanism
1701 }
1702
1703 pub fn virtual_backing(&self) -> Result<Option<BoundVirtualBacking>, BindingError> {
1704 let operation = self
1705 .mechanism
1706 .begin_active("discovering virtual backing capability")?;
1707 let present = self.mechanism.allocator().as_virtual_backing().is_some();
1708 drop(operation);
1709 Ok(present.then(|| BoundVirtualBacking {
1710 binding: self.clone(),
1711 }))
1712 }
1713
1714 pub fn shared_mapping(&self) -> Result<Option<BoundSharedMapping>, BindingError> {
1715 let operation = self
1716 .mechanism
1717 .begin_active("discovering shared mapping capability")?;
1718 let present = self.mechanism.allocator().as_shared_mapping().is_some();
1719 drop(operation);
1720 Ok(present.then(|| BoundSharedMapping {
1721 binding: self.clone(),
1722 }))
1723 }
1724
1725 pub fn with_view<R>(
1728 &self,
1729 view: &BoundMemoryView,
1730 operation: impl FnOnce(ValidatedMemoryView) -> R,
1731 ) -> Result<R, BindingError> {
1732 let active = self
1733 .mechanism
1734 .begin_active("validating a view for device use")?;
1735 self.mechanism
1736 .validate_view(self.identity, view, "validating a view for device use")?;
1737 let validated = ValidatedMemoryView {
1738 ptr: view.ptr,
1739 bytes: view.bytes,
1740 };
1741 let result = operation(validated);
1742 drop(active);
1743 Ok(result)
1744 }
1745}
1746
1747#[derive(Debug)]
1752pub struct BoundAllocation {
1753 binding: MemoryBinding,
1754 identity: AllocationIdentity,
1755 ptr: NonNull<u8>,
1756 bytes: usize,
1757 align: usize,
1758}
1759
1760unsafe impl Send for BoundAllocation {}
1764unsafe impl Sync for BoundAllocation {}
1767
1768impl BoundAllocation {
1769 pub const fn identity(&self) -> AllocationIdentity {
1770 self.identity
1771 }
1772
1773 pub const fn binding(&self) -> &MemoryBinding {
1774 &self.binding
1775 }
1776
1777 pub const fn as_ptr(&self) -> NonNull<u8> {
1778 self.ptr
1779 }
1780
1781 pub const fn len(&self) -> usize {
1782 self.bytes
1783 }
1784
1785 pub const fn is_empty(&self) -> bool {
1786 self.bytes == 0
1787 }
1788
1789 pub const fn alignment(&self) -> usize {
1790 self.align
1791 }
1792
1793 pub fn view(&self, offset: usize, bytes: usize) -> Result<BoundMemoryView, BindingError> {
1794 let end = offset
1795 .checked_add(bytes)
1796 .ok_or(BindingError::ViewOutOfBounds {
1797 offset,
1798 end: usize::MAX,
1799 allocation_bytes: self.bytes,
1800 })?;
1801 if end > self.bytes {
1802 return Err(BindingError::ViewOutOfBounds {
1803 offset,
1804 end,
1805 allocation_bytes: self.bytes,
1806 });
1807 }
1808 self.binding.mechanism.validate_allocation(
1809 self.binding.identity,
1810 self,
1811 "creating a bound view",
1812 )?;
1813 Ok(BoundMemoryView {
1814 binding: self.binding.clone(),
1815 identity: self.identity,
1816 allocation_ptr: self.ptr,
1817 ptr: NonNull::new(self.ptr.as_ptr().wrapping_add(offset))
1818 .expect("offset within a live allocation cannot produce null"),
1819 allocation_bytes: self.bytes,
1820 bytes,
1821 align: self.align,
1822 })
1823 }
1824
1825 fn record(&self) -> AllocationRecord {
1826 AllocationRecord {
1827 identity: self.identity,
1828 ptr: self.ptr.as_ptr() as usize,
1829 bytes: self.bytes,
1830 align: self.align,
1831 }
1832 }
1833
1834 fn matches_record(&self, record: &AllocationRecord) -> bool {
1835 record.identity == self.identity
1836 && record.ptr == self.ptr.as_ptr() as usize
1837 && record.bytes == self.bytes
1838 && record.align == self.align
1839 }
1840}
1841
1842#[derive(Clone, Debug)]
1844pub struct BoundMemoryView {
1845 binding: MemoryBinding,
1846 identity: AllocationIdentity,
1847 allocation_ptr: NonNull<u8>,
1848 ptr: NonNull<u8>,
1849 allocation_bytes: usize,
1850 bytes: usize,
1851 align: usize,
1852}
1853
1854unsafe impl Send for BoundMemoryView {}
1857unsafe impl Sync for BoundMemoryView {}
1859
1860impl BoundMemoryView {
1861 pub const fn binding(&self) -> &MemoryBinding {
1862 &self.binding
1863 }
1864
1865 pub const fn allocation_identity(&self) -> AllocationIdentity {
1866 self.identity
1867 }
1868
1869 pub const fn len(&self) -> usize {
1870 self.bytes
1871 }
1872
1873 pub const fn is_empty(&self) -> bool {
1874 self.bytes == 0
1875 }
1876}
1877
1878#[derive(Clone, Copy, Debug)]
1880pub struct ValidatedMemoryView {
1881 ptr: NonNull<u8>,
1882 bytes: usize,
1883}
1884
1885unsafe impl Send for ValidatedMemoryView {}
1889unsafe impl Sync for ValidatedMemoryView {}
1891
1892impl ValidatedMemoryView {
1893 pub const fn as_ptr(self) -> NonNull<u8> {
1894 self.ptr
1895 }
1896
1897 pub const fn len(self) -> usize {
1898 self.bytes
1899 }
1900
1901 pub const fn is_empty(self) -> bool {
1902 self.bytes == 0
1903 }
1904}
1905
1906#[derive(Debug)]
1937pub struct OwningAllocation {
1938 allocation: Option<BoundAllocation>,
1942 views: Arc<AtomicUsize>,
1943}
1944
1945impl OwningAllocation {
1946 pub fn new(allocation: BoundAllocation) -> Self {
1951 Self {
1952 allocation: Some(allocation),
1953 views: Arc::new(AtomicUsize::new(0)),
1954 }
1955 }
1956
1957 fn allocation(&self) -> &BoundAllocation {
1958 self.allocation
1959 .as_ref()
1960 .expect("an owning allocation holds its allocation until it is consumed")
1961 }
1962
1963 pub fn identity(&self) -> AllocationIdentity {
1964 self.allocation().identity
1965 }
1966
1967 pub fn binding(&self) -> &MemoryBinding {
1968 &self.allocation().binding
1969 }
1970
1971 pub fn bound(&self) -> &BoundAllocation {
1979 self.allocation()
1980 }
1981
1982 pub fn as_ptr(&self) -> NonNull<u8> {
1984 self.allocation().ptr
1985 }
1986
1987 pub fn len(&self) -> usize {
1988 self.allocation().bytes
1989 }
1990
1991 pub fn is_empty(&self) -> bool {
1992 self.allocation().bytes == 0
1993 }
1994
1995 pub fn alignment(&self) -> usize {
1996 self.allocation().align
1997 }
1998
1999 pub const fn state(&self) -> AllocationReleaseState {
2002 AllocationReleaseState::Live
2003 }
2004
2005 pub fn view(&self, offset: usize, bytes: usize) -> Result<OwnedView, BindingError> {
2008 let view = self.allocation().view(offset, bytes)?;
2009 self.views.fetch_add(1, Ordering::AcqRel);
2010 Ok(OwnedView {
2011 view,
2012 outstanding: Arc::clone(&self.views),
2013 })
2014 }
2015
2016 pub fn outstanding_views(&self) -> usize {
2018 self.views.load(Ordering::Acquire)
2019 }
2020
2021 pub fn into_bound(self) -> Result<BoundAllocation, OwningReleaseError> {
2026 self.take("disowning an allocation with outstanding views")
2027 }
2028
2029 pub fn prepare_release(self) -> Result<PreparedAllocationRelease, OwningReleaseError> {
2031 let views = Arc::clone(&self.views);
2032 let allocation = self.take("preparing release with outstanding views")?;
2033 let binding = allocation.binding.clone();
2034 binding.prepare_release(allocation).map_err(|error| {
2035 let (error, allocation) = error.into_parts();
2036 OwningReleaseError {
2037 error,
2038 allocation: Box::new(Self {
2039 allocation: Some(allocation),
2040 views,
2041 }),
2042 }
2043 })
2044 }
2045
2046 pub fn release_now(self) -> Result<AllocationReleaseOutcome, OwningReleaseError> {
2051 Ok(self.prepare_release()?.execute())
2052 }
2053
2054 pub fn release_deferred(
2060 self,
2061 queue: &dyn DeferredReleaseQueue,
2062 ) -> Result<DeferredReleaseDisposition, OwningReleaseError> {
2063 let prepared = self.prepare_release()?;
2064 let identity = prepared.identity();
2065 match queue.enqueue(prepared) {
2066 Ok(()) => Ok(DeferredReleaseDisposition::Queued { identity }),
2067 Err(error) => {
2068 let rejection = error.rejection();
2069 Ok(DeferredReleaseDisposition::Quarantined {
2070 identity,
2071 rejection,
2072 outcome: error.quarantine(),
2073 })
2074 }
2075 }
2076 }
2077
2078 fn take(mut self, operation: &'static str) -> Result<BoundAllocation, OwningReleaseError> {
2079 let _ = operation;
2080 let views = Arc::clone(&self.views);
2081 let outstanding = views.load(Ordering::Acquire);
2082 let allocation = self
2083 .allocation
2084 .take()
2085 .expect("an owning allocation holds its allocation until it is consumed");
2086 if outstanding != 0 {
2087 return Err(OwningReleaseError {
2088 error: BindingError::OutstandingViews {
2089 identity: allocation.identity,
2090 views: outstanding,
2091 },
2092 allocation: Box::new(Self {
2093 allocation: Some(allocation),
2094 views,
2095 }),
2096 });
2097 }
2098 Ok(allocation)
2099 }
2100}
2101
2102impl Drop for OwningAllocation {
2103 fn drop(&mut self) {
2113 let Some(allocation) = self.allocation.take() else {
2114 return;
2115 };
2116 let binding = allocation.binding.clone();
2117 if let Ok(prepared) = binding.prepare_release(allocation) {
2118 let _ = prepared.quarantine(QuarantineReason::OwnerDropped);
2119 }
2120 }
2121}
2122
2123#[derive(Debug)]
2129pub struct OwnedView {
2130 view: BoundMemoryView,
2131 outstanding: Arc<AtomicUsize>,
2132}
2133
2134impl Clone for OwnedView {
2135 fn clone(&self) -> Self {
2136 self.outstanding.fetch_add(1, Ordering::AcqRel);
2137 Self {
2138 view: self.view.clone(),
2139 outstanding: Arc::clone(&self.outstanding),
2140 }
2141 }
2142}
2143
2144impl Drop for OwnedView {
2145 fn drop(&mut self) {
2146 self.outstanding.fetch_sub(1, Ordering::AcqRel);
2147 }
2148}
2149
2150impl OwnedView {
2151 pub const fn view(&self) -> &BoundMemoryView {
2152 &self.view
2153 }
2154
2155 pub const fn binding(&self) -> &MemoryBinding {
2156 self.view.binding()
2157 }
2158
2159 pub const fn allocation_identity(&self) -> AllocationIdentity {
2160 self.view.allocation_identity()
2161 }
2162
2163 pub const fn len(&self) -> usize {
2164 self.view.len()
2165 }
2166
2167 pub const fn is_empty(&self) -> bool {
2168 self.view.is_empty()
2169 }
2170}
2171
2172#[derive(Debug)]
2178pub struct OwningReleaseError {
2179 error: BindingError,
2180 allocation: Box<OwningAllocation>,
2182}
2183
2184impl OwningReleaseError {
2185 pub const fn error(&self) -> &BindingError {
2186 &self.error
2187 }
2188
2189 pub const fn allocation(&self) -> &OwningAllocation {
2190 &self.allocation
2191 }
2192
2193 pub const fn state(&self) -> AllocationReleaseState {
2195 AllocationReleaseState::Live
2196 }
2197
2198 pub fn into_parts(self) -> (BindingError, OwningAllocation) {
2199 (self.error, *self.allocation)
2200 }
2201}
2202
2203impl std::fmt::Display for OwningReleaseError {
2204 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2205 std::fmt::Display::fmt(&self.error, formatter)
2206 }
2207}
2208
2209impl std::error::Error for OwningReleaseError {
2210 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2211 Some(&self.error)
2212 }
2213}
2214
2215#[derive(Debug)]
2226pub struct ExplicitReleaseError {
2227 error: BindingError,
2228 allocation: Box<BoundAllocation>,
2229 outcome: Option<Box<AllocationReleaseOutcome>>,
2231}
2232
2233impl ExplicitReleaseError {
2234 fn unchanged(error: BindingError, allocation: BoundAllocation) -> Self {
2235 Self {
2236 error,
2237 allocation: Box::new(allocation),
2238 outcome: None,
2239 }
2240 }
2241
2242 fn quarantined(
2243 error: BindingError,
2244 allocation: BoundAllocation,
2245 outcome: AllocationReleaseOutcome,
2246 ) -> Self {
2247 Self {
2248 error,
2249 allocation: Box::new(allocation),
2250 outcome: Some(Box::new(outcome)),
2251 }
2252 }
2253
2254 pub const fn error(&self) -> &BindingError {
2255 &self.error
2256 }
2257
2258 pub fn outcome(&self) -> Option<&AllocationReleaseOutcome> {
2262 self.outcome.as_deref()
2263 }
2264
2265 pub const fn is_quarantined(&self) -> bool {
2267 self.outcome.is_some()
2268 }
2269
2270 pub fn state(&self) -> AllocationReleaseState {
2272 self.outcome.as_deref().map_or(
2273 AllocationReleaseState::Live,
2274 AllocationReleaseOutcome::state,
2275 )
2276 }
2277
2278 pub fn into_parts(self) -> (BindingError, BoundAllocation) {
2279 (self.error, *self.allocation)
2280 }
2281}
2282
2283impl std::fmt::Display for ExplicitReleaseError {
2284 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2285 std::fmt::Display::fmt(&self.error, formatter)
2286 }
2287}
2288
2289impl std::error::Error for ExplicitReleaseError {
2290 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2291 Some(&self.error)
2292 }
2293}
2294
2295#[derive(Clone, Debug)]
2297pub struct BoundVirtualBacking {
2298 binding: MemoryBinding,
2299}
2300
2301impl BoundVirtualBacking {
2302 pub const fn binding_identity(&self) -> BindingIdentity {
2303 self.binding.identity
2304 }
2305
2306 pub fn allocate_committed(
2307 &self,
2308 bytes: usize,
2309 align: usize,
2310 committed_ranges: &[std::ops::Range<usize>],
2311 ) -> Result<BoundAllocation, BindingError> {
2312 self.binding.allocate_with(
2313 "allocating through bound virtual backing",
2314 |allocator| {
2315 allocator
2316 .as_virtual_backing()
2317 .expect("capability presence is stable for a registered allocator")
2318 .allocate_committed(bytes, align, committed_ranges)
2319 },
2320 bytes,
2321 align,
2322 )
2323 }
2324
2325 pub fn commit_allocation_range(
2326 &self,
2327 allocation: &BoundAllocation,
2328 offset: usize,
2329 bytes: usize,
2330 ) -> Result<(), BindingError> {
2331 let active = self
2332 .binding
2333 .mechanism
2334 .begin_active("committing through bound virtual backing")?;
2335 self.binding.mechanism.validate_allocation(
2336 self.binding.identity,
2337 allocation,
2338 "validating allocation for virtual commit",
2339 )?;
2340 let capability = self
2341 .binding
2342 .mechanism
2343 .allocator()
2344 .as_virtual_backing()
2345 .expect("capability presence is stable for a registered allocator");
2346 capability.commit_allocation_range(
2347 allocation.ptr,
2348 allocation.bytes,
2349 allocation.align,
2350 offset,
2351 bytes,
2352 )?;
2353 drop(active);
2354 Ok(())
2355 }
2356
2357 pub fn commit_allocation_ranges(
2358 &self,
2359 ranges: &[(&BoundAllocation, usize, usize)],
2360 ) -> Result<(), BindingError> {
2361 let active = self
2362 .binding
2363 .mechanism
2364 .begin_active("committing ranges through bound virtual backing")?;
2365 let mut raw = Vec::with_capacity(ranges.len());
2366 for &(allocation, offset, bytes) in ranges {
2367 self.binding.mechanism.validate_allocation(
2368 self.binding.identity,
2369 allocation,
2370 "validating allocation ranges for virtual commit",
2371 )?;
2372 raw.push(AllocationCommitRange {
2373 ptr: allocation.ptr,
2374 allocation_bytes: allocation.bytes,
2375 align: allocation.align,
2376 offset,
2377 bytes,
2378 });
2379 }
2380 self.binding
2381 .mechanism
2382 .allocator()
2383 .as_virtual_backing()
2384 .expect("capability presence is stable for a registered allocator")
2385 .commit_allocation_ranges(&raw)?;
2386 drop(active);
2387 Ok(())
2388 }
2389
2390 pub fn mapped_bytes_for_allocation(
2391 &self,
2392 bytes: usize,
2393 align: usize,
2394 ) -> Result<u64, BindingError> {
2395 let active = self
2396 .binding
2397 .mechanism
2398 .begin_active("querying bound virtual backing")?;
2399 let mapped = self
2400 .binding
2401 .mechanism
2402 .allocator()
2403 .as_virtual_backing()
2404 .expect("capability presence is stable for a registered allocator")
2405 .mapped_bytes_for_allocation(bytes, align)?;
2406 drop(active);
2407 Ok(mapped)
2408 }
2409
2410 pub fn decommit_allocation_range(
2411 &self,
2412 allocation: &BoundAllocation,
2413 offset: usize,
2414 bytes: usize,
2415 ) -> Result<u64, BindingError> {
2416 let active = self
2417 .binding
2418 .mechanism
2419 .begin_active("decommitting through bound virtual backing")?;
2420 self.binding.mechanism.validate_allocation(
2421 self.binding.identity,
2422 allocation,
2423 "validating allocation for virtual decommit",
2424 )?;
2425 let unmapped = self
2426 .binding
2427 .mechanism
2428 .allocator()
2429 .as_virtual_backing()
2430 .expect("capability presence is stable for a registered allocator")
2431 .decommit_allocation_range(
2432 allocation.ptr,
2433 allocation.bytes,
2434 allocation.align,
2435 offset,
2436 bytes,
2437 )?;
2438 drop(active);
2439 Ok(unmapped)
2440 }
2441
2442 pub fn allocation_committed_bytes(
2443 &self,
2444 allocation: &BoundAllocation,
2445 ) -> Result<usize, BindingError> {
2446 let active = self
2447 .binding
2448 .mechanism
2449 .begin_active("querying bound allocation commitment")?;
2450 self.binding.mechanism.validate_allocation(
2451 self.binding.identity,
2452 allocation,
2453 "validating allocation commitment query",
2454 )?;
2455 let committed = self
2456 .binding
2457 .mechanism
2458 .allocator()
2459 .as_virtual_backing()
2460 .expect("capability presence is stable for a registered allocator")
2461 .allocation_committed_bytes(allocation.ptr, allocation.bytes, allocation.align);
2462 drop(active);
2463 Ok(committed)
2464 }
2465}
2466
2467#[derive(Clone, Debug)]
2469pub struct BoundSharedMapping {
2470 binding: MemoryBinding,
2471}
2472
2473impl BoundSharedMapping {
2474 pub const fn binding_identity(&self) -> BindingIdentity {
2475 self.binding.identity
2476 }
2477
2478 pub fn create_shared_prefix(&self, bytes: usize) -> Result<BoundSharedPrefix, BindingError> {
2479 let active = self
2480 .binding
2481 .mechanism
2482 .begin_active("creating a bound shared prefix")?;
2483 let prefix = self
2484 .binding
2485 .mechanism
2486 .allocator()
2487 .as_shared_mapping()
2488 .expect("capability presence is stable for a registered allocator")
2489 .create_shared_prefix(bytes)?;
2490 drop(active);
2491 Ok(BoundSharedPrefix {
2492 prefix,
2493 binding: self.binding.clone(),
2494 })
2495 }
2496
2497 pub fn incremental_owned_bytes_for_shared_prefix(
2498 &self,
2499 prefix: &BoundSharedPrefix,
2500 ) -> Result<u64, BindingError> {
2501 let active = self
2502 .binding
2503 .mechanism
2504 .begin_active("querying a bound shared prefix")?;
2505 validate_binding_identity(self.binding.identity, prefix.binding.identity)?;
2506 let bytes = self
2507 .binding
2508 .mechanism
2509 .allocator()
2510 .as_shared_mapping()
2511 .expect("capability presence is stable for a registered allocator")
2512 .incremental_owned_bytes_for_shared_prefix(prefix.prefix.as_ref())?;
2513 drop(active);
2514 Ok(bytes)
2515 }
2516
2517 pub fn commit_shared_prefix(
2518 &self,
2519 prefix: &BoundSharedPrefix,
2520 allocation: &BoundAllocation,
2521 byte_offset: usize,
2522 ) -> Result<SharedPrefixCommitInfo, BindingError> {
2523 let active = self
2524 .binding
2525 .mechanism
2526 .begin_active("committing a bound shared prefix")?;
2527 validate_binding_identity(self.binding.identity, prefix.binding.identity)?;
2528 self.binding.mechanism.validate_allocation(
2529 self.binding.identity,
2530 allocation,
2531 "validating allocation for shared prefix commit",
2532 )?;
2533 let info = self
2534 .binding
2535 .mechanism
2536 .allocator()
2537 .as_shared_mapping()
2538 .expect("capability presence is stable for a registered allocator")
2539 .commit_shared_prefix(
2540 prefix.prefix.as_ref(),
2541 allocation.ptr,
2542 allocation.bytes,
2543 byte_offset,
2544 )?;
2545 drop(active);
2546 Ok(info)
2547 }
2548}
2549
2550#[derive(Debug)]
2557pub struct BoundSharedPrefix {
2558 prefix: Box<dyn SharedDevicePrefix>,
2559 binding: MemoryBinding,
2560}
2561
2562impl BoundSharedPrefix {
2563 pub const fn binding_identity(&self) -> BindingIdentity {
2564 self.binding.identity
2565 }
2566
2567 pub fn device_ptr(&self) -> u64 {
2568 self.prefix.device_ptr()
2569 }
2570
2571 pub fn committed_physical_bytes(&self) -> u64 {
2572 self.prefix.committed_physical_bytes()
2573 }
2574
2575 pub fn mapped_bytes(&self) -> usize {
2576 self.prefix.mapped_bytes()
2577 }
2578
2579 pub fn requested_bytes(&self) -> usize {
2580 self.prefix.requested_bytes()
2581 }
2582}
2583
2584fn validate_binding_identity(
2585 expected: BindingIdentity,
2586 actual: BindingIdentity,
2587) -> Result<(), BindingError> {
2588 if expected != actual {
2589 return Err(BindingError::BindingMismatch {
2590 expected: expected.id,
2591 actual: actual.id,
2592 });
2593 }
2594 Ok(())
2595}
2596
2597#[cfg(test)]
2598mod tests {
2599 use std::sync::{Arc, Barrier};
2600 use std::thread;
2601
2602 use crate::{BindingResource, HostAllocator};
2603
2604 use super::*;
2605
2606 struct SelectionFixture {
2611 registry: BindingRegistry,
2612 context: RegisteredProviderContext,
2613 authority: RegisteredAuthority,
2614 }
2615
2616 impl SelectionFixture {
2617 fn new() -> Self {
2618 let registry = BindingRegistry::new().expect("registry");
2619 let context = registry
2620 .register_provider_context(
2621 DeviceKey::HOST,
2622 Arc::new(()) as Arc<dyn BindingResource>,
2623 )
2624 .expect("context registration");
2625 let authority = registry
2626 .register_authority(DeviceKey::HOST, Arc::new(()) as Arc<dyn BindingResource>)
2627 .expect("authority registration");
2628 Self {
2629 registry,
2630 context,
2631 authority,
2632 }
2633 }
2634
2635 fn mechanism(&self) -> RegisteredMechanism {
2638 self.registry
2639 .register_allocator(
2640 self.context,
2641 self.authority,
2642 Arc::new(HostAllocator) as Arc<dyn DeviceAllocator>,
2643 )
2644 .expect("mechanism registration")
2645 }
2646
2647 fn gate(&self, mechanism: RegisteredMechanism, phase: HookPhase) -> Gate {
2648 self.gate_subject(HookSubject::Mechanism(mechanism.identity), phase)
2649 }
2650
2651 fn gate_device(&self, phase: HookPhase) -> Gate {
2652 self.gate_subject(HookSubject::Device(DeviceKey::HOST), phase)
2653 }
2654
2655 fn gate_subject(&self, subject: HookSubject, phase: HookPhase) -> Gate {
2656 let entered = Arc::new(Barrier::new(2));
2657 let resume = Arc::new(Barrier::new(2));
2658 self.registry.install_hook(RegistryHook {
2659 subject,
2660 phase,
2661 entered: Arc::clone(&entered),
2662 resume: Arc::clone(&resume),
2663 });
2664 Gate { entered, resume }
2665 }
2666
2667 fn select_on_thread(
2668 &self,
2669 mechanism: RegisteredMechanism,
2670 ) -> thread::JoinHandle<Result<(), BindingError>> {
2671 let registry = self.registry.clone();
2672 thread::spawn(move || registry.select(mechanism))
2673 }
2674
2675 fn selected_mechanism(&self) -> Result<MechanismIdentity, BindingError> {
2676 self.registry
2677 .bind(DeviceKey::HOST)
2678 .map(|binding| binding.identity().mechanism())
2679 }
2680
2681 fn assert_nothing_selected(&self) {
2682 let error = self
2683 .selected_mechanism()
2684 .expect_err("withdrawal must leave no selection to heal from");
2685 assert!(
2686 matches!(error, BindingError::NoSelectedMechanism(device) if device == DeviceKey::HOST),
2687 "selection was left pointing at a dead or unregistered mechanism: {error:?}"
2688 );
2689 }
2690 }
2691
2692 struct Gate {
2694 entered: Arc<Barrier>,
2695 resume: Arc<Barrier>,
2696 }
2697
2698 impl Gate {
2699 fn wait_entered(&self) {
2700 self.entered.wait();
2701 }
2702
2703 fn resume(&self) {
2704 self.resume.wait();
2705 }
2706 }
2707
2708 fn assert_select_failed(
2709 result: Result<(), BindingError>,
2710 expected: RegisteredMechanism,
2711 expected_lifecycle: MechanismLifecycle,
2712 ) {
2713 let error = result.expect_err("select must fail once its candidate is inactive");
2714 assert!(
2715 matches!(
2716 error,
2717 BindingError::InactiveMechanism {
2718 mechanism,
2719 lifecycle,
2720 operation: "selecting a mechanism",
2721 } if mechanism == expected.identity && lifecycle == expected_lifecycle
2722 ),
2723 "unexpected select error: {error:?}"
2724 );
2725 }
2726
2727 fn publish_a_doomed_candidate(
2730 fixture: &SelectionFixture,
2731 candidate: RegisteredMechanism,
2732 ) -> (thread::JoinHandle<Result<(), BindingError>>, Gate) {
2733 let validated = fixture.gate(candidate, HookPhase::SelectAfterValidation);
2734 let published = fixture.gate(candidate, HookPhase::SelectAfterPublish);
2735 let selecting = fixture.select_on_thread(candidate);
2736
2737 validated.wait_entered();
2738 fixture
2741 .registry
2742 .retire(candidate)
2743 .expect("retire candidate");
2744 validated.resume();
2745
2746 published.wait_entered();
2748 (selecting, published)
2749 }
2750
2751 #[test]
2752 fn failed_select_restores_the_prior_healthy_selection() {
2753 let fixture = SelectionFixture::new();
2754 let prior = fixture.mechanism();
2755 let candidate = fixture.mechanism();
2756
2757 let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
2758 published.resume();
2759
2760 assert_select_failed(
2761 selecting.join().expect("select thread"),
2762 candidate,
2763 MechanismLifecycle::Retired,
2764 );
2765 assert_eq!(
2766 fixture
2767 .selected_mechanism()
2768 .expect("healthy prior restored"),
2769 prior.identity
2770 );
2771 }
2772
2773 #[test]
2774 fn failed_select_clears_a_retired_prior_instead_of_restoring_it() {
2775 let fixture = SelectionFixture::new();
2776 let prior = fixture.mechanism();
2777 let candidate = fixture.mechanism();
2778
2779 let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
2780 fixture.registry.retire(prior).expect("retire prior");
2783 published.resume();
2784
2785 assert_select_failed(
2786 selecting.join().expect("select thread"),
2787 candidate,
2788 MechanismLifecycle::Retired,
2789 );
2790 fixture.assert_nothing_selected();
2791 }
2792
2793 #[test]
2794 fn failed_select_clears_a_removed_prior_instead_of_restoring_it() {
2795 let fixture = SelectionFixture::new();
2796 let prior = fixture.mechanism();
2797 let candidate = fixture.mechanism();
2798
2799 let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
2800 fixture.registry.retire(prior).expect("retire prior");
2801 fixture.registry.remove(prior).expect("remove prior");
2802 published.resume();
2803
2804 assert_select_failed(
2805 selecting.join().expect("select thread"),
2806 candidate,
2807 MechanismLifecycle::Retired,
2808 );
2809 fixture.assert_nothing_selected();
2810 }
2811
2812 #[test]
2813 fn failed_select_does_not_overwrite_a_newer_selection() {
2814 let fixture = SelectionFixture::new();
2815 let _prior = fixture.mechanism();
2816 let candidate = fixture.mechanism();
2817 let newer = fixture.mechanism();
2818
2819 let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
2820 fixture.registry.select(newer).expect("newer selection");
2822 published.resume();
2823
2824 assert_select_failed(
2825 selecting.join().expect("select thread"),
2826 candidate,
2827 MechanismLifecycle::Retired,
2828 );
2829 assert_eq!(
2830 fixture
2831 .selected_mechanism()
2832 .expect("newer selection stands"),
2833 newer.identity
2834 );
2835 }
2836
2837 #[test]
2838 fn two_failed_selects_never_leave_a_dead_mechanism_selected() {
2839 let fixture = SelectionFixture::new();
2840 let prior = fixture.mechanism();
2841 let first = fixture.mechanism();
2842 let second = fixture.mechanism();
2843
2844 let first_published = fixture.gate(first, HookPhase::SelectAfterPublish);
2845 let second_validated = fixture.gate(second, HookPhase::SelectAfterValidation);
2846 let second_published = fixture.gate(second, HookPhase::SelectAfterPublish);
2847
2848 let selecting_first = fixture.select_on_thread(first);
2850 first_published.wait_entered();
2851
2852 let selecting_second = fixture.select_on_thread(second);
2855 second_validated.wait_entered();
2856 fixture.registry.retire(second).expect("retire second");
2857 second_validated.resume();
2858
2859 second_published.wait_entered();
2862 fixture.registry.retire(first).expect("retire first");
2863
2864 first_published.resume();
2867 assert_select_failed(
2868 selecting_first.join().expect("first select thread"),
2869 first,
2870 MechanismLifecycle::Retired,
2871 );
2872
2873 second_published.resume();
2875 assert_select_failed(
2876 selecting_second.join().expect("second select thread"),
2877 second,
2878 MechanismLifecycle::Retired,
2879 );
2880
2881 fixture.assert_nothing_selected();
2882 fixture.registry.select(prior).expect("reselect prior");
2884 assert_eq!(
2885 fixture.selected_mechanism().expect("prior reselected"),
2886 prior.identity
2887 );
2888 }
2889
2890 #[test]
2891 fn a_later_registration_heals_a_cleared_selection() {
2892 let fixture = SelectionFixture::new();
2893 let prior = fixture.mechanism();
2894 let candidate = fixture.mechanism();
2895
2896 let (selecting, published) = publish_a_doomed_candidate(&fixture, candidate);
2897 fixture.registry.retire(prior).expect("retire prior");
2898 fixture.registry.remove(prior).expect("remove prior");
2899 published.resume();
2900
2901 assert_select_failed(
2902 selecting.join().expect("select thread"),
2903 candidate,
2904 MechanismLifecycle::Retired,
2905 );
2906 fixture.assert_nothing_selected();
2907
2908 let healed = fixture.mechanism();
2912 assert_eq!(
2913 fixture
2914 .selected_mechanism()
2915 .expect("registration self-heal"),
2916 healed.identity
2917 );
2918 }
2919
2920 #[test]
2921 fn retire_racing_a_select_cannot_leave_the_retired_mechanism_selected() {
2922 let fixture = SelectionFixture::new();
2923 let prior = fixture.mechanism();
2924 let candidate = fixture.mechanism();
2925
2926 let validated = fixture.gate(candidate, HookPhase::SelectAfterValidation);
2927 let retiring_gate = fixture.gate(candidate, HookPhase::RetireBetweenPhases);
2928
2929 let selecting = fixture.select_on_thread(candidate);
2931 validated.wait_entered();
2932
2933 let registry = fixture.registry.clone();
2937 let retiring = thread::spawn(move || registry.retire(candidate));
2938 retiring_gate.wait_entered();
2939
2940 validated.resume();
2942 assert_select_failed(
2943 selecting.join().expect("select thread"),
2944 candidate,
2945 MechanismLifecycle::Retired,
2946 );
2947
2948 retiring_gate.resume();
2949 retiring
2950 .join()
2951 .expect("retire thread")
2952 .expect("retire must succeed");
2953
2954 assert_eq!(
2955 fixture
2956 .selected_mechanism()
2957 .expect("healthy prior restored"),
2958 prior.identity
2959 );
2960 }
2961
2962 #[test]
2963 fn device_loss_racing_a_select_cannot_leave_a_lost_mechanism_selected() {
2964 let fixture = SelectionFixture::new();
2965 let _prior = fixture.mechanism();
2966 let candidate = fixture.mechanism();
2967
2968 let validated = fixture.gate(candidate, HookPhase::SelectAfterValidation);
2969 let losing = fixture.gate_device(HookPhase::InvalidateBetweenPhases);
2970
2971 let selecting = fixture.select_on_thread(candidate);
2972 validated.wait_entered();
2973
2974 let registry = fixture.registry.clone();
2977 let invalidating =
2978 thread::spawn(move || registry.invalidate_device(DeviceKey::HOST, "select race"));
2979 losing.wait_entered();
2980
2981 validated.resume();
2982 assert_select_failed(
2983 selecting.join().expect("select thread"),
2984 candidate,
2985 MechanismLifecycle::DeviceLost,
2986 );
2987
2988 losing.resume();
2989 invalidating
2990 .join()
2991 .expect("invalidate thread")
2992 .expect("invalidate must succeed");
2993
2994 fixture.assert_nothing_selected();
2997 }
2998
2999 #[test]
3000 fn retirement_and_device_loss_drop_the_current_selection() {
3001 let fixture = SelectionFixture::new();
3002 let retired = fixture.mechanism();
3003 assert_eq!(
3004 fixture
3005 .selected_mechanism()
3006 .expect("first registration selects"),
3007 retired.identity
3008 );
3009
3010 fixture.registry.retire(retired).expect("retire");
3011 fixture.assert_nothing_selected();
3012
3013 let lost = fixture.mechanism();
3014 assert_eq!(
3015 fixture
3016 .selected_mechanism()
3017 .expect("registration self-heal"),
3018 lost.identity
3019 );
3020
3021 fixture
3022 .registry
3023 .invalidate_device(DeviceKey::HOST, "device lost")
3024 .expect("invalidate");
3025 fixture.assert_nothing_selected();
3026 }
3027}