aboutsummaryrefslogtreecommitdiff
path: root/tests/loom.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/loom.rs')
-rw-r--r--tests/loom.rs157
1 files changed, 157 insertions, 0 deletions
diff --git a/tests/loom.rs b/tests/loom.rs
new file mode 100644
index 0000000..4e56acd
--- /dev/null
+++ b/tests/loom.rs
@@ -0,0 +1,157 @@
+#![cfg(crossbeam_loom)]
+
+use crossbeam_epoch as epoch;
+use loom_crate as loom;
+
+use epoch::*;
+use epoch::{Atomic, Owned};
+use loom::sync::atomic::Ordering::{self, Acquire, Relaxed, Release};
+use loom::sync::Arc;
+use loom::thread::spawn;
+use std::mem::ManuallyDrop;
+use std::ptr;
+
+#[test]
+fn it_works() {
+ loom::model(|| {
+ let collector = Collector::new();
+ let item: Atomic<String> = Atomic::from(Owned::new(String::from("boom")));
+ let item2 = item.clone();
+ let collector2 = collector.clone();
+ let guard = collector.register().pin();
+
+ let jh = loom::thread::spawn(move || {
+ let guard = collector2.register().pin();
+ guard.defer(move || {
+ // this isn't really safe, since other threads may still have pointers to the
+ // value, but in this limited test scenario it's okay, since we know the test won't
+ // access item after all the pins are released.
+ let mut item = unsafe { item2.into_owned() };
+ // mutate it as a second measure to make sure the assert_eq below would fail
+ item.retain(|c| c == 'o');
+ drop(item);
+ });
+ });
+
+ let item = item.load(Ordering::SeqCst, &guard);
+ // we pinned strictly before the call to defer_destroy,
+ // so item cannot have been dropped yet
+ assert_eq!(*unsafe { item.deref() }, "boom");
+ drop(guard);
+
+ jh.join().unwrap();
+
+ drop(collector);
+ })
+}
+
+#[test]
+fn treiber_stack() {
+ /// Treiber's lock-free stack.
+ ///
+ /// Usable with any number of producers and consumers.
+ #[derive(Debug)]
+ pub struct TreiberStack<T> {
+ head: Atomic<Node<T>>,
+ }
+
+ #[derive(Debug)]
+ struct Node<T> {
+ data: ManuallyDrop<T>,
+ next: Atomic<Node<T>>,
+ }
+
+ impl<T> TreiberStack<T> {
+ /// Creates a new, empty stack.
+ pub fn new() -> TreiberStack<T> {
+ TreiberStack {
+ head: Atomic::null(),
+ }
+ }
+
+ /// Pushes a value on top of the stack.
+ pub fn push(&self, t: T) {
+ let mut n = Owned::new(Node {
+ data: ManuallyDrop::new(t),
+ next: Atomic::null(),
+ });
+
+ let guard = epoch::pin();
+
+ loop {
+ let head = self.head.load(Relaxed, &guard);
+ n.next.store(head, Relaxed);
+
+ match self
+ .head
+ .compare_exchange(head, n, Release, Relaxed, &guard)
+ {
+ Ok(_) => break,
+ Err(e) => n = e.new,
+ }
+ }
+ }
+
+ /// Attempts to pop the top element from the stack.
+ ///
+ /// Returns `None` if the stack is empty.
+ pub fn pop(&self) -> Option<T> {
+ let guard = epoch::pin();
+ loop {
+ let head = self.head.load(Acquire, &guard);
+
+ match unsafe { head.as_ref() } {
+ Some(h) => {
+ let next = h.next.load(Relaxed, &guard);
+
+ if self
+ .head
+ .compare_exchange(head, next, Relaxed, Relaxed, &guard)
+ .is_ok()
+ {
+ unsafe {
+ guard.defer_destroy(head);
+ return Some(ManuallyDrop::into_inner(ptr::read(&(*h).data)));
+ }
+ }
+ }
+ None => return None,
+ }
+ }
+ }
+
+ /// Returns `true` if the stack is empty.
+ pub fn is_empty(&self) -> bool {
+ let guard = epoch::pin();
+ self.head.load(Acquire, &guard).is_null()
+ }
+ }
+
+ impl<T> Drop for TreiberStack<T> {
+ fn drop(&mut self) {
+ while self.pop().is_some() {}
+ }
+ }
+
+ loom::model(|| {
+ let stack1 = Arc::new(TreiberStack::new());
+ let stack2 = Arc::clone(&stack1);
+
+ // use 5 since it's greater than the 4 used for the sanitize feature
+ let jh = spawn(move || {
+ for i in 0..5 {
+ stack2.push(i);
+ assert!(stack2.pop().is_some());
+ }
+ });
+
+ for i in 0..5 {
+ stack1.push(i);
+ assert!(stack1.pop().is_some());
+ }
+
+ jh.join().unwrap();
+ assert!(stack1.pop().is_none());
+ assert!(stack1.is_empty());
+ });
+}