Skip to content

Commit d42521a

Browse files
committed
auto merge of rust-lang#11866 : alexcrichton/rust/atomic-u64, r=brson
Let's try this again. This is an implementation of mutexes which I believe is free from undefined behavior of OS mutexes (the pitfall of the previous implementation). This implementation is not ideal. There's a yield-loop spot, and it's not particularly fair with respect to lockers who steal without going through the normal code paths. That being said, I believe that this is a correct implementation which is a stepping stone to move from. I haven't done rigorous benchmarking of this mutex, but preliminary results show that it's about 25% slower in the uncontended case on linux (same runtime on OSX), and it's actually faster than a pthreads mutex on high contention (again, not rigorous benchmarking, I just saw these numbers come up).
2 parents cb40eba + acacfb2 commit d42521a

File tree

28 files changed

+1589
-495
lines changed

28 files changed

+1589
-495
lines changed

src/etc/licenseck.py

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"libstd/sync/mpsc_queue.rs", # BSD
4242
"libstd/sync/spsc_queue.rs", # BSD
4343
"libstd/sync/mpmc_bounded_queue.rs", # BSD
44+
"libextra/sync/mpsc_intrusive.rs", # BSD
4445
]
4546

4647
def check_license(name, contents):

src/libextra/sync.rs renamed to src/libextra/sync/mod.rs

+28-20
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
* in std.
1818
*/
1919

20-
20+
use std::cast;
2121
use std::comm;
22-
use std::unstable::sync::Exclusive;
2322
use std::sync::arc::UnsafeArc;
2423
use std::sync::atomics;
2524
use std::unstable::finally::Finally;
@@ -32,6 +31,10 @@ use arc::MutexArc;
3231
* Internals
3332
****************************************************************************/
3433

