Skip to content

Commit 7bb1327

Browse files
committed
Use odd/even instead of high bit
It makes the code so much nicer. A nod to #76 is also worthwhile here.
1 parent c3d206e commit 7bb1327

File tree

4 files changed

+26
-43
lines changed

4 files changed

+26
-43
lines changed

left-right/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@
172172

173173
use std::sync::{atomic, Arc, Mutex};
174174

175-
type Epochs = Arc<Mutex<slab::Slab<Arc<atomic::AtomicU64>>>>;
175+
type Epochs = Arc<Mutex<slab::Slab<Arc<atomic::AtomicUsize>>>>;
176176

177177
mod write;
178178
pub use crate::write::WriteHandle;

left-right/src/read.rs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::cell::Cell;
2+
use std::fmt;
23
use std::marker::PhantomData;
34
use std::sync::atomic;
45
use std::sync::atomic::AtomicPtr;
56
use std::sync::{self, Arc};
6-
use std::{fmt, mem};
77

88
// To make [`WriteHandle`] and friends work.
99
#[cfg(doc)]
@@ -40,9 +40,8 @@ pub use factory::ReadHandleFactory;
4040
pub struct ReadHandle<T> {
4141
pub(crate) inner: sync::Arc<AtomicPtr<T>>,
4242
pub(crate) epochs: crate::Epochs,
43-
epoch: sync::Arc<sync::atomic::AtomicU64>,
43+
epoch: sync::Arc<sync::atomic::AtomicUsize>,
4444
epoch_i: usize,
45-
my_epoch: Cell<u64>,
4645
enters: Cell<usize>,
4746

4847
// `ReadHandle` is _only_ Send if T is Sync. If T is !Sync, then it's not okay for us to expose
@@ -68,7 +67,6 @@ impl<T> fmt::Debug for ReadHandle<T> {
6867
f.debug_struct("ReadHandle")
6968
.field("epochs", &self.epochs)
7069
.field("epoch", &self.epoch)
71-
.field("my_epoch", &self.my_epoch)
7270
.finish()
7371
}
7472
}
@@ -91,15 +89,14 @@ impl<T> ReadHandle<T> {
9189

9290
fn new_with_arc(inner: Arc<AtomicPtr<T>>, epochs: crate::Epochs) -> Self {
9391
// tell writer about our epoch tracker
94-
let epoch = sync::Arc::new(atomic::AtomicU64::new(0));
92+
let epoch = sync::Arc::new(atomic::AtomicUsize::new(0));
9593
// okay to lock, since we're not holding up the epoch
9694
let epoch_i = epochs.lock().unwrap().insert(Arc::clone(&epoch));
9795

9896
Self {
9997
epochs,
10098
epoch,
10199
epoch_i,
102-
my_epoch: Cell::new(0),
103100
enters: Cell::new(0),
104101
inner,
105102
_unimpl_send: PhantomData,
@@ -128,7 +125,6 @@ impl<T> ReadHandle<T> {
128125
self.enters.set(enters + 1);
129126
Some(ReadGuard {
130127
handle: guard::ReadHandleState::from(self),
131-
epoch: self.my_epoch.get(),
132128
t: r_handle,
133129
})
134130
} else {
@@ -164,9 +160,7 @@ impl<T> ReadHandle<T> {
164160
// in all cases, using a pointer we read *after* updating our epoch is safe.
165161

166162
// so, update our epoch tracker.
167-
let epoch = self.my_epoch.get() + 1;
168-
self.my_epoch.set(epoch);
169-
self.epoch.store(epoch, atomic::Ordering::Release);
163+
self.epoch.fetch_add(1, atomic::Ordering::AcqRel);
170164

171165
// ensure that the pointer read happens strictly after updating the epoch
172166
atomic::fence(atomic::Ordering::SeqCst);
@@ -183,16 +177,12 @@ impl<T> ReadHandle<T> {
183177
self.enters.set(enters);
184178
Some(ReadGuard {
185179
handle: guard::ReadHandleState::from(self),
186-
epoch,
187180
t: r_handle,
188181
})
189182
} else {
190183
// the writehandle has been dropped, and so has both copies,
191184
// so restore parity and return None
192-
self.epoch.store(
193-
epoch | 1 << (mem::size_of_val(&self.my_epoch) * 8 - 1),
194-
atomic::Ordering::Release,
195-
);
185+
self.epoch.fetch_add(1, atomic::Ordering::AcqRel);
196186
None
197187
}
198188
}

left-right/src/read/guard.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ use std::sync::atomic;
55

66
#[derive(Debug, Copy, Clone)]
77
pub(super) struct ReadHandleState<'rh> {
8-
pub(super) shared_epoch: &'rh sync::atomic::AtomicU64,
9-
pub(super) own_epoch: &'rh Cell<u64>,
8+
pub(super) epoch: &'rh sync::atomic::AtomicUsize,
109
pub(super) enters: &'rh Cell<usize>,
1110
}
1211

1312
impl<'rh, T> From<&'rh super::ReadHandle<T>> for ReadHandleState<'rh> {
1413
fn from(rh: &'rh super::ReadHandle<T>) -> Self {
1514
Self {
16-
shared_epoch: &rh.epoch,
17-
own_epoch: &rh.my_epoch,
15+
epoch: &rh.epoch,
1816
enters: &rh.enters,
1917
}
2018
}
@@ -33,7 +31,6 @@ pub struct ReadGuard<'rh, T: ?Sized> {
3331
// NOTE: _technically_ this is more like &'self.
3432
// the reference is valid until the guard is dropped.
3533
pub(super) t: &'rh T,
36-
pub(super) epoch: u64,
3734
pub(super) handle: ReadHandleState<'rh>,
3835
}
3936

@@ -63,7 +60,6 @@ impl<'rh, T: ?Sized> ReadGuard<'rh, T> {
6360
{
6461
let rg = ReadGuard {
6562
t: f(orig.t),
66-
epoch: orig.epoch,
6763
handle: orig.handle,
6864
};
6965
mem::forget(orig);
@@ -99,7 +95,6 @@ impl<'rh, T: ?Sized> ReadGuard<'rh, T> {
9995
{
10096
let rg = ReadGuard {
10197
t: f(orig.t)?,
102-
epoch: orig.epoch,
10398
handle: orig.handle,
10499
};
105100
mem::forget(orig);
@@ -126,11 +121,7 @@ impl<'rh, T: ?Sized> Drop for ReadGuard<'rh, T> {
126121
self.handle.enters.set(enters);
127122
if enters == 0 {
128123
// We are the last guard to be dropped -- now release our epoch.
129-
let epoch = self.handle.own_epoch.get();
130-
self.handle.shared_epoch.store(
131-
epoch | 1 << (mem::size_of_val(&epoch) * 8 - 1),
132-
atomic::Ordering::Release,
133-
);
124+
self.handle.epoch.fetch_add(1, atomic::Ordering::AcqRel);
134125
}
135126
}
136127
}

left-right/src/write.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::read::ReadHandle;
44
use std::ptr::NonNull;
55
use std::sync::atomic;
66
use std::sync::{Arc, MutexGuard};
7-
use std::{fmt, mem, thread};
7+
use std::{fmt, thread};
88

99
/// A writer handle to a left-right guarded data structure.
1010
///
@@ -27,7 +27,7 @@ where
2727
oplog: Vec<O>,
2828
swap_index: usize,
2929
r_handle: ReadHandle<T>,
30-
last_epochs: Vec<u64>,
30+
last_epochs: Vec<usize>,
3131
#[cfg(test)]
3232
refreshes: usize,
3333
}
@@ -125,34 +125,36 @@ where
125125
}
126126
}
127127

128-
fn wait(&mut self, epochs: &mut MutexGuard<'_, slab::Slab<Arc<atomic::AtomicU64>>>) {
128+
fn wait(&mut self, epochs: &mut MutexGuard<'_, slab::Slab<Arc<atomic::AtomicUsize>>>) {
129129
let mut iter = 0;
130130
let mut starti = 0;
131131

132-
// We want a 1 in the type of the epochs, without naming that type here.
133-
#[allow(unused_assignments)]
134-
let mut epoch_unit = self.last_epochs.get(0).copied().unwrap_or(0);
135-
epoch_unit = 1;
136-
let high_bit = epoch_unit << (mem::size_of_val(&epoch_unit) * 8 - 1);
137-
138132
// we're over-estimating here, but slab doesn't expose its max index
139133
self.last_epochs.resize(epochs.capacity(), 0);
140134
'retry: loop {
141135
// read all and see if all have changed (which is likely)
142136
for (ii, (ri, epoch)) in epochs.iter().enumerate().skip(starti) {
143-
// note that `ri` _may_ have been re-used since we last read into last_epochs.
137+
// if the reader's epoch was even last we read it (which was _after_ the swap),
138+
// then they either do not have the pointer, or must have read the pointer strictly
139+
// after the swap. in either case, they cannot be using the old pointer value (what
140+
// is now w_handle).
141+
//
142+
// note that this holds even with wrap-around since std::u{N}::MAX == 2 ^ N - 1,
143+
// which is odd, and std::u{N}::MAX + 1 == 0 is even.
144+
//
145+
// note also that `ri` _may_ have been re-used since we last read into last_epochs.
144146
// this is okay though, as a change still implies that the new reader must have
145147
// arrived _after_ we did the atomic swap, and thus must also have seen the new
146148
// pointer.
147-
if self.last_epochs[ri] & high_bit != 0 {
148-
// reader was not active right after last swap
149-
// and therefore *must* only see new pointer
149+
if self.last_epochs[ri] % 2 == 0 {
150150
continue;
151151
}
152152

153153
let now = epoch.load(atomic::Ordering::Acquire);
154-
if (now != self.last_epochs[ri]) | (now & high_bit != 0) | (now == 0) {
155-
// reader must have seen last swap
154+
if now != self.last_epochs[ri] {
155+
// reader must have seen the last swap, since they have done at least one
156+
// operation since we last looked at their epoch, which _must_ mean that they
157+
// are no longer using the old pointer value.
156158
} else {
157159
// reader may not have seen swap
158160
// continue from this reader's epoch

0 commit comments

Comments
 (0)