Skip to content

Commit

Permalink
Add `conc::Guard::{try,maybe}_map.
Browse files Browse the repository at this point in the history
- Add tests for it and `Guard::new` et al.
  • Loading branch information
ticki committed Aug 12, 2017
1 parent 32980a7 commit a89569c
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 15 deletions.
2 changes: 1 addition & 1 deletion conc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "conc"
version = "0.4.2"
version = "0.5.0"
authors = ["ticki <Ticki@users.noreply.github.com>"]
description = "Hazard-pointer-based concurrent memory reclamation."
repository = "https://github.com/ticki/tfs"
Expand Down
104 changes: 91 additions & 13 deletions conc/src/guard.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! RAII guards for hazards.

use std::{cmp, ops};
use std::ops;
use std::sync::atomic;
use {hazard, local};

Expand Down Expand Up @@ -43,7 +43,7 @@ pub struct Guard<T: 'static + ?Sized> {
}

impl<T: ?Sized> Guard<T> {
/// (Failably) create a new guard.
/// Failably create a new guard.
///
/// This has all the same restrictions and properties as `Guard::new()` (please read its
/// documentation before using), with the exception of being failable.
Expand Down Expand Up @@ -113,7 +113,7 @@ impl<T: ?Sized> Guard<T> {
Guard::try_new::<_, ()>(|| Ok(ptr())).unwrap()
}

/// Conditionally create a guard.
/// Conditionally create a new guard.
///
/// This acts `try_new`, but with `Option` instead of `Result`.
pub fn maybe_new<F>(ptr: F) -> Option<Guard<T>>
Expand All @@ -126,29 +126,45 @@ impl<T: ?Sized> Guard<T> {
/// This allows one to map a pointer to a pointer e.g. to an object referenced by the old. It
/// is very convenient for creating APIs without the need for creating a wrapper type.
// TODO: Is this sound?
pub fn map<U, F>(self, f: F) -> Guard<U>
pub fn map<U: ?Sized, F>(self, f: F) -> Guard<U>
where F: FnOnce(&T) -> &U {
Guard {
hazard: self.hazard,
pointer: f(self.pointer),
}
}

/// (Failably) map the pointer to another.
///
/// This corresponds to `map`, but when the closure returns `Err`, this does as well. In other
/// words, the closure can fail.
pub fn try_map<U: ?Sized, E, F>(self, f: F) -> Result<Guard<U>, E>
where F: FnOnce(&T) -> Result<&U, E> {
Ok(Guard {
hazard: self.hazard,
pointer: f(self.pointer)?,
})
}

/// Conditionally map the pointer to another.
///
/// This acts `try_map`, but with `Option` instead of `Result`.
pub fn maybe_map<U: ?Sized, F>(self, f: F) -> Option<Guard<U>>
where F: FnOnce(&T) -> Option<&U> {
let hazard = self.hazard;
f(self.pointer).map(|res| Guard {
hazard: hazard,
pointer: res,
})
}

/// Get the raw pointer of this guard.
pub fn as_ptr(&self) -> *const T {
self.pointer
}
}

impl<T> cmp::PartialEq for Guard<T> {
fn eq(&self, other: &Guard<T>) -> bool {
self.as_ptr() == other.as_ptr()
}
}

impl<T> cmp::Eq for Guard<T> {}

impl<T> ops::Deref for Guard<T> {
impl<T: ?Sized> ops::Deref for Guard<T> {
type Target = T;

fn deref(&self) -> &T {
Expand All @@ -161,6 +177,68 @@ mod tests {
use super::*;
use std::mem;

use Atomic;
use std::sync::atomic;

#[test]
fn new() {
assert_eq!(&*Guard::new(|| "blah"), "blah");
}

#[test]
fn maybe_new() {
assert_eq!(&*Guard::maybe_new(|| Some("blah")).unwrap(), "blah");
assert!(Guard::<u8>::maybe_new(|| None).is_none());
}

#[test]
fn try_new() {
assert_eq!(&*Guard::try_new::<_, u8>(|| Ok("blah")).unwrap(), "blah");
assert_eq!(Guard::<u8>::try_new(|| Err(2)).unwrap_err(), 2);
}

#[test]
fn map() {
let g = Guard::new(|| "blah");
assert_eq!(&*g.map(|x| {
assert_eq!(x, "blah");
"blah2"
}), "blah2");
}

#[test]
fn maybe_map() {
let g = Guard::new(|| "blah");
assert_eq!(&*g.maybe_map(|x| {
assert_eq!(x, "blah");
Some("blah2")
}).unwrap(), "blah2");
let g = Guard::new(|| "blah");
assert_eq!(&*g, "blah");
assert!(g.maybe_map::<u8, _>(|_| None).is_none());
}

#[test]
fn try_map() {
let g = Guard::new(|| "blah");
assert_eq!(&*g.try_map::<_, u8, _>(|x| {
assert_eq!(x, "blah");
Ok("blah2")
}).unwrap(), "blah2");
let g = Guard::new(|| "blah");
assert_eq!(&*g, "blah");
assert_eq!(g.try_map::<u8, _, _>(|_| Err(2)).unwrap_err(), 2);
}

#[test]
fn map_field() {
let a = Atomic::new(Some(Box::new((7, 13))));
let g = a.load(atomic::Ordering::Relaxed).unwrap().map(|&(_, ref b)| b);
drop(a);
::gc();
assert_eq!(*g, 13);
}

#[test]
#[should_panic]
fn panic_during_guard_creation() {
Expand Down
2 changes: 1 addition & 1 deletion conc/src/sync/treiber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl<T> Treiber<T> {
// If it match, we are done as the previous head node was replaced by the tail, popping
// the top element. The element we return is the one carried by the previous head.
if let Some(ref new) = snapshot {
if new == &old {
if new.as_ptr() == old.as_ptr() {
// As we overwrote the old head (the CAS was successful), we must queue its
// deletion.
unsafe { add_garbage_box(old.as_ptr()); }
Expand Down

0 comments on commit a89569c

Please sign in to comment.