Skip to content

Commit ee83323

Browse files
author
Stjepan Glavina
committed
Add special bounded(1) implementation
1 parent 83223b5 commit ee83323

File tree

4 files changed

+321
-6
lines changed

4 files changed

+321
-6
lines changed

src/lib.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ use std::panic::{RefUnwindSafe, UnwindSafe};
3636
use std::sync::atomic::{self, AtomicUsize, Ordering};
3737

3838
use crate::bounded::Bounded;
39+
use crate::single::Single;
3940
use crate::unbounded::Unbounded;
4041

4142
mod bounded;
43+
mod single;
4244
mod unbounded;
4345

4446
/// A concurrent queue.
@@ -67,8 +69,9 @@ impl<T> UnwindSafe for ConcurrentQueue<T> {}
6769
impl<T> RefUnwindSafe for ConcurrentQueue<T> {}
6870

6971
enum Inner<T> {
70-
Bounded(Bounded<T>),
71-
Unbounded(Unbounded<T>),
72+
Single(Single<T>),
73+
Bounded(Box<Bounded<T>>),
74+
Unbounded(Box<Unbounded<T>>),
7275
}
7376

7477
impl<T> ConcurrentQueue<T> {
@@ -88,7 +91,11 @@ impl<T> ConcurrentQueue<T> {
8891
/// let q = ConcurrentQueue::<i32>::bounded(100);
8992
/// ```
9093
pub fn bounded(cap: usize) -> ConcurrentQueue<T> {
91-
ConcurrentQueue(Inner::Bounded(Bounded::new(cap)))
94+
if cap == 1 {
95+
ConcurrentQueue(Inner::Single(Single::new()))
96+
} else {
97+
ConcurrentQueue(Inner::Bounded(Box::new(Bounded::new(cap))))
98+
}
9299
}
93100

94101
/// Creates a new unbounded queue.
@@ -101,7 +108,7 @@ impl<T> ConcurrentQueue<T> {
101108
/// let q = ConcurrentQueue::<i32>::unbounded();
102109
/// ```
103110
pub fn unbounded() -> ConcurrentQueue<T> {
104-
ConcurrentQueue(Inner::Unbounded(Unbounded::new()))
111+
ConcurrentQueue(Inner::Unbounded(Box::new(Unbounded::new())))
105112
}
106113

107114
/// Attempts to push an item into the queue.
@@ -135,6 +142,7 @@ impl<T> ConcurrentQueue<T> {
135142
/// ```
136143
pub fn push(&self, value: T) -> Result<(), PushError<T>> {
137144
match &self.0 {
145+
Inner::Single(q) => q.push(value),
138146
Inner::Bounded(q) => q.push(value),
139147
Inner::Unbounded(q) => q.push(value),
140148
}
@@ -167,6 +175,7 @@ impl<T> ConcurrentQueue<T> {
167175
/// ```
168176
pub fn pop(&self) -> Result<T, PopError> {
169177
match &self.0 {
178+
Inner::Single(q) => q.pop(),
170179
Inner::Bounded(q) => q.pop(),
171180
Inner::Unbounded(q) => q.pop(),
172181
}
@@ -187,6 +196,7 @@ impl<T> ConcurrentQueue<T> {
187196
/// ```
188197
pub fn is_empty(&self) -> bool {
189198
match &self.0 {
199+
Inner::Single(q) => q.is_empty(),
190200
Inner::Bounded(q) => q.is_empty(),
191201
Inner::Unbounded(q) => q.is_empty(),
192202
}
@@ -209,6 +219,7 @@ impl<T> ConcurrentQueue<T> {
209219
/// ```
210220
pub fn is_full(&self) -> bool {
211221
match &self.0 {
222+
Inner::Single(q) => q.is_full(),
212223
Inner::Bounded(q) => q.is_full(),
213224
Inner::Unbounded(q) => q.is_full(),
214225
}
@@ -232,6 +243,7 @@ impl<T> ConcurrentQueue<T> {
232243
/// ```
233244
pub fn len(&self) -> usize {
234245
match &self.0 {
246+
Inner::Single(q) => q.len(),
235247
Inner::Bounded(q) => q.len(),
236248
Inner::Unbounded(q) => q.len(),
237249
}
@@ -254,6 +266,7 @@ impl<T> ConcurrentQueue<T> {
254266
/// ```
255267
pub fn capacity(&self) -> Option<usize> {
256268
match &self.0 {
269+
Inner::Single(_) => Some(1),
257270
Inner::Bounded(q) => Some(q.capacity()),
258271
Inner::Unbounded(_) => None,
259272
}
@@ -288,6 +301,7 @@ impl<T> ConcurrentQueue<T> {
288301
/// ```
289302
pub fn close(&self) -> bool {
290303
match &self.0 {
304+
Inner::Single(q) => q.close(),
291305
Inner::Bounded(q) => q.close(),
292306
Inner::Unbounded(q) => q.close(),
293307
}
@@ -308,6 +322,7 @@ impl<T> ConcurrentQueue<T> {
308322
/// ```
309323
pub fn is_closed(&self) -> bool {
310324
match &self.0 {
325+
Inner::Single(q) => q.is_closed(),
311326
Inner::Bounded(q) => q.is_closed(),
312327
Inner::Unbounded(q) => q.is_closed(),
313328
}

src/single.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::cell::UnsafeCell;
2+
use std::mem::MaybeUninit;
3+
use std::sync::atomic::{AtomicUsize, Ordering};
4+
use std::thread;
5+
6+
use crate::{PopError, PushError};
7+
8+
const LOCKED: usize = 1 << 0;
9+
const PUSHED: usize = 1 << 1;
10+
const CLOSED: usize = 1 << 2;
11+
12+
/// A single-element queue.
13+
pub struct Single<T> {
14+
state: AtomicUsize,
15+
slot: UnsafeCell<MaybeUninit<T>>,
16+
}
17+
18+
impl<T> Single<T> {
19+
/// Creates a new single-element queue.
20+
pub fn new() -> Single<T> {
21+
Single {
22+
state: AtomicUsize::new(0),
23+
slot: UnsafeCell::new(MaybeUninit::uninit()),
24+
}
25+
}
26+
27+
/// Attempts to push an item into the queue.
28+
pub fn push(&self, value: T) -> Result<(), PushError<T>> {
29+
// Lock and fill the slot.
30+
let state = self
31+
.state
32+
.compare_and_swap(0, LOCKED | PUSHED, Ordering::SeqCst);
33+
34+
if state == 0 {
35+
// Write the value and unlock.
36+
unsafe { self.slot.get().write(MaybeUninit::new(value)) }
37+
self.state.fetch_and(!LOCKED, Ordering::Release);
38+
Ok(())
39+
} else if state & CLOSED != 0 {
40+
Err(PushError::Closed(value))
41+
} else {
42+
Err(PushError::Full(value))
43+
}
44+
}
45+
46+
/// Attempts to pop an item from the queue.
47+
pub fn pop(&self) -> Result<T, PopError> {
48+
let mut state = PUSHED;
49+
loop {
50+
// Lock and empty the slot.
51+
let prev =
52+
self.state
53+
.compare_and_swap(state, (state | LOCKED) & !PUSHED, Ordering::SeqCst);
54+
55+
if prev == state {
56+
// Read the value and unlock.
57+
let value = unsafe { self.slot.get().read().assume_init() };
58+
self.state.fetch_and(!LOCKED, Ordering::Release);
59+
return Ok(value);
60+
}
61+
62+
if prev & PUSHED == 0 {
63+
if prev & CLOSED == 0 {
64+
return Err(PopError::Empty);
65+
} else {
66+
return Err(PopError::Closed);
67+
}
68+
}
69+
70+
if prev & LOCKED == 0 {
71+
state = prev;
72+
} else {
73+
thread::yield_now();
74+
state = prev & !LOCKED;
75+
}
76+
}
77+
}
78+
79+
/// Returns the number of items in the queue.
80+
pub fn len(&self) -> usize {
81+
if self.state.load(Ordering::SeqCst) & PUSHED == 0 {
82+
0
83+
} else {
84+
1
85+
}
86+
}
87+
88+
/// Returns `true` if the queue is empty.
89+
pub fn is_empty(&self) -> bool {
90+
self.len() == 0
91+
}
92+
93+
/// Returns `true` if the queue is full.
94+
pub fn is_full(&self) -> bool {
95+
self.len() == 1
96+
}
97+
98+
/// Closes the queue.
99+
///
100+
/// Returns `true` if this call closed the queue.
101+
pub fn close(&self) -> bool {
102+
let state = self.state.fetch_or(CLOSED, Ordering::SeqCst);
103+
state & CLOSED == 0
104+
}
105+
106+
/// Returns `true` if the queue is closed.
107+
pub fn is_closed(&self) -> bool {
108+
self.state.load(Ordering::SeqCst) & CLOSED != 0
109+
}
110+
}
111+
112+
impl<T> Drop for Single<T> {
113+
fn drop(&mut self) {
114+
// Drop the value in the slot.
115+
if *self.state.get_mut() & PUSHED != 0 {
116+
let value = unsafe { self.slot.get().read().assume_init() };
117+
drop(value);
118+
}
119+
}
120+
}

tests/bounded.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use easy_parallel::Parallel;
55

66
#[test]
77
fn smoke() {
8-
let q = ConcurrentQueue::bounded(1);
8+
let q = ConcurrentQueue::bounded(2);
99

1010
q.push(7).unwrap();
1111
assert_eq!(q.pop(), Ok(7));
@@ -114,7 +114,7 @@ fn len() {
114114

115115
#[test]
116116
fn close() {
117-
let q = ConcurrentQueue::bounded(1);
117+
let q = ConcurrentQueue::bounded(2);
118118
assert_eq!(q.push(10), Ok(()));
119119

120120
assert!(!q.is_closed());

0 commit comments

Comments
 (0)