1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#[cfg(feature = "barging")]
pub use guard::{Guard, GuardDeref, GuardDerefMut};
#[cfg(feature = "barging")]
mod guard {
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut};
use loom::cell::{ConstPtr, MutPtr, UnsafeCell};
/// A trait for guard types that protect the access to the underlying data
/// behind Loom's [`UnsafeCell`].
///
/// # Safety
///
/// Must guarantee that an instance of the guard is the only access point
/// to the underlying data through all its lifetime.
pub unsafe trait Guard: Sized {
/// The target type after dereferencing [`GuardDeref`] or [`GuardDerefMut`].
type Target: ?Sized;
/// Returns a shared reference to the underlying [`UnsafeCell`].
fn get(&self) -> &UnsafeCell<Self::Target>;
/// Get a Loom immutable pointer bounded by this guard lifetime.
fn get_ref(&self) -> GuardDeref<'_, Self> {
GuardDeref::new(self)
}
/// Get a Loom mutable pointer bounded by this guard lifetime.
fn get_mut(&mut self) -> GuardDerefMut<'_, Self> {
GuardDerefMut::new(self)
}
}
/// A Loom immutable pointer borrowed from a guard instance.
pub struct GuardDeref<'a, G: Guard> {
ptr: ConstPtr<G::Target>,
marker: PhantomData<(&'a G::Target, &'a G)>,
}
impl<G: Guard> GuardDeref<'_, G> {
fn new(guard: &G) -> Self {
let ptr = guard.get().get();
Self { ptr, marker: PhantomData }
}
}
impl<G: Guard> Deref for GuardDeref<'_, G> {
type Target = G::Target;
fn deref(&self) -> &Self::Target {
// SAFETY: Our lifetime is bounded by the guard borrow.
unsafe { self.ptr.deref() }
}
}
/// A Loom mutable pointer borrowed from a guard instance.
pub struct GuardDerefMut<'a, G: Guard> {
ptr: MutPtr<G::Target>,
marker: PhantomData<(&'a mut G::Target, &'a mut G)>,
}
impl<G: Guard> GuardDerefMut<'_, G> {
#[allow(clippy::needless_pass_by_ref_mut)]
fn new(guard: &mut G) -> Self {
let ptr = guard.get().get_mut();
Self { ptr, marker: PhantomData }
}
}
impl<G: Guard> Deref for GuardDerefMut<'_, G> {
type Target = G::Target;
fn deref(&self) -> &Self::Target {
// SAFETY: Our lifetime is bounded by the guard borrow.
unsafe { self.ptr.deref() }
}
}
impl<G: Guard> DerefMut for GuardDerefMut<'_, G> {
fn deref_mut(&mut self) -> &mut Self::Target {
// SAFETY: Our lifetime is bounded by the guard borrow.
unsafe { self.ptr.deref() }
}
}
}
pub mod models {
use core::array;
use loom::sync::Arc;
use loom::{model, thread};
use crate::test::{get, inc, try_inc, Int};
use crate::test::{LockThen, TryLockThen};
/// Get a copy of the shared integer, converting it to usize.
///
/// Panics if the cast fails.
fn get_unwrap<L>(lock: &Arc<L>) -> usize
where
L: LockThen<Target = Int>,
{
get(lock).try_into().unwrap()
}
// TODO: Three or more threads make lock models run for too long. It would
// be nice to run a lock model with at least three threads because that
// would cover a queue with head, tail and at least one more queue node
// instead of a queue with just head and tail.
const LOCKS: usize = 2;
const TRY_LOCKS: usize = 3;
/// Evaluates that concurrent `try_lock` calls will serialize all mutations
/// against the shared data, therefore no data races.
pub fn try_lock_join<L>()
where
L: TryLockThen<Target = Int> + 'static,
{
model(|| {
const RUNS: usize = TRY_LOCKS;
let lock = Arc::new(L::new(0));
let handles: [_; RUNS] = array::from_fn(|_| {
let lock = Arc::clone(&lock);
thread::spawn(move || try_inc(&lock))
});
for handle in handles {
handle.join().unwrap();
}
let value = get_unwrap(&lock);
assert!((1..=RUNS).contains(&value));
});
}
/// Evaluates that concurrent `lock` calls will serialize all mutations
/// against the shared data, therefore no data races.
pub fn lock_join<L>()
where
L: LockThen<Target = Int> + 'static,
{
model(|| {
const RUNS: usize = LOCKS;
let lock = Arc::new(L::new(0));
let handles: [_; RUNS] = array::from_fn(|_| {
let lock = Arc::clone(&lock);
thread::spawn(move || inc(&lock))
});
for handle in handles {
handle.join().unwrap();
}
let value = get_unwrap(&lock);
assert_eq!(RUNS, value);
});
}
/// Evaluates that concurrent `lock` and `try_lock` calls will serialize
/// all mutations against the shared data, therefore no data races.
pub fn mixed_lock_join<L>()
where
L: TryLockThen<Target = Int> + 'static,
{
model(|| {
const RUNS: usize = LOCKS;
let lock = Arc::new(L::new(0));
let handles: [_; RUNS] = array::from_fn(|run| {
let lock = Arc::clone(&lock);
let f = if run % 2 == 0 { inc } else { try_inc };
thread::spawn(move || f(&lock))
});
for handle in handles {
handle.join().unwrap();
}
let value = get_unwrap(&lock);
assert!((1..=RUNS).contains(&value));
});
}
}