34+
pub mod mutex;
35+
pub mod one;
36+
mod mpsc_intrusive;
37+
3538
// Each waiting task receives on one of these.
3639
#[doc(hidden)]
3740
type WaitEnd = Port<()>;
@@ -54,7 +57,7 @@ impl WaitQueue {
5457
comm::Data(ch) => {
5558
// Send a wakeup signal. If the waiter was killed, its port will
5659
// have closed. Keep trying until we get a live task.
57-
if ch.try_send_deferred(()) {
60+
if ch.try_send(()) {
5861
true
5962
} else {
6063
self.signal()
@@ -69,7 +72,7 @@ impl WaitQueue {
6972
loop {
7073
match self.head.try_recv() {
7174
comm::Data(ch) => {
72-
if ch.try_send_deferred(()) {
75+
if ch.try_send(()) {
7376
count += 1;
7477
}
7578
}
@@ -81,36 +84,45 @@ impl WaitQueue {
8184

8285
fn wait_end(&self) -> WaitEnd {
8386
let (wait_end, signal_end) = Chan::new();
84-
assert!(self.tail.try_send_deferred(signal_end));
87+
assert!(self.tail.try_send(signal_end));
8588
wait_end
8689
}
8790
}
8891

8992
// The building-block used to make semaphores, mutexes, and rwlocks.
90-
#[doc(hidden)]
9193
struct SemInner<Q> {
94+
lock: mutex::Mutex,
9295
count: int,
9396
waiters: WaitQueue,
9497
// Can be either unit or another waitqueue. Some sems shouldn't come with
9598
// a condition variable attached, others should.
9699
blocked: Q
97100
}
98101

99-
#[doc(hidden)]
100-
struct Sem<Q>(Exclusive<SemInner<Q>>);
102+
struct Sem<Q>(UnsafeArc<SemInner<Q>>);
101103

102104
#[doc(hidden)]
103105
impl<Q:Send> Sem<Q> {
104106
fn new(count: int, q: Q) -> Sem<Q> {
105-
Sem(Exclusive::new(SemInner {
106-
count: count, waiters: WaitQueue::new(), blocked: q }))
107+
Sem(UnsafeArc::new(SemInner {
108+
count: count,
109+
waiters: WaitQueue::new(),
110+
blocked: q,
111+
lock: mutex::Mutex::new(),
112+
}))
113+
}
114+
115+
unsafe fn with(&self, f: |&mut SemInner<Q>|) {
116+
let Sem(ref arc) = *self;
117+
let state = arc.get();
118+
let _g = (*state).lock.lock();
119+
f(cast::transmute(state));
107120
}
108121

109122
pub fn acquire(&self) {
110123
unsafe {
111124
let mut waiter_nobe = None;
112-
let Sem(ref lock) = *self;
113-
lock.with(|state| {
125+
self.with(|state| {
114126
state.count -= 1;
115127
if state.count < 0 {
116128
// Create waiter nobe, enqueue ourself, and tell
@@ -129,8 +141,7 @@ impl<Q:Send> Sem<Q> {
129141

130142
pub fn release(&self) {
131143
unsafe {
132-
let Sem(ref lock) = *self;
133-
lock.with(|state| {
144+
self.with(|state| {
134145
state.count += 1;
135146
if state.count <= 0 {
136147
state.waiters.signal();
@@ -210,8 +221,7 @@ impl<'a> Condvar<'a> {
210221
let mut out_of_bounds = None;
211222
// Release lock, 'atomically' enqueuing ourselves in so doing.
212223
unsafe {
213-
let Sem(ref queue) = *self.sem;
214-
queue.with(|state| {
224+
self.sem.with(|state| {
215225
if condvar_id < state.blocked.len() {
216226
// Drop the lock.
217227
state.count += 1;
@@ -253,8 +263,7 @@ impl<'a> Condvar<'a> {
253263
unsafe {
254264
let mut out_of_bounds = None;
255265
let mut result = false;
256-
let Sem(ref lock) = *self.sem;
257-
lock.with(|state| {
266+
self.sem.with(|state| {
258267
if condvar_id < state.blocked.len() {
259268
result = state.blocked[condvar_id].signal();
260269
} else {
@@ -276,8 +285,7 @@ impl<'a> Condvar<'a> {
276285
let mut out_of_bounds = None;
277286
let mut queue = None;
278287
unsafe {
279-
let Sem(ref lock) = *self.sem;
280-
lock.with(|state| {
288+
self.sem.with(|state| {
281289
if condvar_id < state.blocked.len() {
282290
// To avoid :broadcast_heavy, we make a new waitqueue,
283291
// swap it out with the old one, and broadcast on the

src/libextra/sync/mpsc_intrusive.rs

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/* Copyright (c) 2010-2011 Dmitry Vyukov. All rights reserved.
2+
* Redistribution and use in source and binary forms, with or without
3+
* modification, are permitted provided that the following conditions are met:
4+
*
5+
* 1. Redistributions of source code must retain the above copyright notice,
6+
* this list of conditions and the following disclaimer.
7+
*
8+
* 2. Redistributions in binary form must reproduce the above copyright
9+
* notice, this list of conditions and the following disclaimer in the
10+
* documentation and/or other materials provided with the distribution.
11+
*
12+
* THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIED
13+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
14+
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
15+
* EVENT SHALL DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
16+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
17+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
18+
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
19+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
20+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
21+
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22+
*
23+
* The views and conclusions contained in the software and documentation are
24+
* those of the authors and should not be interpreted as representing official
25+
* policies, either expressed or implied, of Dmitry Vyukov.
26+
*/
27+
28+
//! A mostly lock-free multi-producer, single consumer queue.
29+
//!
30+
//! This module implements an intrusive MPSC queue. This queue is incredibly
31+
//! unsafe (due to use of unsafe pointers for nodes), and hence is not public.
32+
33+
// http://www.1024cores.net/home/lock-free-algorithms
34+
// /queues/intrusive-mpsc-node-based-queue
35+
36+
use std::cast;
37+
use std::sync::atomics;
38+
39+
// NB: all links are done as AtomicUint instead of AtomicPtr to allow for static
40+
// initialization.
41+
42+
pub struct Node<T> {
43+
next: atomics::AtomicUint,
44+
data: T,
45+
}
46+
47+
pub struct DummyNode {
48+
next: atomics::AtomicUint,
49+
}
50+
51+
pub struct Queue<T> {
52+
head: atomics::AtomicUint,
53+
tail: *mut Node<T>,
54+
stub: DummyNode,
55+
}
56+
57+
impl<T: Send> Queue<T> {
58+
pub fn new() -> Queue<T> {
59+
Queue {
60+
head: atomics::AtomicUint::new(0),
61+
tail: 0 as *mut Node<T>,
62+
stub: DummyNode {
63+
next: atomics::AtomicUint::new(0),
64+
},
65+
}
66+
}
67+
68+
pub unsafe fn push(&mut self, node: *mut Node<T>) {
69+
(*node).next.store(0, atomics::Release);
70+
let prev = self.head.swap(node as uint, atomics::AcqRel);
71+
72+
// Note that this code is slightly modified to allow static
73+
// initialization of these queues with rust's flavor of static
74+
// initialization.
75+
if prev == 0 {
76+
self.stub.next.store(node as uint, atomics::Release);
77+
} else {
78+
let prev = prev as *mut Node<T>;
79+
(*prev).next.store(node as uint, atomics::Release);
80+
}
81+
}
82+
83+
/// You'll note that the other MPSC queue in std::sync is non-intrusive and
84+
/// returns a `PopResult` here to indicate when the queue is inconsistent.
85+
/// An "inconsistent state" in the other queue means that a pusher has
86+
/// pushed, but it hasn't finished linking the rest of the chain.
87+
///
88+
/// This queue also suffers from this problem, but I currently haven't been
89+
/// able to detangle when this actually happens. This code is translated
90+
/// verbatim from the website above, and is more complicated than the
91+
/// non-intrusive version.
92+
///
93+
/// Right now consumers of this queue must be ready for this fact. Just
94+
/// because `pop` returns `None` does not mean that there is not data
95+
/// on the queue.
96+
pub unsafe fn pop(&mut self) -> Option<*mut Node<T>> {
97+
let tail = self.tail;
98+
let mut tail = if !tail.is_null() {tail} else {
99+
cast::transmute(&self.stub)
100+
};
101+
let mut next = (*tail).next(atomics::Relaxed);
102+
if tail as uint == &self.stub as *DummyNode as uint {
103+
if next.is_null() {
104+
return None;
105+
}
106+
self.tail = next;
107+
tail = next;
108+
next = (*next).next(atomics::Relaxed);
109+
}
110+
if !next.is_null() {
111+
self.tail = next;
112+
return Some(tail);
113+
}
114+
let head = self.head.load(atomics::Acquire) as *mut Node<T>;
115+
if tail != head {
116+
return None;
117+
}
118+
let stub = cast::transmute(&self.stub);
119+
self.push(stub);
120+
next = (*tail).next(atomics::Relaxed);
121+
if !next.is_null() {
122+
self.tail = next;
123+
return Some(tail);
124+
}
125+
return None
126+
}
127+
}
128+
129+
impl<T: Send> Node<T> {
130+
pub fn new(t: T) -> Node<T> {
131+
Node {
132+
data: t,
133+
next: atomics::AtomicUint::new(0),
134+
}
135+
}
136+
pub unsafe fn next(&mut self, ord: atomics::Ordering) -> *mut Node<T> {
137+
cast::transmute::<uint, *mut Node<T>>(self.next.load(ord))
138+
}
139+
}

0 commit comments

Comments
 (0)