use core::{
cell::UnsafeCell,
fmt,
ops::Deref,
panic::{RefUnwindSafe, UnwindSafe},
sync::atomic::{AtomicUsize, Ordering::Relaxed},
};
use crate::{
sync::RawMutex,
sys::{
sync as sys,
thread::{sceKernelGetThreadId, ThreadId},
},
};
type DefaultMutex = cfg_select! {
feature = "kernel" => sys::Mutex,
_ => sys::Mutex,
};
pub struct ReentrantLock<T: ?Sized, M = DefaultMutex> {
mutex: M,
owner: AtomicUsize,
lock_count: UnsafeCell<u32>,
data: T,
}
unsafe impl<T: Send + ?Sized, M> Send for ReentrantLock<T, M> {}
unsafe impl<T: Send + ?Sized, M> Sync for ReentrantLock<T, M> {}
impl<T: UnwindSafe + ?Sized, M> UnwindSafe for ReentrantLock<T, M> {}
impl<T: RefUnwindSafe + ?Sized, M> RefUnwindSafe for ReentrantLock<T, M> {}
#[must_use = "if unused the ReentrantLock will immediately unlock"]
pub struct ReentrantLockGuard<'a, T: ?Sized + 'a, M: RawMutex = DefaultMutex> {
lock: &'a ReentrantLock<T, M>,
}
impl<T: ?Sized, M: RawMutex> !Send for ReentrantLockGuard<'_, T, M> {}
unsafe impl<T: ?Sized + Sync, M: RawMutex> Sync for ReentrantLockGuard<'_, T, M> {}
impl<T> ReentrantLock<T> {
pub const fn new(t: T) -> ReentrantLock<T> {
ReentrantLock {
mutex: DefaultMutex::new(),
owner: AtomicUsize::new(0),
lock_count: UnsafeCell::new(0),
data: t,
}
}
}
impl<T, M: RawMutex> ReentrantLock<T, M> {
#[inline]
pub const fn new_with(value: T, raw_mutex: M) -> Self {
Self {
mutex: raw_mutex,
owner: AtomicUsize::new(0),
lock_count: UnsafeCell::new(0),
data: value,
}
}
pub fn into_inner(self) -> T {
self.data
}
}
impl<T: ?Sized, M: RawMutex> ReentrantLock<T, M> {
pub fn lock(&self) -> ReentrantLockGuard<'_, T, M> {
let this_thread = current_thread_id().to_inner() as usize;
unsafe {
if self.owner.load(Relaxed) == this_thread {
self.increment_lock_count()
.expect("lock count overflow in reentrant mutex");
} else {
self.mutex.lock();
self.owner.store(this_thread, Relaxed);
debug_assert_eq!(*self.lock_count.get(), 0);
*self.lock_count.get() = 1;
}
}
ReentrantLockGuard { lock: self }
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.data
}
pub const fn data_ptr(&self) -> *const T {
&raw const self.data
}
pub(crate) fn try_lock(&self) -> Option<ReentrantLockGuard<'_, T, M>> {
let this_thread = current_thread_id().to_inner() as usize;
unsafe {
if self.owner.load(Relaxed) == this_thread {
self.increment_lock_count()?;
Some(ReentrantLockGuard { lock: self })
} else if self.mutex.try_lock() {
self.owner.store(this_thread, Relaxed);
debug_assert_eq!(*self.lock_count.get(), 0);
*self.lock_count.get() = 1;
Some(ReentrantLockGuard { lock: self })
} else {
None
}
}
}
unsafe fn increment_lock_count(&self) -> Option<()> {
unsafe { *self.lock_count.get() = (*self.lock_count.get()).checked_add(1)? };
Some(())
}
}
impl<T: fmt::Debug + ?Sized, M: RawMutex> fmt::Debug for ReentrantLock<T, M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("ReentrantLock");
match self.try_lock() {
Some(v) => d.field("data", &&*v),
None => d.field("data", &format_args!("<locked>")),
};
d.finish_non_exhaustive()
}
}
impl<T: Default> Default for ReentrantLock<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T> From<T> for ReentrantLock<T> {
fn from(t: T) -> Self {
Self::new(t)
}
}
impl<T: ?Sized, M: RawMutex> Deref for ReentrantLockGuard<'_, T, M> {
type Target = T;
fn deref(&self) -> &T {
&self.lock.data
}
}
impl<T: fmt::Debug + ?Sized, M: RawMutex> fmt::Debug for ReentrantLockGuard<'_, T, M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display + ?Sized, M: RawMutex> fmt::Display for ReentrantLockGuard<'_, T, M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: ?Sized, M: RawMutex> Drop for ReentrantLockGuard<'_, T, M> {
#[inline]
fn drop(&mut self) {
unsafe {
*self.lock.lock_count.get() -= 1;
if *self.lock.lock_count.get() == 0 {
self.lock.owner.store(0, Relaxed);
self.lock.mutex.unlock();
}
}
}
}
pub(crate) fn current_thread_id() -> ThreadId {
cfg_select! {
target_os = "psp" => {
sceKernelGetThreadId().into_result().expect("wrong context for `sceKernelGetThreadId`")
}
_ => {
unimplemented!("Not a PSP OS")
}
}
}