use super::{AllocError, Allocator, AllocatorStats};
use std::sync::{Condvar, Mutex, MutexGuard, PoisonError, TryLockError};
#[path = "synchronized/access.rs"]
mod access;
#[path = "synchronized/virtual_block.rs"]
mod virtual_block;
pub use virtual_block::SynchronizedVirtualBlock;
pub struct SynchronizedAllocator<'device> {
pub(in crate::mem) inner: Mutex<TransferableAllocator<'device>>,
idle: Condvar,
}
pub(in crate::mem) struct TransferableAllocator<'device> {
pub(in crate::mem) allocator: Allocator<'device>,
pub(in crate::mem) poisoned: bool,
pub(in crate::mem) active: usize,
}
unsafe impl Send for TransferableAllocator<'_> {}
impl<'device> SynchronizedAllocator<'device> {
pub unsafe fn new(allocator: Allocator<'device>) -> Self {
Self {
inner: Mutex::new(TransferableAllocator {
allocator,
poisoned: false,
active: 0,
}),
idle: Condvar::new(),
}
}
pub fn new_owned(allocator: Allocator<'static>) -> Self {
unsafe { Self::new(allocator) }
}
pub(in crate::mem) fn lock_owner(
&self,
) -> Result<MutexGuard<'_, TransferableAllocator<'device>>, AllocError> {
let guard = self
.inner
.lock()
.map_err(|_| AllocError::SynchronizationPoisoned)?;
if guard.poisoned {
return Err(AllocError::SynchronizationPoisoned);
}
Ok(guard)
}
pub(in crate::mem) fn wait_owner<'owner>(
&'owner self,
guard: MutexGuard<'owner, TransferableAllocator<'device>>,
) -> Result<MutexGuard<'owner, TransferableAllocator<'device>>, AllocError> {
let guard = self
.idle
.wait(guard)
.map_err(|_| AllocError::SynchronizationPoisoned)?;
if guard.poisoned {
return Err(AllocError::SynchronizationPoisoned);
}
Ok(guard)
}
pub(in crate::mem) fn lock_idle(
&self,
) -> Result<MutexGuard<'_, TransferableAllocator<'device>>, AllocError> {
let mut guard = self.lock_owner()?;
while guard.active != 0 {
guard = self.wait_owner(guard)?;
}
Ok(guard)
}
pub(in crate::mem) fn notify_idle(&self) {
self.idle.notify_all();
}
pub unsafe fn with_allocator<R>(
&self,
operation: impl FnOnce(&mut Allocator<'device>) -> R,
) -> Result<R, AllocError> {
let mut guard = self.lock_idle()?;
Ok(operation(&mut guard.allocator))
}
pub unsafe fn try_with_allocator<R>(
&self,
operation: impl FnOnce(&mut Allocator<'device>) -> R,
) -> Result<Option<R>, AllocError> {
let mut guard = match self.inner.try_lock() {
Ok(guard) => guard,
Err(TryLockError::WouldBlock) => return Ok(None),
Err(TryLockError::Poisoned(_)) => return Err(AllocError::SynchronizationPoisoned),
};
if guard.poisoned {
return Err(AllocError::SynchronizationPoisoned);
}
if guard.active != 0 {
return Ok(None);
}
Ok(Some(operation(&mut guard.allocator)))
}
pub(in crate::mem) fn owner_mut(&mut self) -> &mut Allocator<'device> {
&mut self
.inner
.get_mut()
.unwrap_or_else(PoisonError::into_inner)
.allocator
}
pub(in crate::mem) fn is_poisoned(&self) -> bool {
self.inner.is_poisoned() || self.inner.lock().is_ok_and(|owner| owner.poisoned)
}
pub unsafe fn with_pools<R>(
&self,
pools: &[&super::Pool],
operation: impl FnOnce(&[Self]) -> R,
) -> Result<R, AllocError> {
unsafe { self.with_domains(super::allocator::Selection::Pools(pools), operation) }
}
pub unsafe fn with_memory_types<R>(
&self,
memory_types: &[u32],
operation: impl FnOnce(&[Self]) -> R,
) -> Result<R, AllocError> {
unsafe {
self.with_domains(
super::allocator::Selection::MemoryTypes(memory_types),
operation,
)
}
}
pub unsafe fn with_blocks<R>(
&self,
allocations: &[&super::Allocation],
operation: impl FnOnce(&[Self]) -> R,
) -> Result<R, AllocError> {
unsafe { self.with_domains(super::allocator::Selection::Blocks(allocations), operation) }
}
unsafe fn with_domains<R>(
&self,
selection: super::allocator::Selection<'_>,
operation: impl FnOnce(&[Self]) -> R,
) -> Result<R, AllocError> {
let mut guard = self.lock_idle()?;
let (result, poisoned) =
unsafe { super::allocator::with_domains(&mut guard.allocator, selection, operation) }?;
if poisoned {
guard.poisoned = true;
return Err(AllocError::SynchronizationPoisoned);
}
Ok(result)
}
pub fn statistics(&self) -> Result<AllocatorStats, AllocError> {
let guard = self.lock_idle()?;
Ok(guard.allocator.statistics())
}
#[allow(clippy::result_large_err)]
pub fn into_inner(self) -> Result<Allocator<'device>, PoisonError<Allocator<'device>>> {
match self.inner.into_inner() {
Ok(owner) if !owner.poisoned => Ok(owner.allocator),
Ok(owner) => Err(PoisonError::new(owner.allocator)),
Err(error) => Err(PoisonError::new(error.into_inner().allocator)),
}
}
}