diff --git a/.gitignore b/.gitignore index 91d4552..9f26963 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ **/*.rs.bk **/release .vscode -*.lock \ No newline at end of file +*.lock +*.log +*.ld \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index caa74df..5be4dd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ branches: only: - master + - release language: rust @@ -10,44 +11,114 @@ rust: # build nightly only for the time beeing - nightly -matrix: - fast_finish: true +# increase build speed by caching installed cargo dependencies +cache: cargo + +# define the stages and their order +stages: + - compile + - test + - publish_dry + - name: prepare_release + if: branch = master AND type != pull_request + - name: deploy + if: branch = release AND type != pull_request + - name: publish + if: branch = release AND type != pull_request + +jobs: include: - - name: "build 64Bit" + - stage: compile + name: "Compile The Crate" install: - - sudo apt-get install gcc-aarch64-linux-gnu + - sudo apt-get install -y gcc-aarch64-linux-gnu - cargo install cargo-xbuild - cargo install cargo-make - - rustup target add aarch64-unknown-linux-gnu + - rustup target add aarch64-unknown-none - rustup component add rust-src - rustup component add llvm-tools-preview # if we not build a PR we remove the patch of the dependencies to their github repo's - - 'if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then sed -i "/^\[patch\.crates-io\]$/ {N; s/^.*//g}" Cargo.toml; fi' - script: cargo make --profile a64-travis pi3 + - 'if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then sed -i "/\[patch\.crates-io\]/,$ s/^ruspiro-.*\(git\|path\).*//g" Cargo.toml; fi' + script: cargo make --profile travis pi3 - - name: "build 32Bit" + - stage: test + name: "Run Doc Tests" install: - - sudo apt-get install gcc-arm-linux-gnueabihf - - cargo install cargo-xbuild - cargo install cargo-make - - rustup target add armv7-unknown-linux-gnueabihf - - rustup component add rust-src - - rustup component add llvm-tools-preview # if we not build a PR we remove the patch of the dependencies to their github repo's - - 'if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then sed -i "/^\[patch\.crates-io\]$/ {N; s/^.*//g}" Cargo.toml; fi' - script: cargo make --profile a32 pi3 - - - name: "unit tests" + - 'if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then sed -i "/{^\[patch\.crates-io\] /{:a;N;/\Z}/!ba};/^ruspiro-.*\(git\|path\).*/d" Cargo.toml; fi' + script: cargo make doctest --profile dummy + - stage: test + name: "Run Unit Tests" install: + - cargo install cargo-make # if we not build a PR we remove the patch of the dependencies to their github repo's - - 'if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then sed -i "/^\[patch\.crates-io\]$/ {N; s/^.*//g}" Cargo.toml; fi' - script: cargo test --tests + - 'if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then sed -i "/{^\[patch\.crates-io\] /{:a;N;/\Z}/!ba};/^ruspiro-.*\(git\|path\).*/d" Cargo.toml; fi' + script: cargo make unittest --profile dummy - - name: "doc tests" + - stage: publish_dry + name: "Run Cargo Publish Dry-Run" install: - # if we not build a PR we remove the patch of the dependencies to their github repo's - - 'if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then sed -i "/^\[patch\.crates-io\]$/ {N; s/^.*//g}" Cargo.toml; fi' - - cat Cargo.toml - script: cargo test --doc + - sudo apt-get install -y gcc-aarch64-linux-gnu + - cargo install cargo-xbuild + - cargo install cargo-make + - rustup target add aarch64-unknown-none + - rustup component add rust-src + - rustup component add llvm-tools-preview + script: cargo make publish_dry --profile travis + + - stage: prepare_release + name: "Create PR against the release branch" + script: + - 'curl -H ''Authorization: Token ''"$GIT_API_TOKEN"'''' -X POST -H ''Content-type: application/json'' --data ''{"title":"Prepare Release and crates.io publishing", "head":"master", "base":"release", "draft":false, "body":"Automatic PR to the release branch as preperation to publish the library"}'' https://api.github.com/repos/$TRAVIS_REPO_SLUG/pulls > /dev/null' - \ No newline at end of file + - stage: deploy + name: "Create GitHub release" + script: echo "creating github release" + before_deploy: + # extract current crate version from argo.toml + - export CRATE_VERSION=v`sed -En 's/^version.*=.*\"(.*)\".*$/\1/p' < Cargo.toml` + # retrieve last release version from github + - export LAST_VERSION="$(curl --silent "https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')" + # use default version if none yet published (required for proper release note extraction) + - export LAST_VERSION=${LAST_VERSION:-v0.0.0} + - echo $CRATE_VERSION + - echo $LAST_VERSION + - git config --local user.name "2ndTaleStudio" + - git config --local user.email "43264484+2ndTaleStudio@users.noreply.github.com" + # create the TAG required for the release + - git tag $CRATE_VERSION -m "$CRATE_VERSION" + # extract the release notes of the current release from the changelog + - sed -En '/##.*:.*:.*'"$LAST_VERSION"'/q;p' CHANGELOG.md > RELEASENOTES.md + - sed -i -e 's/^# Changelog/# Release Notes/g' RELEASENOTES.md + deploy: + provider: releases + # use dpl v2 version for deployments to support the release_notes_file option + edge: true + api_key: "$GIT_API_TOKEN" + name: "$CRATE_VERSION" + release_notes_file: "RELEASENOTES.md" + file: "RELEASENOTES.md" + skip_cleanup: true + on: + branch: release + + - stage: publish + name: "Run Cargo Publish" + install: + - sudo apt-get install -y gcc-aarch64-linux-gnu + - cargo install cargo-xbuild + - cargo install cargo-make + - rustup target add aarch64-unknown-none + - rustup component add rust-src + - rustup component add llvm-tools-preview + # extract current crate version from argo.toml + - export CRATE_VERSION=`sed -En 's/^version.*=.*\"(.*)\".*$/\1/p' < Cargo.toml` + # before actually publishing replace the final version for doc and repository in the Crago.toml + - sed -i -e 's/||VERSION||/'$CRATE_VERSION'/g' Cargo.toml + # also update the version in the lib.rs doc root url + - sed -i -e 's/||VERSION||/'$CRATE_VERSION'/g' src/lib.rs + # and the README.md + - sed -i -e 's/||VERSION||/'$CRATE_VERSION'/g' README.md + # publish with token and dirty flag as we just updated some files and won't commit them back to the branch + script: cargo make publish --profile travis --env CRATES_TOKEN="$CRATES_TOKEN" > /dev/null diff --git a/CHANGELOG.md b/CHANGELOG.md index 83cbef0..6defc8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog -## :banana: v0.4.3 - - ### :detective: Fixes - - remove `asm!` macro usages and replace with `llvm_asm!` - - use `cargo make` to stabilize cross-platform builds \ No newline at end of file + +## :peach: v0.4.0 + +- ### :bulb: Features + + - Introduce the ability to lazylie initialize the value stored inside the `Singleton` using a closure. The initialization is evaluated on first access to the `Singleton` contents. + +- ### :wrench: Maintenance + + - Enable proper and stable pipeline to support release and publishing process + +## :banana: v0.3.1 + +- ### :detective: Fixes + + - remove `asm!` macro usages and replace with `llvm_asm!` + - use `cargo make` to stabilize cross-platform builds diff --git a/Cargo.toml b/Cargo.toml index 6f14088..d56053d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "ruspiro-singleton" authors = ["André Borrmann "] -version = "0.3.1" # remember to update html_root_url +version = "0.4.0" # remember to update html_root_url description = "Simple and easy to use singleton pattern" license = "Apache-2.0" -repository = "https://github.com/RusPiRo/ruspiro-singleton/tree/v0.3.1" -documentation = "https://docs.rs/ruspiro-singleton/0.3.1" +repository = "https://github.com/RusPiRo/ruspiro-singleton/tree/v||VERSION||" +documentation = "https://docs.rs/ruspiro-singleton/||VERSION||" readme = "README.md" keywords = ["RusPiRo", "singleton", "raspberrypi"] categories = ["no-std", "embedded"] @@ -18,10 +18,13 @@ maintenance = { status = "actively-developed" } [lib] +[dev-dependencies] + [dependencies] -ruspiro-lock = "0.3" +ruspiro-lock = "0.4.0" [features] +ruspiro_pi3 = [ ] [patch.crates-io] -ruspiro-lock = { git = "https://github.com/RusPiRo/ruspiro-lock.git" } \ No newline at end of file +ruspiro-lock = { git = "https://github.com/RusPiRo/ruspiro-lock.git" } diff --git a/LICENSE b/LICENSE-APACHE similarity index 100% rename from LICENSE rename to LICENSE-APACHE diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..11fdb53 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 André Borrmann + +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. diff --git a/Makefile.toml b/Makefile.toml index 57d42bf..61ded7d 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,41 +1,74 @@ #*********************************************************************************************************************** # cargo make tasks to build the example for the Raspberry Pi #*********************************************************************************************************************** -[env.a64] + +# AARCH64 specific profile environment varialbles +[env.development] CC = "aarch64-none-elf-gcc" AR = "aarch64-none-elf-ar" -OC = "aarch64-none-elf-objcopy" CFLAGS = "-march=armv8-a -Wall -O3 -nostdlib -nostartfiles -ffreestanding -mtune=cortex-a53" RUSTFLAGS = "-C linker=${CC} -C target-cpu=cortex-a53 -C link-arg=-nostartfiles -C link-arg=-T./link64.ld" -BUILD_TARGET = "aarch64-unknown-linux-gnu" - -[env.a32] -CC = "arm-none-eabi-gcc" -AR = "arm-none-eabi-ar" -OC = "arm-none-eabi-objcopy" -CFLAGS = "-mcpu=cortex-a53 -march=armv7-a -mfpu=neon -mfloat-abi=softfp -Wall -O3 -nostdlib -nostartfiles" -RUSTFLAGS = "-C linker=${CC} -C target-cpu=cortex-a53 -C link-arg=-nostartfiles" -BUILD_TARGET = "armv7a-none-eabi" - -# Travis CI need a different CC/AR alltogether as the aarch64-none-elf is not available there as it seems -[env.a64-travis] +BUILD_TARGET = "aarch64-unknown-none" + +# AARCH64 specific Travis CI env. variables. "aarch64-none-elf" is not available there as it seems +[env.travis] CC = "aarch64-linux-gnu-gcc" AR = "aarch64-linux-gnu-ar" -OC = "aarch64-linux-gnu-objcopy" CFLAGS = "-march=armv8-a -Wall -O3 -nostdlib -nostartfiles -ffreestanding -mtune=cortex-a53" -RUSTFLAGS = "-C linker=${CC} -C target-cpu=cortex-a53 -C link-arg=-nostartfiles" -BUILD_TARGET = "aarch64-unknown-linux-gnu" +RUSTFLAGS = "-C linker=${CC} -C target-cpu=cortex-a53 -C link-arg=-nostartfiles -C link-arg=-T./link64.ld" +BUILD_TARGET = "aarch64-unknown-none" [tasks.xbuild] command = "cargo" -args = ["xbuild", "--target", "${BUILD_TARGET}", "--release", "--features", "${FEATURES}"] +args = ["build", "--target", "${BUILD_TARGET}", "--release", "--features", "${FEATURES}"] [tasks.pi3] -env = { FEATURES = "" } -run_task = [ - { name = "xbuild" } -] +env = { FEATURES = "ruspiro_pi3" } +run_task = "xbuild" + +[tasks.clippy] +env = { FEATURES = "ruspiro_pi3" } +command = "cargo" +args = ["clippy", "--target", "${BUILD_TARGET}", "--features", "${FEATURES}"] + +[tasks.doc] +env = { FEATURES = "ruspiro_pi3" } +command = "cargo" +args = ["doc", "--target", "${BUILD_TARGET}", "--features", "${FEATURES}", "--open"] + +[tasks.unittest] +env = { FEATURES = "ruspiro_pi3" } +command = "cargo" +args = ["test", "--tests", "--features", "${FEATURES}"] + + +[tasks.doctest] +env = { FEATURES = "ruspiro_pi3" } +command = "cargo" +args = ["test", "--doc", "--features", "${FEATURES}"] + +[tasks.publish_dry] +env = { FEATURES = "ruspiro_pi3" } +command = "cargo" +args = ["publish", "--dry-run", "--target", "${BUILD_TARGET}", "--features", "${FEATURES}"] + +[tasks.publish] +env = { FEATURES = "ruspiro_pi3" } +command = "cargo" +args = ["publish", "--token", "${CRATES_TOKEN}", "--allow-dirty", "--target", "${BUILD_TARGET}", "--features", "${FEATURES}"] [tasks.clean] command = "cargo" -args = ["clean"] \ No newline at end of file +args = ["clean"] + +[tasks.qemu-test-objcopy] +command = "aarch64-none-elf-objcopy" +args = ["-O", "binary", "${CARGO_MAKE_TASK_ARGS}", "./target/kernel-test.img"] + +[tasks.qemu-test] +script = [ + "qemu-system-aarch64 -semihosting -display none -M raspi3 -kernel ./target/kernel-test.img -serial null -serial stdio -d int,mmu -D qemu-test.log" +] +dependencies = [ + "qemu-test-objcopy" +] \ No newline at end of file diff --git a/README.md b/README.md index 627bfaf..a9c6b2c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This crate provides an easy to use singleton pattern that is safe to be used across cores. -[![Travis-CI Status](https://api.travis-ci.org/RusPiRo/ruspiro-singleton.svg?branch=master)](https://travis-ci.org/RusPiRo/ruspiro-singleton) +[![Travis-CI Status](https://api.travis-ci.com/RusPiRo/ruspiro-singleton.svg?branch=master)](https://travis-ci.com/RusPiRo/ruspiro-singleton) [![Latest Version](https://img.shields.io/crates/v/ruspiro-singleton.svg)](https://crates.io/crates/ruspiro-singleton) [![Documentation](https://docs.rs/ruspiro-singleton/badge.svg)](https://docs.rs/ruspiro-singleton) [![License](https://img.shields.io/crates/l/ruspiro-singleton.svg)](https://github.com/RusPiRo/ruspiro-singleton#license) @@ -13,10 +13,10 @@ To use this crate simply add the dependency to your ``Cargo.toml`` file: ```toml [dependencies] -ruspiro-singleton = "0.3" +ruspiro-singleton = "||VERSION||" ``` -Once done on any rust file you can define a static variable as singleton of any type for safe cross core access like so: +Once done on any rust file you can define a static variable as `Singleton` of any type for safe cross core access in two different ways. The first variant requires to provide an instance of the data to be wrapped inside the singleton while defining the same. ```rust // define the static variable @@ -33,35 +33,63 @@ impl Demo { Demo { count: 0, } - }; + } } +``` -fn main() { - // safely use the singleton inside the closure passed to [take_for]. - DEMO.take_for(|d| { - d.count += 10; - }); +The second variant allows to pass a closure to the initialization of the `Singleton` that will be evaluated at first access to the contents of it. - // you may also return a value from the singleton to work with it after the safe singleton access - let _current = DEMO.take_for(|d| { - d.count - }); +> !HINT! +> Safe lazy initialization is ensured using atomics. On the Raspberry Pi atmomic operations require the *MMU* to be configured and active. Otherwise the executing CPU core will hang when trying to execute the atomic operation. + +```rust +// define the static variable with an inizialization closure +static DEMO:Singleton> = Singleton::lazy(&|| { + Box::new( + Demo::new() + ) +}); + +// define the type to be accessible as singleton +struct Demo { + pub count: u32, +} + +// implement the type that should provided as singlton +impl Demo { + pub const fn new() -> Self { + Demo { + count: 0, + } + }; } ``` -If the singleton does only require ``read only`` access a non-blocking function could be used: +To use the data sealed with the `Singleton` call either of the two methods `with_ref` and `with_mut` providing a closure accessing the data immuable or mutable. ```rust fn main() { - DEMO.use_for(|d| { - // d is available with immutable access only in this scenario - println!("current count: {}", d.count); + // safely use the singleton inside the closure passed to [with_mut] to update it's contents + DEMO.with_mut(|d| { + d.count += 10; + }); + + // safely use the singleton inside the closure passed to [with_ref] if read-only access is required + DEMO.with_mut(|d| { + println!("Value: {}", d.count); + }); + + // you may also return a value from the singleton to work with it after the safe singleton access + let val = DEMO.with_ref(|d| { + if d.count != 0 { + true + } else { + false + } }); } ``` -## Limitation -The current version of the implementation does not allow *lazy* initialization. Only ``const fn`` functions can be used to initialize the structure instance that should be wrapped by the ``Singleton``. - ## License -Licensed under Apache License, Version 2.0, ([LICENSE](LICENSE) or http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file + +Licensed under Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) or MIT ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)) at your choice. diff --git a/src/lazy.rs b/src/lazy.rs new file mode 100644 index 0000000..408c09b --- /dev/null +++ b/src/lazy.rs @@ -0,0 +1,92 @@ +/*********************************************************************************************************************** + * Copyright (c) 2019 by the authors + * + * Author: André Borrmann + * License: MIT / Apache License 2.0 + **********************************************************************************************************************/ + +//! # Lazy Singleton initialization +//! + +use core::cell::UnsafeCell; +use ruspiro_lock::Spinlock; + +/// A wrapper that enables lazy initialization of the value stored within the `Singleton` +pub struct LazyValue { + /// the actual value that shall be provided as a singleton + inner: UnsafeCell>, + /// the closure used to initialize the singleton + init: Option<&'static dyn Fn() -> T>, + /// A lock that secures the lazy update of the inner value in case it happens across cores + lock: Spinlock, +} + +impl LazyValue { + /// create a new [LazySingleton] where the value is already available + pub const fn with_value(value: T) -> Self { + Self { + inner: UnsafeCell::new(Some(value)), + init: None, + lock: Spinlock::new(), + } + } + + /// create a new [LazySingleton] where the actual value will be lazily created at first access + pub const fn with_init(init: &'static F) -> Self + where + F: Fn() -> T, + T: 'static, + { + Self { + inner: UnsafeCell::new(None), + init: Some(init), + lock: Spinlock::new(), + } + } + + fn set(&self, value: T) -> Result<(), T> { + let inner = unsafe { &*self.inner.get() }; + if inner.is_some() { + return Err(value); + } + // update the actual value of LazyValue. This is safe as this is the + // only place this is updated and we checked the value is actually None + // before + let inner = unsafe { &mut *self.inner.get() }; + *inner = Some(value); + + Ok(()) + } + + fn init(&self) { + // locking the spinlock to ensure the initialization really happens + // exclusively + self.lock.aquire(); + // if we could aquire the lock there is a probability that the initialization was kind of a longer running + // task and thus has already happened while waiting for the lock. So check if the value is still not initialized + if unsafe { &*self.inner.get() }.is_none() { + let init = self.init.unwrap(); + let value = init(); + assert!(self.set(value).is_ok(), "LazyValue initialized twice"); + } + self.lock.release(); + } + + pub fn get(&self) -> &T { + if let Some(inner) = unsafe { &*self.inner.get() }.as_ref() { + inner + } else { + self.init(); + unsafe { &*self.inner.get() }.as_ref().unwrap() + } + } + + pub fn get_mut(&self) -> &mut T { + if let Some(inner) = unsafe { &mut *self.inner.get() }.as_mut() { + inner + } else { + self.init(); + unsafe { &mut *self.inner.get() }.as_mut().unwrap() + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 71ebae8..d36df5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,138 +2,135 @@ * Copyright (c) 2019 by the authors * * Author: André Borrmann - * License: Apache License 2.0 + * License: MIT / Apache License 2.0 **********************************************************************************************************************/ -#![doc(html_root_url = "https://docs.rs/ruspiro-singleton/0.3.1")] +#![doc(html_root_url = "https://docs.rs/ruspiro-singleton/||VERSION||")] #![no_std] +#![feature(const_fn)] + //! # Singleton pattern implementation //! -//! Provide a cross core synchronisation safe singleton implementation pattern +//! Provide a cross core synchronisation safe singleton implementation pattern. The `Singleton` is intended to be used +//! to declare crate level static variables that require safe access accross cores. This is helpful where the data +//! structure used within the `Singleton` represents a peripheral where the crate shall only hand out a single instance +//! to safely represent to unique existance of the peripheral. +//! +//! # HINT +//! Safe lazy initialization is ensured using atomics. On the Raspberry Pi atmomic operations require the *MMU* to be +//! configured and active. Otherwise the executing CPU core will hang when trying to execute the atomic operation. //! //! # Example -//! ``` -//! use ruspiro_singleton::*; +//! ```no_run +//! # use ruspiro_singleton::*; +//! // define the static variable with an inizialization closure +//! static FOO:Singleton = Singleton::new(20); //! -//! static FOO: Singleton = Singleton::new(Foo::new(0)); +//! // define the static variable with an inizialization closure +//! static DEMO:Singleton> = Singleton::lazy(&|| { +//! Box::new( +//! Demo::new() +//! ) +//! }); //! -//! struct Foo { -//! count: u32, +//! // define the type to be accessible as singleton +//! struct Demo { +//! pub count: u32, //! } //! -//! impl Foo { -//! pub const fn new(initial_count: u32) -> Self { -//! Foo { -//! count: initial_count, +//! // implement the type that should provided as singlton +//! impl Demo { +//! pub const fn new() -> Self { +//! Demo { +//! count: 0, //! } //! } -//! -//! pub fn count(&self) -> u32 { -//! self.count -//! } -//! -//! pub fn add_count(&mut self, value: u32) -> u32 { -//! self.count += value; -//! self.count -//! } //! } //! -//! fn main () { -//! let counter = FOO.take_for( |foo| { -//! println!("secure access to the singleton"); -//! // do something with the singleton, it is mutable inside 'take_for' -//! let c = foo.add_count(1); -//! // and return any value, the return value of take_for is inferred from the return -//! // value of the closure given to this function. -//! c +//! fn main() { +//! // safely use the singleton inside the closure passed to [with_mut] to update it's contents +//! DEMO.with_mut(|d| { +//! d.count += 10; //! }); //! -//! println!("successfull {}", counter); -//! } -//! ``` -//! -//! In case only immutable access to the contents of the singleton is required the ``use_for`` function -//! can be used. -//! ``` -//! # use ruspiro_singleton::*; -//! # static FOO: Singleton = Singleton::new(Foo::new(0)); -//! # struct Foo { -//! # count: u32, -//! # } -//! # impl Foo { -//! # pub const fn new(initial_count: u32) -> Self { -//! # Foo { -//! # count: initial_count, -//! # } -//! # } -//! # -//! # pub fn count(&self) -> u32 { -//! # self.count -//! # } -//! # } -//! -//! fn main () { -//! let counter = FOO.use_for( |foo| { -//! foo.count() -//! }); +//! // safely use the singleton inside the closure passed to [with_ref] if read-only access is required +//! DEMO.with_mut(|d| { +//! println!("Value: {}", d.count); +//! }); //! -//! println!("current counter: {}", counter); +//! // you may also return a value from the singleton to work with it after the safe singleton access +//! let val = DEMO.with_ref(|d| { +//! if d.count != 0 { +//! true +//! } else { +//! false +//! } +//! }); //! } //! ``` -//! -use core::cell::UnsafeCell; -use ruspiro_lock::Spinlock; + +mod lazy; + +use lazy::LazyValue; +use ruspiro_lock::RWLock; /// The Singleton wrapper stores any type pub struct Singleton { - inner: UnsafeCell, - lock: Spinlock, + /// the inner value wrapping the contained data for safe read/write access + inner: RWLock>, } -// The Singleton need to implement Sync to ensure cross core sync compile check mechanisms -#[doc(hidden)] +// The Singleton need to implement Send & Sync to ensure cross core compile check mechanics +// this is safe as the inner RWLock ensures cross core safety unsafe impl Sync for Singleton {} - -#[doc(hidden)] unsafe impl Send for Singleton {} impl Singleton { - /// Create a new singleton instance to be used in a static variable. Only ``const fn`` constructors are allowed here. - /// If this is not sufficient the ``Singleton`` may be further wrapped by a ``lazy_static!`` available as - /// external crate from [crates.io](https://crates.io/crates/lazy_static) - pub const fn new(data: T) -> Singleton { + /// Create a new [Singleton] instance to be used in a static variable. Only ``const fn`` constructors are allowed + /// here. + /// # Example + /// ```no_run + /// # use ruspiro_singleton::*; + /// static FOO: Singleton = Singleton::new(20); + /// # fn main() {} + /// ``` + pub const fn new(value: T) -> Self { Singleton { - inner: UnsafeCell::new(data), - lock: Spinlock::new(), + inner: RWLock::new(LazyValue::with_value(value)), } } - /// Take the stored singleton for whatever operation and prevent usage by other cores - /// Safe access to the singleton mutable instance is guarantied inside the given closure. - /// + /// Create a new [Singleton] instance passing a closure that will be evaluated at first access to the contents of + /// the singleton that will provide its value /// # Example - /// ``` + /// ```no_run /// # use ruspiro_singleton::*; - /// # static FOO: Singleton = Singleton::new(0); - /// # fn main() { - /// FOO.take_for(|foo| { - /// // do something mutable with [foo] - /// }); - /// # } + /// static FOO: Singleton = Singleton::lazy(&|| String::from("foo")); + /// # fn main() {} /// ``` - pub fn take_for(&self, f: F) -> R + pub const fn lazy(init: &'static F) -> Self + where + F: Fn() -> T, + { + Self { + inner: RWLock::new(LazyValue::with_init(init)), + } + } + + /// Take the stored singleton for whatever operation and prevent usage by other cores + /// Safe access to the singleton mutable instance is guarantied inside the given closure. + /// + pub fn with_mut(&self, f: F) -> R where F: FnOnce(&mut T) -> R, { - // to ensure atomic access to the singleton wrapped resource we aquire a lock before allowing to access - // the same. While the lock is aquired interrupts are disabled. This ensures there are now deadlocks - // possible when a lock is interrupted and the handler tries to aquire the same lock - self.lock.aquire(); + let inner = self.inner.lock(); + // use write lock to mutably access the inner value of the singleton. As long + // as the write lock exists no other write or read lock is possible + let r = f(inner.get_mut()); - let r = f(unsafe { &mut *self.inner.get() }); + // explicitly release the lock befor providing the result of the closure to the caller + drop(inner); - // after processing we can release the lock so other cores can access the singleton as well - // this also re-enables interrupts - self.lock.release(); r } @@ -141,20 +138,18 @@ impl Singleton { /// This access does not enforce any lock nor guarantees safe atomic access to the instance. However, it is usefull /// in read-only access scenarios like inside interrupt handlers. /// - /// # Example - /// ``` - /// # use ruspiro_singleton::*; - /// # static FOO: Singleton = Singleton::new(0); - /// # fn main() { - /// FOO.use_for(|foo| { - /// // do something immutable with [foo] - /// }); - /// # } - /// ``` - pub fn use_for(&self, f: F) -> R + pub fn with_ref(&self, f: F) -> R where F: FnOnce(&T) -> R, { - f(unsafe { &*self.inner.get() }) + let inner = self.inner.read(); + // multiple read locks are possible when accessing the inner data of the singleton + // all read locks are required to be release before the next write lock could happen + let r = f(inner.get()); + + // explicitly release the lock befor providing the result of the closure to the caller + drop(inner); + + r } }