reference-counted-singleton-0.1.1/.cargo_vcs_info.json0000644000000001120000000000000164210ustar { "git": { "sha1": "8e703ddbe01d3d37222beb087efdf1fa0c8d1dda" } } reference-counted-singleton-0.1.1/.gitignore000064400000000000000000000000720000000000000171640ustar 00000000000000/target Cargo.lock /.vscode/ /*.code-workspace .directory reference-counted-singleton-0.1.1/CHANGELOG.md000064400000000000000000000003530000000000000170070ustar 00000000000000# Change log This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.1.1] - 2021-07-28 ### Changed - Updated dependencies: `assert_matches`. ## [0.1.0] - 2021-06-09 ### Added - Initial release. reference-counted-singleton-0.1.1/Cargo.toml0000644000000023130000000000000144240ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "reference-counted-singleton" version = "0.1.1" authors = ["Koutheir Attouchi "] description = "Reference-counted singleton whose protected data can be recreated as needed" documentation = "https://docs.rs/reference-counted-singleton" readme = "README.md" keywords = ["static", "concurrency", "cross-platform", "data-structure", "cache"] categories = ["algorithms", "memory-management", "concurrency", "data-structures", "development-tools::ffi"] license = "MIT" repository = "https://github.com/koutheir/reference-counted-singleton" [dependencies] [dev-dependencies.assert_matches] version = "1.5" [dev-dependencies.serial_test] version = "0.5" reference-counted-singleton-0.1.1/Cargo.toml.orig000064400000000000000000000013600000000000000200640ustar 00000000000000[package] name = "reference-counted-singleton" description = "Reference-counted singleton whose protected data can be recreated as needed" version = "0.1.1" # Remember to update `html_root_url`. authors = ["Koutheir Attouchi "] edition = "2018" readme = "README.md" license = "MIT" keywords = [ "static", "concurrency", "cross-platform", "data-structure", "cache" ] categories = [ "algorithms", "memory-management", "concurrency", "data-structures", "development-tools::ffi" ] repository = "https://github.com/koutheir/reference-counted-singleton" documentation = "https://docs.rs/reference-counted-singleton" [dependencies] [dev-dependencies] assert_matches = { version = "1.5" } serial_test = { version = "0.5" } reference-counted-singleton-0.1.1/LICENSE.txt000064400000000000000000000021100000000000000170120ustar 00000000000000MIT License Copyright (c) 2021 Koutheir Attouchi. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. reference-counted-singleton-0.1.1/README.md000064400000000000000000000020440000000000000164540ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/reference-counted-singleton.svg)](https://crates.io/crates/reference-counted-singleton) [![docs.rs](https://docs.rs/reference-counted-singleton/badge.svg)](https://docs.rs/reference-counted-singleton) [![license](https://img.shields.io/github/license/koutheir/reference-counted-singleton?color=black)](https://raw.githubusercontent.com/koutheir/reference-counted-singleton/master/LICENSE.txt) # Reference-Counted Singleton `RefCountedSingleton` is a reference-counted singleton whose protected data can be recreated as needed. The protected data is created when `RefCountedSingleton::get_or_init` is called. That functions returns an `RCSRef` reference to the singleton. `RCSRef` instances can be cloned as needed. The last `RCSRef` reference drops the data. Calling `RefCountedSingleton::get_or_init` again recreates the data. ## Versioning This project adheres to [Semantic Versioning]. The `CHANGELOG.md` file details notable changes over time. [Semantic Versioning]: https://semver.org/spec/v2.0.0.html reference-counted-singleton-0.1.1/coverage-style.css.patch000064400000000000000000000035110000000000000217360ustar 00000000000000--- a/style.css 2020-12-01 18:44:28.612937212 -0500 +++ b/style.css 2020-12-01 14:25:39.616532796 -0500 @@ -1,22 +1,24 @@ .red { - background-color: #ffd0d0; + background-color: #880000; } .cyan { background-color: cyan; } body { font-family: -apple-system, sans-serif; + color: #ffffff; + background-color: #222222; } pre { margin-top: 0px !important; margin-bottom: 0px !important; } .source-name-title { padding: 5px 10px; border-bottom: 1px solid #dbdbdb; - background-color: #eee; + background-color: #00007f; line-height: 35px; } .centered { display: table; margin-left: left; @@ -52,25 +54,25 @@ font-weight: bold; text-align: left; } .column-entry-yellow { text-align: left; - background-color: #ffffd0; + background-color: #808000; } .column-entry-yellow:hover { background-color: #fffff0; } .column-entry-red { text-align: left; - background-color: #ffd0d0; + background-color: #aa0000; } .column-entry-red:hover { background-color: #fff0f0; } .column-entry-green { text-align: left; - background-color: #d0ffd0; + background-color: #005500; } .column-entry-green:hover { background-color: #f0fff0; } .line-number { @@ -86,11 +88,11 @@ color: #ff3300; } .tooltip { position: relative; display: inline; - background-color: #b3e6ff; + background-color: #006699; text-decoration: none; } .tooltip span.tooltip-content { position: absolute; width: 100px; @@ -126,10 +128,11 @@ padding: 2px 8px; border-collapse: collapse; border-right: solid 1px #eee; border-left: solid 1px #eee; text-align: left; + background-color: #222222; } td pre { display: inline-block; } td:first-child { @@ -139,5 +142,9 @@ border-right: none; } tr:hover { background-color: #f0f0f0; } +a { + color: yellow; + text-decoration: none; +} reference-counted-singleton-0.1.1/coverage.sh000075500000000000000000000047160000000000000173370ustar 00000000000000#!/bin/bash set -e # exit if any command has a non-zero exit code set -u # exit if any referenced variable has not been previously defined unset CDPATH IFS=$' \n\t' : "${BASH_SOURCE?'BASH_SOURCE variable not defined, not running in bash'}" # change into the directory this script resides in cd "$(dirname "${BASH_SOURCE[0]}")" TARGET_DIR="$(pwd)/target/coverage" COV_CARGO_ENV='RUSTFLAGS=-Zinstrument-coverage RUSTDOCFLAGS=-Zinstrument-coverage' COV_CARGO_ENV="$COV_CARGO_ENV CARGO_INCREMENTAL=0" # https://rust-lang.github.io/rustup-components-history/ # rustup +nightly-2021-07-06 component add llvm-tools-preview NIGHTLY_TOOLCHAIN=nightly-2021-07-06 IGNORED_FILE_NAMES='/\.cargo/registry/ /rustc/ /tests.rs$$ ^'"$TARGET_DIR/" # cargo install rustfilt LLVM_COV_FLAGS=$(sed 's: : --ignore-filename-regex=:g' <<< " $IGNORED_FILE_NAMES") LLVM_COV_FLAGS="$LLVM_COV_FLAGS --Xdemangler=rustfilt" RUST_SYS_ROOT=$(rustc "+$NIGHTLY_TOOLCHAIN" --print sysroot) LLVM_PROFDATA=$(find "$RUST_SYS_ROOT" -type f -name llvm-profdata | head -1) LLVM_COV=$(find "$RUST_SYS_ROOT" -type f -name llvm-cov | head -1) mkdir -p "$TARGET_DIR" rm -f "$TARGET_DIR"/test-*.failed "$TARGET_DIR"/*.profraw env $COV_CARGO_ENV 'LLVM_PROFILE_FILE=/dev/null' \ cargo "+$NIGHTLY_TOOLCHAIN" test --tests --workspace \ --target-dir "$TARGET_DIR" --no-run --message-format=json | \ jq -r 'select(.profile.test == true) | .filenames[]' \ > "$TARGET_DIR/tests-paths.list" TESTS_PATHS=$(sed 's:^:--object=:' "$TARGET_DIR/tests-paths.list" | tr '\n' ' ') #grcov $(sed 's:^:--binary-path=:g' "$TARGET_DIR/tests-paths.list" | tr '\n' ' ') -s "$(pwd)" --parallel --llvm -t html --branch --ignore-not-existing -o "$TARGET_DIR/grcov" "$(pwd)" env $COV_CARGO_ENV "LLVM_PROFILE_FILE=$TARGET_DIR/%m.profraw" \ cargo "+$NIGHTLY_TOOLCHAIN" test --tests --workspace \ --target-dir "$TARGET_DIR" \ || touch "$TARGET_DIR/test-workspace.failed" $LLVM_PROFDATA merge --sparse \ "--output=$TARGET_DIR/coverage.profdata" "$TARGET_DIR"/*.profraw $LLVM_COV export $LLVM_COV_FLAGS --format=lcov \ "--instr-profile=$TARGET_DIR/coverage.profdata" \ $TESTS_PATHS > "$TARGET_DIR/lcov.info" $LLVM_COV show $LLVM_COV_FLAGS --format=html --show-line-counts-or-regions \ --show-instantiations "--output-dir=$TARGET_DIR" \ "--instr-profile=$TARGET_DIR/coverage.profdata" $TESTS_PATHS patch "--directory=$TARGET_DIR" < 'coverage-style.css.patch' test '!' -f "$TARGET_DIR/test-workspace.failed" reference-counted-singleton-0.1.1/src/lib.rs000064400000000000000000000123560000000000000171070ustar 00000000000000#![doc(html_root_url = "https://docs.rs/reference-counted-singleton/0.1.1")] /*! [`RefCountedSingleton`] is a reference-counted singleton whose protected data can be recreated as needed. The protected data is created when [`RefCountedSingleton::get_or_init`] is called. That functions returns an [`RCSRef`] reference to the singleton. [`RCSRef`] instances can be cloned as needed. The last [`RCSRef`] reference drops the data. Calling [`RefCountedSingleton::get_or_init`] again recreates the data. */ #[cfg(test)] mod tests; use std::error::Error; use std::hash::{Hash, Hasher}; use std::mem::ManuallyDrop; use std::ops::Deref; use std::sync::{Arc, Mutex}; /// A reference-counted singleton whose protected data can be recreated /// as needed. /// /// The protected data is created when [`RefCountedSingleton::get_or_init`] /// is called. /// That functions returns an [`RCSRef`] reference to the singleton. /// /// [`RCSRef`] instances can be cloned as needed. /// The last [`RCSRef`] reference drops the data. /// Calling [`RefCountedSingleton::get_or_init`] again recreates the data. #[derive(Debug)] pub struct RefCountedSingleton(Mutex>>); impl Default for RefCountedSingleton { fn default() -> Self { Self(Mutex::new(None)) } } impl RefCountedSingleton { /// Return a counted reference to the protected data if such data exists, /// otherwise creates a new instance of the data by calling `creator()`. /// /// If the lock is poisoned, then this returns `Err(None)`. /// If `creator()` returns an error `err`, then this returns /// `Err(Some(err))`. pub fn get_or_init( &'_ self, creator: impl FnOnce() -> Result, ) -> Result, Option> { if let Ok(mut value) = self.0.lock() { match *value { // Data is not created. None => match creator() { Ok(data) => { // We created a new instance. let data = Arc::new(data); let data_ref = Arc::clone(&data); *value = Some(data); Ok(RCSRef { data: ManuallyDrop::new(data_ref), rcs: &self, }) } // Failed to create a new instance of the data. Err(err) => Err(Some(err)), }, // Data is already created. Return a new reference. Some(ref data) => Ok(RCSRef { data: ManuallyDrop::new(Arc::clone(data)), rcs: &self, }), } } else { Err(None) // The mutex was poisoned. } } /// Return a counted reference to the protected data if such data exists. /// /// If such data is not instantiated, or the lock is poisoned, then this /// returns `None`. pub fn get(&'_ self) -> Option> { self.0.lock().ok().and_then(|value| { value.as_ref().map(|data| RCSRef { data: ManuallyDrop::new(Arc::clone(data)), rcs: &self, }) }) } } /// Read-only counted reference to an instance of [`RefCountedSingleton`]. #[derive(Debug)] pub struct RCSRef<'t, T> { data: ManuallyDrop>, rcs: &'t RefCountedSingleton, } impl<'t, T: PartialEq> PartialEq for RCSRef<'t, T> { fn eq(&self, other: &Self) -> bool { self.data.as_ref().deref().eq(other.data.as_ref().deref()) } } impl<'t, T: Eq> Eq for RCSRef<'t, T> {} impl<'t, T: PartialOrd> PartialOrd for RCSRef<'t, T> { fn partial_cmp(&self, other: &Self) -> Option { self.data .as_ref() .deref() .partial_cmp(other.data.as_ref().deref()) } } impl<'t, T: Ord> Ord for RCSRef<'t, T> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.data.as_ref().deref().cmp(other.data.as_ref().deref()) } } impl<'t, T: Hash> Hash for RCSRef<'t, T> { fn hash(&self, state: &mut H) { self.data.as_ref().deref().hash(state) } } impl<'t, T> Deref for RCSRef<'t, T> { type Target = T; fn deref(&self) -> &T { self.data.deref().deref() } } impl<'t, T> Clone for RCSRef<'t, T> { fn clone(&self) -> Self { Self { data: ManuallyDrop::new(Arc::clone(&self.data)), rcs: self.rcs, } } } impl<'t, T> Drop for RCSRef<'t, T> { fn drop(&mut self) { // Drop our own counted reference. // SAFETY: `self.data` is not used after this. unsafe { ManuallyDrop::drop(&mut self.data) }; if let Ok(mut value) = self.rcs.0.lock() { if let Some(data) = value.take() { match Arc::try_unwrap(data) { // Singleton locked, and there are no more counted references to it. // Destroy the singleton. Ok(data) => drop(data), // Singleton locked, but there are other counted references to it. // Put the singleton data back. Err(data) => *value = Some(data), } } } } } reference-counted-singleton-0.1.1/src/tests.rs000064400000000000000000000105140000000000000174750ustar 00000000000000#![cfg(test)] use std::collections::HashSet; use std::hash::{Hash, Hasher}; use std::mem::MaybeUninit; use std::sync::{atomic, atomic::AtomicI32, Once}; use std::{cmp, io}; #[derive(Debug)] struct T1<'t>(&'t AtomicI32); impl<'t> T1<'t> { fn new(counter: &'t AtomicI32) -> io::Result { counter.store(1, atomic::Ordering::Release); Ok(Self(counter)) } } impl<'t> Drop for T1<'t> { fn drop(&mut self) { self.0.store(-1, atomic::Ordering::Release); } } impl<'t> PartialEq for T1<'t> { fn eq(&self, other: &Self) -> bool { self.0 .load(atomic::Ordering::Acquire) .eq(&other.0.load(atomic::Ordering::Acquire)) } } impl<'t> Eq for T1<'t> {} impl<'t> PartialOrd for T1<'t> { fn partial_cmp(&self, other: &Self) -> Option { self.0 .load(atomic::Ordering::Acquire) .partial_cmp(&other.0.load(atomic::Ordering::Acquire)) } } impl<'t> Ord for T1<'t> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0 .load(atomic::Ordering::Acquire) .cmp(&other.0.load(atomic::Ordering::Acquire)) } } impl<'t> Hash for T1<'t> { fn hash(&self, state: &mut H) { self.0.load(atomic::Ordering::Acquire).hash(state) } } static T1_S_VALUE: AtomicI32 = AtomicI32::new(0); static T1_S_INIT: Once = Once::new(); static mut T1_S: MaybeUninit>> = MaybeUninit::uninit(); fn get_or_init_t2_s() -> &'static super::RefCountedSingleton> { T1_S_INIT.call_once(|| unsafe { T1_S = MaybeUninit::new(super::RefCountedSingleton::default()); }); unsafe { T1_S.as_ptr().as_ref().unwrap() } } #[test] fn ref_counted_singleton_static() { T1_S_VALUE.store(1000, atomic::Ordering::Release); let creator = || T1::new(&T1_S_VALUE); let s = get_or_init_t2_s(); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), 1000); assert!(s.get().is_none()); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), 1000); let r1 = s.get_or_init(creator).unwrap(); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), 1); let r2 = r1.clone(); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), 1); drop(r1); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), 1); drop(r2); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), -1); T1_S_VALUE.store(2000, atomic::Ordering::Release); assert!(s.get().is_none()); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), 2000); let r = s.get_or_init(creator).unwrap(); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), 1); drop(r); assert_eq!(T1_S_VALUE.load(atomic::Ordering::Acquire), -1); } #[test] fn ref_counted_singleton_new() { let value = AtomicI32::new(42); let creator = || T1::new(&value); let s = super::RefCountedSingleton::::default(); assert_eq!(value.load(atomic::Ordering::Acquire), 42); assert!(s.get().is_none()); assert_eq!(value.load(atomic::Ordering::Acquire), 42); let r1 = s.get_or_init(creator).unwrap(); assert_eq!(r1.0.load(atomic::Ordering::Acquire), 1); let r2 = r1.clone(); assert_eq!(r2.0.load(atomic::Ordering::Acquire), 1); assert_eq!(r1, r2); assert_eq!(r1.partial_cmp(&r1), Some(cmp::Ordering::Equal)); assert_eq!(r1.cmp(&r1), cmp::Ordering::Equal); let _ignored = format!("{:?}", &r1); let mut hm = HashSet::new(); hm.insert(r1.clone()); drop(hm); drop(r1); assert_eq!(value.load(atomic::Ordering::Acquire), 1); drop(r2); assert_eq!(value.load(atomic::Ordering::Acquire), -1); value.store(1024, atomic::Ordering::Release); assert!(s.get().is_none()); assert_eq!(value.load(atomic::Ordering::Acquire), 1024); let r = s.get_or_init(creator).unwrap(); assert_eq!(value.load(atomic::Ordering::Acquire), 1); drop(r); assert_eq!(value.load(atomic::Ordering::Acquire), -1); drop(s); assert_eq!(value.load(atomic::Ordering::Acquire), -1); } #[test] fn ref_counted_singleton_error() { let creator = || Err(io::Error::from(io::ErrorKind::Other)); let s = super::RefCountedSingleton::::default(); assert!(s.get().is_none()); assert!(s.get_or_init(creator).is_err()); assert!(s.get_or_init(creator).is_err()); assert!(s.get().is_none()); }