Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for lock_api #161

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ categories = ["concurrency", "data-structures"]
default = []
checkpoint = ["serde", "serde_json"]
futures = ["futures-util"]
lock_api = ["lock_api_", "once_cell"]

[dependencies]
cfg-if = "0.1.6"
Expand All @@ -38,3 +39,10 @@ serde = { version = "1.0.92", features = ["derive"], optional = true }
serde_json = { version = "1.0.33", optional = true }

futures-util = { version = "0.3.0", optional = true }

# Provides locking primitives for the `lock_api` crate
lock_api_ = { package = "lock_api", version = "0.4.1", optional = true }
once_cell = { version = "1.4.0", optional = true }

[package.metadata.docs.rs]
features = ["lock_api"]
4 changes: 4 additions & 0 deletions ci/azure-test-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ jobs:
# Test with futures feature
- script: cargo test --features futures
displayName: cargo test --features futures

# Test with lock_api feature
- script: cargo test --features lock_api
displayName: cargo test --features lock_api
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ if_futures! {
pub mod future;
}

#[cfg(feature = "lock_api")]
pub mod lock_api;

#[doc(hidden)]
pub fn __debug_enabled() -> bool {
rt::execution(|e| e.log)
Expand Down
123 changes: 123 additions & 0 deletions src/lock_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//! Mock implementation of the `lock_api` and `parking_lot` crates.
//!
//! _These types are only available if loom is built with the `"lock_api"`
//! feature_

use crate::rt;
use lock_api_ as lock_api;
use once_cell::sync::OnceCell;

/// Mock implementation of `lock_api::RawMutex`
#[allow(missing_debug_implementations)]
pub struct RawMutex {
object: OnceCell<rt::Mutex>,
}

impl RawMutex {
// Unfortunately, we're required to have a `const INIT` in order to
// implement `lock_api::RawMutex`, so we need to lazily create the actual
// `rt::Mutex`.
fn object(&self) -> &rt::Mutex {
self.object.get_or_init(|| rt::Mutex::new(true))
}
Comment on lines +17 to +22
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't think of a cleaner way to satisfy this requirement, so I threw in a OnceCell.

}

unsafe impl lock_api::RawMutex for RawMutex {
const INIT: RawMutex = RawMutex {
object: OnceCell::new(),
};

type GuardMarker = lock_api::GuardNoSend;

fn lock(&self) {
self.object().acquire_lock();
}

fn try_lock(&self) -> bool {
self.object().try_acquire_lock()
}

unsafe fn unlock(&self) {
self.object().release_lock()
}

fn is_locked(&self) -> bool {
self.object().is_locked()
}
}

/// Mock implementation of `lock_api::RawRwLock`
#[allow(missing_debug_implementations)]
pub struct RawRwLock {
object: OnceCell<rt::RwLock>,
}

impl RawRwLock {
// Unfortunately we're required to have a `const INIT` in order to implement
// `lock_api::RawRwLock`, so we need to lazily create the actual
// `rt::RwLock`.
fn object(&self) -> &rt::RwLock {
self.object.get_or_init(|| rt::RwLock::new())
}
}

unsafe impl lock_api::RawRwLock for RawRwLock {
const INIT: RawRwLock = RawRwLock {
object: OnceCell::new(),
};

type GuardMarker = lock_api::GuardNoSend;

fn lock_shared(&self) {
self.object().acquire_read_lock()
}

fn try_lock_shared(&self) -> bool {
self.object().try_acquire_read_lock()
}

unsafe fn unlock_shared(&self) {
self.object().release_read_lock()
}

fn lock_exclusive(&self) {
self.object().acquire_write_lock()
}

fn try_lock_exclusive(&self) -> bool {
self.object().try_acquire_write_lock()
}

unsafe fn unlock_exclusive(&self) {
self.object().release_write_lock()
}

fn is_locked(&self) -> bool {
let object = self.object();
object.is_read_locked() || object.is_write_locked()
}
}

/// Mock implementation of `lock_api::Mutex`
pub type Mutex<T> = lock_api::Mutex<RawMutex, T>;

/// Mock implementation of `lock_api::MutexGuard`
pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, RawMutex, T>;

/// Mock implementation of `lock_api::MappedMutexGuard`
pub type MappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawMutex, T>;

/// Mock implementation of `lock_api::RwLock`
pub type RwLock<T> = lock_api::RwLock<RawRwLock, T>;

/// Mock implementation of `lock_api::RwLockReadGuard`
pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawRwLock, T>;

/// Mock implementation of `lock_api::RwLockWriteGuard`
pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawRwLock, T>;

/// Mock implementation of `lock_api::MappedRwLockReadGuard`
pub type MappedRwLockReadGuard<'a, T> = lock_api::MappedRwLockReadGuard<'a, RawRwLock, T>;

/// Mock implementation of `lock_api::MappedRwLockWriteGuard`
pub type MappedRwLockWriteGuard<'a, T> = lock_api::MappedRwLockWriteGuard<'a, RawRwLock, T>;
2 changes: 1 addition & 1 deletion src/rt/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl Mutex {
}

/// Returns `true` if the mutex is currently locked
fn is_locked(&self) -> bool {
pub(crate) fn is_locked(&self) -> bool {
super::execution(|execution| self.state.get(&execution.objects).lock.is_some())
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/rt/rwlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl RwLock {
}

/// Returns `true` if RwLock is read locked
fn is_read_locked(&self) -> bool {
pub(crate) fn is_read_locked(&self) -> bool {
super::execution(
|execution| match self.state.get(&mut execution.objects).lock {
Some(Locked::Read(_)) => true,
Expand All @@ -163,7 +163,7 @@ impl RwLock {
}

/// Returns `true` if RwLock is write locked.
fn is_write_locked(&self) -> bool {
pub(crate) fn is_write_locked(&self) -> bool {
super::execution(
|execution| match self.state.get(&mut execution.objects).lock {
Some(Locked::Write(_)) => true,
Expand Down
144 changes: 144 additions & 0 deletions tests/lock_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#![cfg(feature = "lock_api")]
#![deny(warnings, rust_2018_idioms)]

use loom::cell::UnsafeCell;
use loom::lock_api::{Mutex, RwLock};
use loom::sync::atomic::AtomicUsize;
use loom::thread;

use std::rc::Rc;
use std::sync::atomic::Ordering::SeqCst;
use std::sync::Arc;

#[test]
fn mutex_enforces_mutal_exclusion() {
loom::model(|| {
let data = Rc::new((Mutex::new(0), AtomicUsize::new(0)));

let ths: Vec<_> = (0..2)
.map(|_| {
let data = data.clone();

thread::spawn(move || {
let mut locked = data.0.lock();

let prev = data.1.fetch_add(1, SeqCst);
assert_eq!(prev, *locked);
*locked += 1;
})
})
.collect();

for th in ths {
th.join().unwrap();
}

let locked = data.0.lock();

assert_eq!(*locked, data.1.load(SeqCst));
});
}

#[test]
fn mutex_establishes_seq_cst() {
loom::model(|| {
struct Data {
cell: UnsafeCell<usize>,
flag: Mutex<bool>,
}

let data = Rc::new(Data {
cell: UnsafeCell::new(0),
flag: Mutex::new(false),
});

{
let data = data.clone();

thread::spawn(move || {
unsafe { data.cell.with_mut(|v| *v = 1) };
*data.flag.lock() = true;
});
}

let flag = *data.flag.lock();

if flag {
let v = unsafe { data.cell.with(|v| *v) };
assert_eq!(v, 1);
}
});
}

#[test]
fn rwlock_read_one() {
loom::model(|| {
let lock = Arc::new(RwLock::new(1));
let c_lock = lock.clone();

let n = lock.read();
assert_eq!(*n, 1);

thread::spawn(move || {
let _l = c_lock.read();
})
.join()
.unwrap();
});
}

#[test]
fn rwlock_read_two_write_one() {
loom::model(|| {
let lock = Arc::new(RwLock::new(1));

for _ in 0..2 {
let lock = lock.clone();

thread::spawn(move || {
let _l = lock.read();

thread::yield_now();
});
}

let _l = lock.write();
thread::yield_now();
});
}

#[test]
fn rwlock_try_read() {
loom::model(|| {
let lock = RwLock::new(1);

match lock.try_read() {
Some(n) => assert_eq!(*n, 1),
None => unreachable!(),
};
});
}

#[test]
fn rwlock_write() {
loom::model(|| {
let lock = RwLock::new(1);

let mut n = lock.write();
*n = 2;

assert!(lock.try_read().is_none());
});
}

#[test]
fn rwlock_try_write() {
loom::model(|| {
let lock = RwLock::new(1);

let n = lock.read();
assert_eq!(*n, 1);

assert!(lock.try_write().is_none());
});
}