Skip to content

Commit

Permalink
Merge pull request #4 from qinsoon/always-check-in-debug-builds
Browse files Browse the repository at this point in the history
Always check when debug_assertions is enabled
  • Loading branch information
dovahcrow committed Sep 28, 2023
2 parents efd7e5a + b91104c commit 6cc0b5b
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 32 deletions.
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,9 @@ This crate is designed for this purpose.

peace_lock is a drop-in replacement for std/parking_lot's `Mutex` and `RwLock`.

Add `peace_lock = { version = "0.1", features = ["check"]}` to your Cargo.toml
to enable the check mode. In the check mode, calling `write` or `read` will just
panic in case of contention. This let's you know the scheduling algorithm has a
bug!
Add `peace_lock = "0.1"` to your Cargo.toml. In a debug build, the check mode is
enabled, and calling `write` or `read` will just panic in case of contention.
This lets you know the scheduling algorithm has a bug!


```rust
Expand Down Expand Up @@ -115,9 +114,9 @@ thread::spawn(|| { *shared_map.get(&1).unwrap().lock() = 3; });
thread::spawn(|| { *shared_map.get(&2).unwrap().lock() = 4; });
```

If you want to squeeze the performance, you can disable the check by remove it
from the feature list: `peace_lock = { version = "0.1", features = [] }`. This
will make the lock zero-cost.
In a release build, the checks are by default omitted. This
will make the lock zero-cost. You can explicitly turn on
the check mode by adding the `check` feature to the feature list (e.g. `peace_lock = { version = "0.1", features = ["check"] }`).

## Optional Features

Expand Down
16 changes: 8 additions & 8 deletions src/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use owning_ref::StableAddress;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
use std::sync::atomic::{AtomicBool, Ordering};
use std::{
cell::UnsafeCell,
Expand All @@ -13,7 +13,7 @@ use std::{
/// A mutual exclusive lock
#[derive(Debug)]
pub struct Mutex<T: ?Sized> {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
state: AtomicBool,
value: UnsafeCell<T>,
}
Expand Down Expand Up @@ -43,7 +43,7 @@ impl<T> Mutex<T> {
#[inline]
pub fn new(val: T) -> Self {
Self {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
state: AtomicBool::new(false),
value: UnsafeCell::new(val),
}
Expand Down Expand Up @@ -83,7 +83,7 @@ where
#[inline]
pub fn lock<'a>(&'a self) -> MutexGuard<'a, T> {
if !self.lock_exclusive() {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
panic!("The lock is already write locked")
}

Expand All @@ -92,27 +92,27 @@ where

#[inline]
fn lock_exclusive(&self) -> bool {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
{
self.state
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}

#[cfg(not(feature = "check"))]
#[cfg(not(any(debug_assertions, feature = "check")))]
true
}

#[inline]
fn unlock_exclusive(&self) -> bool {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
{
self.state
.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}

#[cfg(not(feature = "check"))]
#[cfg(not(any(debug_assertions, feature = "check")))]
true
}
}
Expand Down
26 changes: 13 additions & 13 deletions src/rwlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use owning_ref::StableAddress;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{
cell::UnsafeCell,
Expand All @@ -13,16 +13,16 @@ use std::{
// Locking bits are copied from [parking_lot](https://github.com/Amanieu/parking_lot).
// If the reader count is zero: a writer is currently holding an exclusive lock.
// Otherwise: a writer is waiting for the remaining readers to exit the lock.
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
const WRITER_BIT: usize = 0b1000;
// Base unit for counting readers.
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
const ONE_READER: usize = 0b10000;

/// A read-write lock
#[derive(Debug)]
pub struct RwLock<T: ?Sized> {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
state: AtomicUsize,
value: UnsafeCell<T>,
}
Expand Down Expand Up @@ -53,7 +53,7 @@ impl<T> RwLock<T> {
pub const fn new(val: T) -> Self {
Self {
value: UnsafeCell::new(val),
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
state: AtomicUsize::new(0),
}
}
Expand Down Expand Up @@ -93,7 +93,7 @@ where
#[inline]
pub fn write<'a>(&'a self) -> RwLockWriteGuard<'a, T> {
if !self.lock_exclusive() {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
panic!("The lock is already write locked")
}

Expand All @@ -116,7 +116,7 @@ where
#[inline]
pub fn read<'a>(&'a self) -> RwLockReadGuard<'a, T> {
if !self.lock_shared() {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
panic!("The lock is already write locked")
}

Expand All @@ -125,33 +125,33 @@ where

#[inline]
fn lock_exclusive(&self) -> bool {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
{
self.state
.compare_exchange(0, WRITER_BIT, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}

#[cfg(not(feature = "check"))]
#[cfg(not(any(debug_assertions, feature = "check")))]
true
}

#[inline]
fn unlock_exclusive(&self) -> bool {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
{
self.state
.compare_exchange(WRITER_BIT, 0, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}

#[cfg(not(feature = "check"))]
#[cfg(not(any(debug_assertions, feature = "check")))]
true
}

#[inline]
fn lock_shared(&self) -> bool {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
loop {
let state = self.state.load(Ordering::Relaxed);
if state & WRITER_BIT != 0 {
Expand All @@ -178,7 +178,7 @@ where

#[inline]
fn unlock_shared(&self) {
#[cfg(feature = "check")]
#[cfg(any(debug_assertions, feature = "check"))]
self.state.fetch_sub(ONE_READER, Ordering::Release);
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn lock_unlock_lock() {
}

#[test]
#[should_panic]
#[cfg_attr(any(debug_assertions, feature = "check"), should_panic)]
fn double_lock() {
let val = Mutex::new(1);
thread::scope(|s| {
Expand Down
6 changes: 3 additions & 3 deletions tests/rwlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn write_lock_unlock_lock() {
}

#[test]
#[should_panic]
#[cfg_attr(any(debug_assertions, feature = "check"), should_panic)]
fn double_write_lock() {
let val = RwLock::new(1);
thread::scope(|s| {
Expand All @@ -36,7 +36,7 @@ fn double_write_lock() {
}

#[test]
#[should_panic]
#[cfg_attr(any(debug_assertions, feature = "check"), should_panic)]
fn read_write_lock_conflict1() {
let val = RwLock::new(1);
thread::scope(|s| {
Expand All @@ -53,7 +53,7 @@ fn read_write_lock_conflict1() {
}

#[test]
#[should_panic]
#[cfg_attr(any(debug_assertions, feature = "check"), should_panic)]
fn read_write_lock_conflict2() {
let val = RwLock::new(1);
thread::scope(|s| {
Expand Down

0 comments on commit 6cc0b5b

Please sign in to comment.