Skip to content

Commit 08ce178

Browse files
committed
rollup merge of #19274: alexcrichton/rewrite-sync
This commit is a reimplementation of `std::sync` to be based on the system-provided primitives wherever possible. The previous implementation was fundamentally built on top of channels, and as part of the runtime reform it has become clear that this is not the level of abstraction that the standard level should be providing. This rewrite aims to provide as thin of a shim as possible on top of the system primitives in order to make them safe. The overall interface of the `std::sync` module has in general not changed, but there are a few important distinctions, highlighted below: * The condition variable type, `Condvar`, has been separated out of a `Mutex`. A condition variable is now an entirely separate type. This separation benefits users who only use one mutex, and provides a clearer distinction of who's responsible for managing condition variables (the application). * All of `Condvar`, `Mutex`, and `RWLock` are now directly built on top of system primitives rather than using a custom implementation. The `Once`, `Barrier`, and `Semaphore` types are still built upon these abstractions of the system primitives. * The `Condvar`, `Mutex`, and `RWLock` types all have a new static type and constant initializer corresponding to them. These are provided primarily for C FFI interoperation, but are often useful to otherwise simply have a global lock. The types, however, will leak memory unless `destroy()` is called on them, which is clearly documented. * The fundamental architecture of this design is to provide two separate layers. The first layer is that exposed by `sys_common` which is a cross-platform bare-metal abstraction of the system synchronization primitives. No attempt is made at making this layer safe, and it is quite unsafe to use! It is currently not exported as part of the API of the standard library, but the stabilization of the `sys` module will ensure that these will be exposed in time. The purpose of this layer is to provide the core cross-platform abstractions if necessary to implementors. The second layer is the layer provided by `std::sync` which is intended to be the thinnest possible layer on top of `sys_common` which is entirely safe to use. There are a few concerns which need to be addressed when making these system primitives safe: * Once used, the OS primitives can never be **moved**. This means that they essentially need to have a stable address. The static primitives use `&'static self` to enforce this, and the non-static primitives all use a `Box` to provide this guarantee. * Poisoning is leveraged to ensure that invalid data is not accessible from other tasks after one has panicked. In addition to these overall blanket safety limitations, each primitive has a few restrictions of its own: * Mutexes and rwlocks can only be unlocked from the same thread that they were locked by. This is achieved through RAII lock guards which cannot be sent across threads. * Mutexes and rwlocks can only be unlocked if they were previously locked. This is achieved by not exposing an unlocking method. * A condition variable can only be waited on with a locked mutex. This is achieved by requiring a `MutexGuard` in the `wait()` method. * A condition variable cannot be used concurrently with more than one mutex. This is guaranteed by dynamically binding a condition variable to precisely one mutex for its entire lifecycle. This restriction may be able to be relaxed in the future (a mutex is unbound when no threads are waiting on the condvar), but for now it is sufficient to guarantee safety. * Condvars support timeouts for their blocking operations. The implementation for these operations is provided by the system. Due to the modification of the `Condvar` API, removal of the `std::sync::mutex` API, and reimplementation, this is a breaking change. Most code should be fairly easy to port using the examples in the documentation of these primitives. [breaking-change] Closes #17094 Closes #18003
2 parents 4573da6 + c3adbd3 commit 08ce178

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2572
-3229
lines changed

src/etc/licenseck.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@
3838
"rt/isaac/randport.cpp", # public domain
3939
"rt/isaac/rand.h", # public domain
4040
"rt/isaac/standard.h", # public domain
41-
"libstd/sync/mpsc_queue.rs", # BSD
42-
"libstd/sync/spsc_queue.rs", # BSD
43-
"libstd/sync/mpmc_bounded_queue.rs", # BSD
41+
"libstd/comm/mpsc_queue.rs", # BSD
42+
"libstd/comm/spsc_queue.rs", # BSD
4443
"test/bench/shootout-binarytrees.rs", # BSD
4544
"test/bench/shootout-chameneos-redux.rs", # BSD
4645
"test/bench/shootout-fannkuch-redux.rs", # BSD

src/libstd/comm/mod.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ mod select;
354354
mod shared;
355355
mod stream;
356356
mod sync;
357+
mod mpsc_queue;
358+
mod spsc_queue;
357359

358360
/// The receiving-half of Rust's channel type. This half can only be owned by
359361
/// one task
@@ -628,24 +630,26 @@ impl<T: Send> Sender<T> {
628630
#[unstable]
629631
impl<T: Send> Clone for Sender<T> {
630632
fn clone(&self) -> Sender<T> {
631-
let (packet, sleeper) = match *unsafe { self.inner() } {
633+
let (packet, sleeper, guard) = match *unsafe { self.inner() } {
632634
Oneshot(ref p) => {
633635
let a = Arc::new(UnsafeCell::new(shared::Packet::new()));
634636
unsafe {
635-
(*a.get()).postinit_lock();
637+
let guard = (*a.get()).postinit_lock();
636638
match (*p.get()).upgrade(Receiver::new(Shared(a.clone()))) {
637-
oneshot::UpSuccess | oneshot::UpDisconnected => (a, None),
638-
oneshot::UpWoke(task) => (a, Some(task))
639+
oneshot::UpSuccess |
640+
oneshot::UpDisconnected => (a, None, guard),
641+
oneshot::UpWoke(task) => (a, Some(task), guard)
639642
}
640643
}
641644
}
642645
Stream(ref p) => {
643646
let a = Arc::new(UnsafeCell::new(shared::Packet::new()));
644647
unsafe {
645-
(*a.get()).postinit_lock();
648+
let guard = (*a.get()).postinit_lock();
646649
match (*p.get()).upgrade(Receiver::new(Shared(a.clone()))) {
647-
stream::UpSuccess | stream::UpDisconnected => (a, None),
648-
stream::UpWoke(task) => (a, Some(task)),
650+
stream::UpSuccess |
651+
stream::UpDisconnected => (a, None, guard),
652+
stream::UpWoke(task) => (a, Some(task), guard),
649653
}
650654
}
651655
}
@@ -657,7 +661,7 @@ impl<T: Send> Clone for Sender<T> {
657661
};
658662

659663
unsafe {
660-
(*packet.get()).inherit_blocker(sleeper);
664+
(*packet.get()).inherit_blocker(sleeper, guard);
661665

662666
let tmp = Sender::new(Shared(packet.clone()));
663667
mem::swap(self.inner_mut(), tmp.inner_mut());

src/libstd/sync/mpsc_queue.rs renamed to src/libstd/comm/mpsc_queue.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,6 @@ impl<T: Send> Queue<T> {
132132
if self.head.load(Acquire) == tail {Empty} else {Inconsistent}
133133
}
134134
}
135-
136-
/// Attempts to pop data from this queue, but doesn't attempt too hard. This
137-
/// will canonicalize inconsistent states to a `None` value.
138-
pub fn casual_pop(&self) -> Option<T> {
139-
match self.pop() {
140-
Data(t) => Some(t),
141-
Empty | Inconsistent => None,
142-
}
143-
}
144135
}
145136

146137
#[unsafe_destructor]

src/libstd/comm/shared.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ use alloc::boxed::Box;
2626
use core::cmp;
2727
use core::int;
2828
use rustrt::local::Local;
29-
use rustrt::mutex::NativeMutex;
3029
use rustrt::task::{Task, BlockedTask};
3130
use rustrt::thread::Thread;
3231

33-
use sync::atomic;
34-
use sync::mpsc_queue as mpsc;
32+
use sync::{atomic, Mutex, MutexGuard};
33+
use comm::mpsc_queue as mpsc;
3534

3635
const DISCONNECTED: int = int::MIN;
3736
const FUDGE: int = 1024;
@@ -56,7 +55,7 @@ pub struct Packet<T> {
5655

5756
// this lock protects various portions of this implementation during
5857
// select()
59-
select_lock: NativeMutex,
58+
select_lock: Mutex<()>,
6059
}
6160

6261
pub enum Failure {
@@ -76,7 +75,7 @@ impl<T: Send> Packet<T> {
7675
channels: atomic::AtomicInt::new(2),
7776
port_dropped: atomic::AtomicBool::new(false),
7877
sender_drain: atomic::AtomicInt::new(0),
79-
select_lock: unsafe { NativeMutex::new() },
78+
select_lock: Mutex::new(()),
8079
};
8180
return p;
8281
}
@@ -86,16 +85,18 @@ impl<T: Send> Packet<T> {
8685
// In other case mutex data will be duplicated while cloning
8786
// and that could cause problems on platforms where it is
8887
// represented by opaque data structure
89-
pub fn postinit_lock(&mut self) {
90-
unsafe { self.select_lock.lock_noguard() }
88+
pub fn postinit_lock(&self) -> MutexGuard<()> {
89+
self.select_lock.lock()
9190
}
9291

9392
// This function is used at the creation of a shared packet to inherit a
9493
// previously blocked task. This is done to prevent spurious wakeups of
9594
// tasks in select().
9695
//
9796
// This can only be called at channel-creation time
98-
pub fn inherit_blocker(&mut self, task: Option<BlockedTask>) {
97+
pub fn inherit_blocker(&mut self,
98+
task: Option<BlockedTask>,
99+
guard: MutexGuard<()>) {
99100
match task {
100101
Some(task) => {
101102
assert_eq!(self.cnt.load(atomic::SeqCst), 0);
@@ -135,7 +136,7 @@ impl<T: Send> Packet<T> {
135136
// interfere with this method. After we unlock this lock, we're
136137
// signifying that we're done modifying self.cnt and self.to_wake and
137138
// the port is ready for the world to continue using it.
138-
unsafe { self.select_lock.unlock_noguard() }
139+
drop(guard);
139140
}
140141

141142
pub fn send(&mut self, t: T) -> Result<(), T> {
@@ -441,7 +442,7 @@ impl<T: Send> Packet<T> {
441442
// done with. Without this bounce, we can race with inherit_blocker
442443
// about looking at and dealing with to_wake. Once we have acquired the
443444
// lock, we are guaranteed that inherit_blocker is done.
444-
unsafe {
445+
{
445446
let _guard = self.select_lock.lock();
446447
}
447448

src/libstd/sync/spsc_queue.rs renamed to src/libstd/comm/spsc_queue.rs

Lines changed: 56 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ use core::prelude::*;
4040
use alloc::boxed::Box;
4141
use core::mem;
4242
use core::cell::UnsafeCell;
43-
use alloc::arc::Arc;
4443

4544
use sync::atomic::{AtomicPtr, Relaxed, AtomicUint, Acquire, Release};
4645

@@ -74,39 +73,6 @@ pub struct Queue<T> {
7473
cache_subtractions: AtomicUint,
7574
}
7675

77-
/// A safe abstraction for the consumer in a single-producer single-consumer
78-
/// queue.
79-
pub struct Consumer<T> {
80-
inner: Arc<Queue<T>>
81-
}
82-
83-
impl<T: Send> Consumer<T> {
84-
/// Attempts to pop the value from the head of the queue, returning `None`
85-
/// if the queue is empty.
86-
pub fn pop(&mut self) -> Option<T> {
87-
self.inner.pop()
88-
}
89-
90-
/// Attempts to peek at the head of the queue, returning `None` if the queue
91-
/// is empty.
92-
pub fn peek<'a>(&'a mut self) -> Option<&'a mut T> {
93-
self.inner.peek()
94-
}
95-
}
96-
97-
/// A safe abstraction for the producer in a single-producer single-consumer
98-
/// queue.
99-
pub struct Producer<T> {
100-
inner: Arc<Queue<T>>
101-
}
102-
103-
impl<T: Send> Producer<T> {
104-
/// Pushes a new value onto the queue.
105-
pub fn push(&mut self, t: T) {
106-
self.inner.push(t)
107-
}
108-
}
109-
11076
impl<T: Send> Node<T> {
11177
fn new() -> *mut Node<T> {
11278
unsafe {
@@ -118,30 +84,6 @@ impl<T: Send> Node<T> {
11884
}
11985
}
12086

121-
/// Creates a new queue with a consumer-producer pair.
122-
///
123-
/// The producer returned is connected to the consumer to push all data to
124-
/// the consumer.
125-
///
126-
/// # Arguments
127-
///
128-
/// * `bound` - This queue implementation is implemented with a linked
129-
/// list, and this means that a push is always a malloc. In
130-
/// order to amortize this cost, an internal cache of nodes is
131-
/// maintained to prevent a malloc from always being
132-
/// necessary. This bound is the limit on the size of the
133-
/// cache (if desired). If the value is 0, then the cache has
134-
/// no bound. Otherwise, the cache will never grow larger than
135-
/// `bound` (although the queue itself could be much larger.
136-
pub fn queue<T: Send>(bound: uint) -> (Consumer<T>, Producer<T>) {
137-
let q = unsafe { Queue::new(bound) };
138-
let arc = Arc::new(q);
139-
let consumer = Consumer { inner: arc.clone() };
140-
let producer = Producer { inner: arc };
141-
142-
(consumer, producer)
143-
}
144-
14587
impl<T: Send> Queue<T> {
14688
/// Creates a new queue.
14789
///
@@ -296,78 +238,88 @@ impl<T: Send> Drop for Queue<T> {
296238
mod test {
297239
use prelude::*;
298240

299-
use super::{queue};
241+
use sync::Arc;
242+
use super::Queue;
300243

301244
#[test]
302245
fn smoke() {
303-
let (mut consumer, mut producer) = queue(0);
304-
producer.push(1i);
305-
producer.push(2);
306-
assert_eq!(consumer.pop(), Some(1i));
307-
assert_eq!(consumer.pop(), Some(2));
308-
assert_eq!(consumer.pop(), None);
309-
producer.push(3);
310-
producer.push(4);
311-
assert_eq!(consumer.pop(), Some(3));
312-
assert_eq!(consumer.pop(), Some(4));
313-
assert_eq!(consumer.pop(), None);
246+
unsafe {
247+
let queue = Queue::new(0);
248+
queue.push(1i);
249+
queue.push(2);
250+
assert_eq!(queue.pop(), Some(1i));
251+
assert_eq!(queue.pop(), Some(2));
252+
assert_eq!(queue.pop(), None);
253+
queue.push(3);
254+
queue.push(4);
255+
assert_eq!(queue.pop(), Some(3));
256+
assert_eq!(queue.pop(), Some(4));
257+
assert_eq!(queue.pop(), None);
258+
}
314259
}
315260

316261
#[test]
317262
fn peek() {
318-
let (mut consumer, mut producer) = queue(0);
319-
producer.push(vec![1i]);
263+
unsafe {
264+
let queue = Queue::new(0);
265+
queue.push(vec![1i]);
266+
267+
// Ensure the borrowchecker works
268+
match queue.peek() {
269+
Some(vec) => match vec.as_slice() {
270+
// Note that `pop` is not allowed here due to borrow
271+
[1] => {}
272+
_ => return
273+
},
274+
None => unreachable!()
275+
}
320276

321-
// Ensure the borrowchecker works
322-
match consumer.peek() {
323-
Some(vec) => match vec.as_slice() {
324-
// Note that `pop` is not allowed here due to borrow
325-
[1] => {}
326-
_ => return
327-
},
328-
None => unreachable!()
277+
queue.pop();
329278
}
330-
331-
consumer.pop();
332279
}
333280

334281
#[test]
335282
fn drop_full() {
336-
let (_, mut producer) = queue(0);
337-
producer.push(box 1i);
338-
producer.push(box 2i);
283+
unsafe {
284+
let q = Queue::new(0);
285+
q.push(box 1i);
286+
q.push(box 2i);
287+
}
339288
}
340289

341290
#[test]
342291
fn smoke_bound() {
343-
let (mut consumer, mut producer) = queue(1);
344-
producer.push(1i);
345-
producer.push(2);
346-
assert_eq!(consumer.pop(), Some(1));
347-
assert_eq!(consumer.pop(), Some(2));
348-
assert_eq!(consumer.pop(), None);
349-
producer.push(3);
350-
producer.push(4);
351-
assert_eq!(consumer.pop(), Some(3));
352-
assert_eq!(consumer.pop(), Some(4));
353-
assert_eq!(consumer.pop(), None);
292+
unsafe {
293+
let q = Queue::new(0);
294+
q.push(1i);
295+
q.push(2);
296+
assert_eq!(q.pop(), Some(1));
297+
assert_eq!(q.pop(), Some(2));
298+
assert_eq!(q.pop(), None);
299+
q.push(3);
300+
q.push(4);
301+
assert_eq!(q.pop(), Some(3));
302+
assert_eq!(q.pop(), Some(4));
303+
assert_eq!(q.pop(), None);
304+
}
354305
}
355306

356307
#[test]
357308
fn stress() {
358-
stress_bound(0);
359-
stress_bound(1);
309+
unsafe {
310+
stress_bound(0);
311+
stress_bound(1);
312+
}
360313

361-
fn stress_bound(bound: uint) {
362-
let (consumer, mut producer) = queue(bound);
314+
unsafe fn stress_bound(bound: uint) {
315+
let q = Arc::new(Queue::new(bound));
363316

364317
let (tx, rx) = channel();
318+
let q2 = q.clone();
365319
spawn(proc() {
366-
// Move the consumer to a local mutable slot
367-
let mut consumer = consumer;
368320
for _ in range(0u, 100000) {
369321
loop {
370-
match consumer.pop() {
322+
match q2.pop() {
371323
Some(1i) => break,
372324
Some(_) => panic!(),
373325
None => {}
@@ -377,7 +329,7 @@ mod test {
377329
tx.send(());
378330
});
379331
for _ in range(0i, 100000) {
380-
producer.push(1);
332+
q.push(1);
381333
}
382334
rx.recv();
383335
}

src/libstd/comm/stream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use rustrt::task::{Task, BlockedTask};
3232
use rustrt::thread::Thread;
3333

3434
use sync::atomic;
35-
use sync::spsc_queue as spsc;
35+
use comm::spsc_queue as spsc;
3636
use comm::Receiver;
3737

3838
const DISCONNECTED: int = int::MIN;

src/libstd/dynamic_lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ pub mod dl {
225225
}
226226

227227
pub fn check_for_errors_in<T>(f: || -> T) -> Result<T, String> {
228-
use rustrt::mutex::{StaticNativeMutex, NATIVE_MUTEX_INIT};
229-
static LOCK: StaticNativeMutex = NATIVE_MUTEX_INIT;
228+
use sync::{StaticMutex, MUTEX_INIT};
229+
static LOCK: StaticMutex = MUTEX_INIT;
230230
unsafe {
231231
// dlerror isn't thread safe, so we need to lock around this entire
232232
// sequence

0 commit comments

Comments
 (0)