Skip to content

Commit 71d4e77

Browse files
committed
std: Rewrite the sync module
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 `Condvar` implementation for an `RWLock` write lock has been removed. This may be added back in the future with a userspace implementation, but this commit is focused on exposing the system primitives first. * 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 now 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
1 parent 361baab commit 71d4e77

29 files changed

+2480
-3048
lines changed

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

-9
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/sync/spsc_queue.rs renamed to src/libstd/comm/spsc_queue.rs

+56-104
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/sync/barrier.rs

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use sync::{Mutex, Condvar};
12+
13+
/// A barrier enables multiple tasks to synchronize the beginning
14+
/// of some computation.
15+
///
16+
/// ```rust
17+
/// use std::sync::{Arc, Barrier};
18+
///
19+
/// let barrier = Arc::new(Barrier::new(10));
20+
/// for _ in range(0u, 10) {
21+
/// let c = barrier.clone();
22+
/// // The same messages will be printed together.
23+
/// // You will NOT see any interleaving.
24+
/// spawn(proc() {
25+
/// println!("before wait");
26+
/// c.wait();
27+
/// println!("after wait");
28+
/// });
29+
/// }
30+
/// ```
31+
pub struct Barrier {
32+
lock: Mutex<BarrierState>,
33+
cvar: Condvar,
34+
num_threads: uint,
35+
}
36+
37+
// The inner state of a double barrier
38+
struct BarrierState {
39+
count: uint,
40+
generation_id: uint,
41+
}
42+
43+
impl Barrier {
44+
/// Create a new barrier that can block a given number of threads.
45+
///
46+
/// A barrier will block `n`-1 threads which call `wait` and then wake up
47+
/// all threads at once when the `n`th thread calls `wait`.
48+
pub fn new(n: uint) -> Barrier {
49+
Barrier {
50+
lock: Mutex::new(BarrierState {
51+
count: 0,
52+
generation_id: 0,
53+
}),
54+
cvar: Condvar::new(),
55+
num_threads: n,
56+
}
57+
}
58+
59+
/// Block the current thread until all threads has rendezvoused here.
60+
///
61+
/// Barriers are re-usable after all threads have rendezvoused once, and can
62+
/// be used continuously.
63+
pub fn wait(&self) {
64+
let mut lock = self.lock.lock();
65+
let local_gen = lock.generation_id;
66+
lock.count += 1;
67+
if lock.count < self.num_threads {
68+
// We need a while loop to guard against spurious wakeups.
69+
// http://en.wikipedia.org/wiki/Spurious_wakeup
70+
while local_gen == lock.generation_id &&
71+
lock.count < self.num_threads {
72+
self.cvar.wait(&lock);
73+
}
74+
} else {
75+
lock.count = 0;
76+
lock.generation_id += 1;
77+
self.cvar.notify_all();
78+
}
79+
}
80+
}
81+
82+
#[cfg(test)]
83+
mod tests {
84+
use prelude::*;
85+
86+
use sync::{Arc, Barrier};
87+
use comm::Empty;
88+
89+
#[test]
90+
fn test_barrier() {
91+
let barrier = Arc::new(Barrier::new(10));
92+
let (tx, rx) = channel();
93+
94+
for _ in range(0u, 9) {
95+
let c = barrier.clone();
96+
let tx = tx.clone();
97+
spawn(proc() {
98+
c.wait();
99+
tx.send(true);
100+
});
101+
}
102+
103+
// At this point, all spawned tasks should be blocked,
104+
// so we shouldn't get anything from the port
105+
assert!(match rx.try_recv() {
106+
Err(Empty) => true,
107+
_ => false,
108+
});
109+
110+
barrier.wait();
111+
// Now, the barrier is cleared and we should get data.
112+
for _ in range(0u, 9) {
113+
rx.recv();
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)