use core::cell::Cell;
use core::fmt::{Display, Formatter};
use core::panic::Location;
use lock_api::{GuardNoSend, RawMutex, RawRwLock, RawRwLockRecursive};
pub struct CellMutex(CellRwLock);
unsafe impl RawMutex for CellMutex {
#[allow(clippy::declare_interior_mutable_const)] const INIT: Self = CellMutex(CellRwLock::INIT);
type GuardMarker = GuardNoSend;
#[inline]
#[track_caller]
fn lock(&self) {
self.0.lock_exclusive()
}
#[inline]
#[track_caller]
fn try_lock(&self) -> bool {
self.0.try_lock_exclusive()
}
#[inline]
#[track_caller]
unsafe fn unlock(&self) {
self.0.unlock_exclusive()
}
#[inline]
#[track_caller]
fn is_locked(&self) -> bool {
self.0.is_locked()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
struct BorrowFlag {
count: isize,
}
impl BorrowFlag {
pub const UNUSED: BorrowFlag = BorrowFlag { count: 0 };
#[inline]
pub fn state(self) -> BorrowState {
#[allow(clippy::comparison_chain)]
if self.count < 0 {
BorrowState::MutableBorrow
} else if self.count > 0 {
BorrowState::SharedBorrow
} else {
BorrowState::Unused
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum BorrowState {
MutableBorrow,
Unused,
SharedBorrow,
}
#[derive(Debug)]
pub struct CellRwLock {
borrow_count: Cell<BorrowFlag>,
#[cfg(debug_location)]
earliest_borrow_location: Cell<Option<&'static Location<'static>>>,
}
impl CellRwLock {
#[inline]
fn earliest_borrow_location(&self) -> Option<&'static Location<'static>> {
#[cfg(debug_location)]
{
self.earliest_borrow_location.get()
}
#[cfg(not(debug_location))]
{
None
}
}
#[inline]
#[track_caller]
fn try_borrow_exclusively(&self) -> Result<(), BorrowFailError> {
if matches!(self.borrow_count.get().state(), BorrowState::Unused) {
assert_eq!(self.borrow_count.get().count, 0);
self.borrow_count.set(BorrowFlag { count: -1 });
#[cfg(debug_location)]
self.earliest_borrow_location.set(Location::caller());
Ok(())
} else {
Err(BorrowFailError {
is_exclusive: true,
existing_location: self.earliest_borrow_location(),
})
}
}
#[inline]
#[track_caller]
fn try_borrow_shared(&self) -> Result<(), BorrowFailError> {
if matches!(
self.borrow_count.get().state(),
BorrowState::Unused | BorrowState::SharedBorrow
) {
self.borrow_count.set(BorrowFlag {
count: self
.borrow_count
.get()
.count
.checked_add(1)
.expect("Overflow shared borrows"),
});
Ok(())
} else {
debug_assert_eq!(self.borrow_count.get().state(), BorrowState::MutableBorrow);
Err(BorrowFailError {
is_exclusive: false,
existing_location: self.earliest_borrow_location(),
})
}
}
}
#[derive(Debug)]
struct BorrowFailError {
is_exclusive: bool,
existing_location: Option<&'static Location<'static>>,
}
impl BorrowFailError {
#[cold]
#[track_caller]
pub fn panic(&self) -> ! {
panic!("{self}")
}
}
impl Display for BorrowFailError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str("Unable to ")?;
if self.is_exclusive {
f.write_str("exclusively ")?
}
f.write_str("borrow")?;
if let Some(existing_location) = self.existing_location {
write!(
f,
": {existing_borrow_kind} borrowed at {existing_location}",
existing_borrow_kind = if self.is_exclusive {
"Already"
} else {
"Exclusively"
}
)?;
}
Ok(())
}
}
unsafe impl RawRwLock for CellRwLock {
#[allow(clippy::declare_interior_mutable_const)] const INIT: Self = CellRwLock {
borrow_count: Cell::new(BorrowFlag::UNUSED),
#[cfg(debug_location)]
earliest_borrow_location: Cell::new(None),
};
type GuardMarker = GuardNoSend;
#[track_caller]
#[inline]
fn lock_shared(&self) {
match self.try_borrow_shared() {
Ok(()) => {}
Err(fail) => fail.panic(),
}
}
#[track_caller]
#[inline]
fn try_lock_shared(&self) -> bool {
self.try_borrow_shared().is_ok()
}
#[inline]
#[track_caller]
unsafe fn unlock_shared(&self) {
debug_assert_eq!(self.borrow_count.get().state(), BorrowState::SharedBorrow);
debug_assert!(self.borrow_count.get().count > 0);
self.borrow_count.set(BorrowFlag {
count: self.borrow_count.get().count - 1,
});
if !self.is_locked() {
#[cfg(debug_location)]
self.earliest_borrow_location.set(None);
}
}
#[inline]
#[track_caller]
fn lock_exclusive(&self) {
match self.try_borrow_exclusively() {
Ok(()) => (),
Err(e) => e.panic(),
}
}
#[inline]
#[track_caller]
fn try_lock_exclusive(&self) -> bool {
self.try_borrow_exclusively().is_ok()
}
#[inline]
#[track_caller]
unsafe fn unlock_exclusive(&self) {
debug_assert_eq!(self.borrow_count.get().state(), BorrowState::MutableBorrow);
debug_assert!(self.borrow_count.get().count < 0);
self.borrow_count.set(BorrowFlag {
count: self.borrow_count.get().count + 1,
});
if !self.is_locked() {
#[cfg(debug_location)]
self.earliest_borrow_location.set(None);
}
}
#[inline]
fn is_locked(&self) -> bool {
match self.borrow_count.get().state() {
BorrowState::Unused => false,
BorrowState::MutableBorrow | BorrowState::SharedBorrow => true,
}
}
#[inline]
fn is_locked_exclusive(&self) -> bool {
matches!(self.borrow_count.get().state(), BorrowState::MutableBorrow)
}
}
unsafe impl RawRwLockRecursive for CellRwLock {
#[inline]
#[track_caller]
fn lock_shared_recursive(&self) {
self.lock_shared()
}
#[inline]
#[track_caller]
fn try_lock_shared_recursive(&self) -> bool {
self.try_lock_shared()
}
}