Skip to content

Commit dcdac00

Browse files
committed
Make {Mutex, Condvar, RwLock}::new const
These constructors have been const since Rust 1.63 (rust-lang/rust#93740). It's pretty easy for us to make them const too, which allows code that relies on them being const to correctly compile with Shuttle. The one exception is that HashMap::new isn't const, and our Condvar implementation uses a HashMap to track waiters. I took the easy way out and just used a vector as an association list instead -- we shouldn't expect large numbers of waiters on the same condvar, so this shouldn't be too inefficient.
1 parent b9205aa commit dcdac00

File tree

6 files changed

+45
-44
lines changed

6 files changed

+45
-44
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ keywords = ["concurrency", "lock", "thread", "async"]
99
categories = ["asynchronous", "concurrency", "development-tools::testing"]
1010

1111
[dependencies]
12+
assoc = "0.1.3"
1213
bitvec = "1.0.1"
1314
generator = "0.7.1"
1415
hex = "0.4.2"
@@ -17,7 +18,7 @@ rand_core = "0.6.4"
1718
rand = "0.8.5"
1819
rand_pcg = "0.3.1"
1920
scoped-tls = "1.0.0"
20-
smallvec = "1.6.1"
21+
smallvec = { version = "1.10.0", features = ["const_new"] }
2122
tracing = { version = "0.1.21", default-features = false, features = ["std"] }
2223

2324
[dev-dependencies]

src/runtime/task/clock.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ pub struct VectorClock {
88
}
99

1010
impl VectorClock {
11-
pub(crate) fn new() -> Self {
12-
Self { time: SmallVec::new() }
11+
pub(crate) const fn new() -> Self {
12+
Self {
13+
time: SmallVec::new_const(),
14+
}
1315
}
1416

1517
#[cfg(test)]

src/runtime/task/mod.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,8 @@ pub(crate) struct TaskSet {
427427
}
428428

429429
impl TaskSet {
430-
pub fn new() -> Self {
431-
Self {
432-
tasks: BitVec::from_bitslice(bits![0; DEFAULT_INLINE_TASKS]),
433-
}
430+
pub const fn new() -> Self {
431+
Self { tasks: BitVec::EMPTY }
434432
}
435433

436434
pub fn contains(&self, tid: TaskId) -> bool {
@@ -446,7 +444,7 @@ impl TaskSet {
446444
/// the set did have this value present, `false` is returned.
447445
pub fn insert(&mut self, tid: TaskId) -> bool {
448446
if tid.0 >= self.tasks.len() {
449-
self.tasks.resize(1 + tid.0, false);
447+
self.tasks.resize(DEFAULT_INLINE_TASKS.max(1 + tid.0), false);
450448
}
451449
!std::mem::replace(&mut *self.tasks.get_mut(tid.0).unwrap(), true)
452450
}

src/sync/condvar.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use crate::runtime::task::clock::VectorClock;
44
use crate::runtime::task::TaskId;
55
use crate::runtime::thread;
66
use crate::sync::MutexGuard;
7+
use assoc::AssocExt;
78
use std::cell::RefCell;
8-
use std::collections::{HashMap, VecDeque};
9-
use std::rc::Rc;
9+
use std::collections::VecDeque;
1010
use std::sync::{LockResult, PoisonError};
1111
use std::time::Duration;
1212
use tracing::trace;
@@ -15,12 +15,13 @@ use tracing::trace;
1515
/// waiting for an event to occur.
1616
#[derive(Debug)]
1717
pub struct Condvar {
18-
state: Rc<RefCell<CondvarState>>,
18+
state: RefCell<CondvarState>,
1919
}
2020

2121
#[derive(Debug)]
2222
struct CondvarState {
23-
waiters: HashMap<TaskId, CondvarWaitStatus>,
23+
// TODO: this should be a HashMap but [HashMap::new] is not const
24+
waiters: Vec<(TaskId, CondvarWaitStatus)>,
2425
next_epoch: usize,
2526
}
2627

@@ -114,14 +115,14 @@ enum CondvarWaitStatus {
114115
// and can run in any order (because they are all contending on the same mutex).
115116
impl Condvar {
116117
/// Creates a new condition variable which is ready to be waited on and notified.
117-
pub fn new() -> Self {
118+
pub const fn new() -> Self {
118119
let state = CondvarState {
119-
waiters: HashMap::new(),
120+
waiters: Vec::new(),
120121
next_epoch: 0,
121122
};
122123

123124
Self {
124-
state: Rc::new(RefCell::new(state)),
125+
state: RefCell::new(state),
125126
}
126127
}
127128

@@ -133,7 +134,8 @@ impl Condvar {
133134

134135
trace!(waiters=?state.waiters, next_epoch=state.next_epoch, "waiting on condvar {:p}", self);
135136

136-
assert!(state.waiters.insert(me, CondvarWaitStatus::Waiting).is_none());
137+
debug_assert!(<_ as AssocExt<_, _>>::get(&state.waiters, &me).is_none());
138+
state.waiters.push((me, CondvarWaitStatus::Waiting));
137139
// TODO: Condvar::wait should allow for spurious wakeups.
138140
ExecutionState::with(|s| s.current_mut().block(false));
139141
drop(state);
@@ -144,7 +146,7 @@ impl Condvar {
144146
// After the context switch, consume whichever signal that woke this thread
145147
let mut state = self.state.borrow_mut();
146148
trace!(waiters=?state.waiters, next_epoch=state.next_epoch, "woken from condvar {:p}", self);
147-
let my_status = state.waiters.remove(&me).expect("should be waiting");
149+
let my_status = <_ as AssocExt<_, _>>::remove(&mut state.waiters, &me).expect("should be waiting");
148150
match my_status {
149151
CondvarWaitStatus::Broadcast(clock) => {
150152
// Woken by a broadcast, so nothing to do except update the clock

src/sync/mutex.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ use std::cell::RefCell;
66
use std::fmt::{Debug, Display};
77
use std::ops::{Deref, DerefMut};
88
use std::panic::{RefUnwindSafe, UnwindSafe};
9-
use std::rc::Rc;
109
use std::sync::{LockResult, PoisonError, TryLockError, TryLockResult};
1110
use tracing::trace;
1211

1312
/// A mutex, the same as [`std::sync::Mutex`].
1413
pub struct Mutex<T: ?Sized> {
15-
state: Rc<RefCell<MutexState>>,
14+
state: RefCell<MutexState>,
1615
inner: std::sync::Mutex<T>,
1716
}
1817

@@ -31,7 +30,7 @@ struct MutexState {
3130

3231
impl<T> Mutex<T> {
3332
/// Creates a new mutex in an unlocked state ready for use.
34-
pub fn new(value: T) -> Self {
33+
pub const fn new(value: T) -> Self {
3534
let state = MutexState {
3635
holder: None,
3736
waiters: TaskSet::new(),
@@ -40,7 +39,7 @@ impl<T> Mutex<T> {
4039

4140
Self {
4241
inner: std::sync::Mutex::new(value),
43-
state: Rc::new(RefCell::new(state)),
42+
state: RefCell::new(state),
4443
}
4544
}
4645
}

src/sync/rwlock.rs

+22-23
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::cell::RefCell;
66
use std::fmt::{Debug, Display};
77
use std::ops::{Deref, DerefMut};
88
use std::panic::{RefUnwindSafe, UnwindSafe};
9-
use std::rc::Rc;
109
use std::sync::{LockResult, PoisonError, TryLockError, TryLockResult};
1110
use tracing::trace;
1211

@@ -16,7 +15,7 @@ use tracing::trace;
1615
/// `RwLock` more than once. The `std` version is ambiguous about what behavior is allowed here, so
1716
/// we choose the most conservative one.
1817
pub struct RwLock<T: ?Sized> {
19-
state: Rc<RefCell<RwLockState>>,
18+
state: RefCell<RwLockState>,
2019
inner: std::sync::RwLock<T>,
2120
}
2221

@@ -43,7 +42,7 @@ enum RwLockType {
4342

4443
impl<T> RwLock<T> {
4544
/// Create a new instance of an `RwLock<T>` which is unlocked.
46-
pub fn new(value: T) -> Self {
45+
pub const fn new(value: T) -> Self {
4746
let state = RwLockState {
4847
holder: RwLockHolder::None,
4948
waiting_readers: TaskSet::new(),
@@ -53,7 +52,7 @@ impl<T> RwLock<T> {
5352

5453
Self {
5554
inner: std::sync::RwLock::new(value),
56-
state: Rc::new(RefCell::new(state)),
55+
state: RefCell::new(state),
5756
}
5857
}
5958
}
@@ -67,12 +66,12 @@ impl<T: ?Sized> RwLock<T> {
6766
match self.inner.try_read() {
6867
Ok(guard) => Ok(RwLockReadGuard {
6968
inner: Some(guard),
70-
state: Rc::clone(&self.state),
69+
rwlock: self,
7170
me: ExecutionState::me(),
7271
}),
7372
Err(TryLockError::Poisoned(err)) => Err(PoisonError::new(RwLockReadGuard {
7473
inner: Some(err.into_inner()),
75-
state: Rc::clone(&self.state),
74+
rwlock: self,
7675
me: ExecutionState::me(),
7776
})),
7877
Err(TryLockError::WouldBlock) => panic!("rwlock state out of sync"),
@@ -87,12 +86,12 @@ impl<T: ?Sized> RwLock<T> {
8786
match self.inner.try_write() {
8887
Ok(guard) => Ok(RwLockWriteGuard {
8988
inner: Some(guard),
90-
state: Rc::clone(&self.state),
89+
rwlock: self,
9190
me: ExecutionState::me(),
9291
}),
9392
Err(TryLockError::Poisoned(err)) => Err(PoisonError::new(RwLockWriteGuard {
9493
inner: Some(err.into_inner()),
95-
state: Rc::clone(&self.state),
94+
rwlock: self,
9695
me: ExecutionState::me(),
9796
})),
9897
Err(TryLockError::WouldBlock) => panic!("rwlock state out of sync"),
@@ -111,12 +110,12 @@ impl<T: ?Sized> RwLock<T> {
111110
match self.inner.try_read() {
112111
Ok(guard) => Ok(RwLockReadGuard {
113112
inner: Some(guard),
114-
state: Rc::clone(&self.state),
113+
rwlock: self,
115114
me: ExecutionState::me(),
116115
}),
117116
Err(TryLockError::Poisoned(err)) => Err(TryLockError::Poisoned(PoisonError::new(RwLockReadGuard {
118117
inner: Some(err.into_inner()),
119-
state: Rc::clone(&self.state),
118+
rwlock: self,
120119
me: ExecutionState::me(),
121120
}))),
122121
Err(TryLockError::WouldBlock) => panic!("rwlock state out of sync"),
@@ -135,12 +134,12 @@ impl<T: ?Sized> RwLock<T> {
135134
match self.inner.try_write() {
136135
Ok(guard) => Ok(RwLockWriteGuard {
137136
inner: Some(guard),
138-
state: Rc::clone(&self.state),
137+
rwlock: self,
139138
me: ExecutionState::me(),
140139
}),
141140
Err(TryLockError::Poisoned(err)) => Err(TryLockError::Poisoned(PoisonError::new(RwLockWriteGuard {
142141
inner: Some(err.into_inner()),
143-
state: Rc::clone(&self.state),
142+
rwlock: self,
144143
me: ExecutionState::me(),
145144
}))),
146145
Err(TryLockError::WouldBlock) => panic!("rwlock state out of sync"),
@@ -175,7 +174,7 @@ impl<T: ?Sized> RwLock<T> {
175174
waiting_writers = ?state.waiting_writers,
176175
"acquiring {:?} lock on rwlock {:p}",
177176
typ,
178-
self.state,
177+
self,
179178
);
180179

181180
// We are waiting for the lock
@@ -250,7 +249,7 @@ impl<T: ?Sized> RwLock<T> {
250249
waiting_writers = ?state.waiting_writers,
251250
"acquired {:?} lock on rwlock {:p}",
252251
typ,
253-
self.state
252+
self
254253
);
255254

256255
// Increment the current thread's clock and update this RwLock's clock to match.
@@ -283,7 +282,7 @@ impl<T: ?Sized> RwLock<T> {
283282
waiting_writers = ?state.waiting_writers,
284283
"trying to acquire {:?} lock on rwlock {:p}",
285284
typ,
286-
self.state,
285+
self,
287286
);
288287

289288
let acquired = match (typ, &mut state.holder) {
@@ -309,7 +308,7 @@ impl<T: ?Sized> RwLock<T> {
309308
"{} {:?} lock on rwlock {:p}",
310309
if acquired { "acquired" } else { "failed to acquire" },
311310
typ,
312-
self.state,
311+
self,
313312
);
314313

315314
// Update this thread's clock with the clock stored in the RwLock.
@@ -403,9 +402,9 @@ impl<T: ?Sized + Debug> Debug for RwLock<T> {
403402

404403
/// RAII structure used to release the shared read access of a `RwLock` when dropped.
405404
pub struct RwLockReadGuard<'a, T: ?Sized> {
406-
state: Rc<RefCell<RwLockState>>,
407-
me: TaskId,
408405
inner: Option<std::sync::RwLockReadGuard<'a, T>>,
406+
rwlock: &'a RwLock<T>,
407+
me: TaskId,
409408
}
410409

411410
impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {
@@ -432,14 +431,14 @@ impl<T: ?Sized> Drop for RwLockReadGuard<'_, T> {
432431
fn drop(&mut self) {
433432
self.inner = None;
434433

435-
let mut state = self.state.borrow_mut();
434+
let mut state = self.rwlock.state.borrow_mut();
436435

437436
trace!(
438437
holder = ?state.holder,
439438
waiting_readers = ?state.waiting_readers,
440439
waiting_writers = ?state.waiting_writers,
441440
"releasing Read lock on rwlock {:p}",
442-
self.state
441+
self.rwlock
443442
);
444443

445444
match &mut state.holder {
@@ -471,7 +470,7 @@ impl<T: ?Sized> Drop for RwLockReadGuard<'_, T> {
471470
/// RAII structure used to release the exclusive write access of a `RwLock` when dropped.
472471
pub struct RwLockWriteGuard<'a, T: ?Sized> {
473472
inner: Option<std::sync::RwLockWriteGuard<'a, T>>,
474-
state: Rc<RefCell<RwLockState>>,
473+
rwlock: &'a RwLock<T>,
475474
me: TaskId,
476475
}
477476

@@ -505,13 +504,13 @@ impl<T: ?Sized> Drop for RwLockWriteGuard<'_, T> {
505504
fn drop(&mut self) {
506505
self.inner = None;
507506

508-
let mut state = self.state.borrow_mut();
507+
let mut state = self.rwlock.state.borrow_mut();
509508
trace!(
510509
holder = ?state.holder,
511510
waiting_readers = ?state.waiting_readers,
512511
waiting_writers = ?state.waiting_writers,
513512
"releasing Write lock on rwlock {:p}",
514-
self.state
513+
self.rwlock
515514
);
516515

517516
assert_eq!(state.holder, RwLockHolder::Write(self.me));

0 commit comments

Comments
 (0)