This is a component that I wanted to extract from `netmod-tcp`, in the qaul.net tree. I was considering just moving this lib to the utils tree in the qaul.net repo, but I decided that I needed this library in too many other programs, and that it really didn't have much to do with qaul.net so it's living here now. Considering that I want to also convey stability in the API, I'll immediately bump it to 1.0.0 as well.wip/yesman
parent
81dbf21e0b
commit
4c4d16d58c
@ -0,0 +1 @@ |
|||||||
|
eval "$(lorri direnv)" |
@ -0,0 +1,4 @@ |
|||||||
|
|
||||||
|
/target/ |
||||||
|
**/*.rs.bk |
||||||
|
Cargo.lock |
@ -0,0 +1,11 @@ |
|||||||
|
[package] |
||||||
|
name = "atomptr" |
||||||
|
description = "A safe, dependency-less abstraction for typed atomic smart pointers" |
||||||
|
version = "1.0.0" |
||||||
|
authors = ["Mx Kookie <kookie@spacekookie.de>"] |
||||||
|
edition = "2018" |
||||||
|
license = "GPL-3.0-or-later" |
||||||
|
repository = "https://git.spacekookie.de/kookienomicon" |
||||||
|
keywords = ["memory", "concurrency", "performance", "lock-free", "atomic"] |
||||||
|
categories = ["concurrency", "memory-management"] |
||||||
|
readme = "README.md" |
@ -0,0 +1 @@ |
|||||||
|
../../../licenses/GPL-3.0 |
@ -0,0 +1,51 @@ |
|||||||
|
# AtomPtr |
||||||
|
|
||||||
|
A safe, strongly typed (generic) atomic pointer abstraction to build |
||||||
|
datastructures, and lock-free algorithms on top of. Only uses |
||||||
|
`libstd`. |
||||||
|
|
||||||
|
The standard library contains an `AtomicPtr` type, which by itself |
||||||
|
isn't very ergonomic to use, because it deals with raw pointers. This |
||||||
|
library assumes that types can always be heap allocated, wrapping them |
||||||
|
in a `Box<T>`, and provides a nicer (and safe!) abstraction for |
||||||
|
`std::sync::atomic::AtomicPtr`. Using this crate is fairely |
||||||
|
self-explanatory: |
||||||
|
|
||||||
|
```rust |
||||||
|
use atomptr::AtomPtr; |
||||||
|
|
||||||
|
struct MyData { name: String } |
||||||
|
let data = MyData { name: "Kookie".into() }; |
||||||
|
|
||||||
|
let a = AtomPtr::new(data); |
||||||
|
println!("Name is: {}", a.get_ref().name); |
||||||
|
|
||||||
|
let old_ref = a.swap(MyData { name: "Bob".into() }); |
||||||
|
println!("Name now is: {}, was {}", a.get_ref().name, old_ref.name); |
||||||
|
``` |
||||||
|
|
||||||
|
Note that the type that is returned by `get_ref` and `swap` is |
||||||
|
`Ref<T>`, which means that the old data is not de-allocated after a |
||||||
|
swap, before this last reference goes out of scope. You can of course |
||||||
|
always manually call `drop()` on it. |
||||||
|
|
||||||
|
|
||||||
|
## License |
||||||
|
|
||||||
|
This micro-library is free software, and licensed under the GNU |
||||||
|
General Public License, version 3.0 or (at your choice), any later |
||||||
|
version. |
||||||
|
|
||||||
|
**Additional Permissions:** For Submission to the Apple App Store: |
||||||
|
Provided that you are otherwise in compliance with the GPLv3 for each |
||||||
|
covered work you convey (including without limitation making the |
||||||
|
Corresponding Source available in compliance with Section 6 of the |
||||||
|
GPLv3), the qaul.net developers also grant you the additional |
||||||
|
permission to convey through the Apple App Store non-source executable |
||||||
|
versions of the Program as incorporated into each applicable covered |
||||||
|
work as Executable Versions only under the Mozilla Public License |
||||||
|
version 2.0. |
||||||
|
|
||||||
|
A copy of both the GPL-3.0 and MPL-2.0 license texts are included in |
||||||
|
this repository. |
||||||
|
|
@ -0,0 +1,8 @@ |
|||||||
|
with import <nixpkgs> {}; |
||||||
|
|
||||||
|
stdenv.mkDerivation { |
||||||
|
name = "atomicptr"; |
||||||
|
buildInputs = with pkgs; [ |
||||||
|
rustracer rustup clangStdenv |
||||||
|
]; |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
#![feature(external_doc)] |
||||||
|
#![doc(include = "../README.md")] |
||||||
|
|
||||||
|
use std::sync::{ |
||||||
|
atomic::{AtomicPtr, Ordering}, |
||||||
|
Arc, |
||||||
|
}; |
||||||
|
use std::{cmp::PartialEq, ops::Deref}; |
||||||
|
|
||||||
|
/// An alias for a referenced pointer
|
||||||
|
pub struct Ref<T> { |
||||||
|
inner: Box<Arc<T>>, |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> Deref for Ref<T> { |
||||||
|
type Target = Arc<T>; |
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
&self.inner |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A safe atomic pointer wrapper
|
||||||
|
#[derive(Clone, Debug)] |
||||||
|
pub struct AtomPtr<T> { |
||||||
|
inner: Arc<AtomicPtr<Arc<T>>>, |
||||||
|
} |
||||||
|
|
||||||
|
// Implement Default for all T that implement default
|
||||||
|
impl<T: Default> Default for AtomPtr<T> { |
||||||
|
fn default() -> Self { |
||||||
|
Self::new(T::default()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> PartialEq for AtomPtr<T> { |
||||||
|
fn eq(&self, other: &Self) -> bool { |
||||||
|
Arc::ptr_eq(&self.get_ref().inner, &other.get_ref().inner) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> AtomPtr<T> { |
||||||
|
fn make_raw_ptr(t: T) -> *mut Arc<T> { |
||||||
|
Box::into_raw(Box::new(Arc::new(t))) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a new atomic pointer for a type
|
||||||
|
pub fn new(t: T) -> Self { |
||||||
|
let ptr = Self::make_raw_ptr(t); |
||||||
|
let inner = Arc::new(AtomicPtr::from(ptr)); |
||||||
|
Self { inner } |
||||||
|
} |
||||||
|
|
||||||
|
/// Get an immutable reference to the current value
|
||||||
|
pub fn get_ref(&self) -> Ref<T> { |
||||||
|
let ptr = self.inner.load(Ordering::Relaxed); |
||||||
|
let b = unsafe { Box::from_raw(ptr) }; |
||||||
|
|
||||||
|
let arc = Arc::clone(&*b); |
||||||
|
std::mem::forget(b); |
||||||
|
|
||||||
|
Ref { |
||||||
|
inner: Box::new(arc), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Swap the data entry with a new value, returning the old
|
||||||
|
pub fn swap(&self, new: T) -> Ref<T> { |
||||||
|
let new = Self::make_raw_ptr(new); |
||||||
|
let prev = self.inner.swap(new, Ordering::Relaxed); |
||||||
|
|
||||||
|
let b = unsafe { Box::from_raw(prev) }; |
||||||
|
let arc = Arc::clone(&*b); |
||||||
|
std::mem::forget(b); |
||||||
|
|
||||||
|
Ref { |
||||||
|
inner: Box::new(arc), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
#[derive(Clone, Debug, PartialEq)] |
||||||
|
struct TestStruct { |
||||||
|
name: String, |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn cloned() { |
||||||
|
let ts = TestStruct { |
||||||
|
name: "Hello".into(), |
||||||
|
}; |
||||||
|
|
||||||
|
let ptr1 = AtomPtr::new(ts); |
||||||
|
let ptr2 = ptr1.clone(); |
||||||
|
|
||||||
|
assert_eq!(ptr1, ptr2); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn swap() { |
||||||
|
let ts1 = TestStruct { |
||||||
|
name: "Hello 1".into(), |
||||||
|
}; |
||||||
|
|
||||||
|
let ts2 = TestStruct { |
||||||
|
name: "Hello 2".into(), |
||||||
|
}; |
||||||
|
|
||||||
|
// Make an AtomPtr with some data
|
||||||
|
let ptr = AtomPtr::new(ts1.clone()); |
||||||
|
assert_eq!(ptr.get_ref().name, "Hello 1".to_string()); |
||||||
|
|
||||||
|
// Swap the data
|
||||||
|
let still_ts1 = ptr.swap(ts2); |
||||||
|
assert_eq!(ptr.get_ref().name, "Hello 2".to_string()); |
||||||
|
|
||||||
|
// But the old ref is still valid
|
||||||
|
assert_eq!(ts1, *still_ts1.as_ref()); |
||||||
|
} |
Loading…
Reference in new issue