├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.toml ├── Changelog.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── img └── top.png └── src ├── lib.rs ├── relax.rs ├── rw_spinlock.rs └── spinlock.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | tags: 8 | - '*' 9 | schedule: 10 | - cron: '40 4 * * *' # every day at 4:40 11 | pull_request: 12 | 13 | jobs: 14 | test: 15 | name: "Test" 16 | 17 | strategy: 18 | matrix: 19 | platform: [ 20 | ubuntu-latest, 21 | macos-latest, 22 | windows-latest 23 | ] 24 | 25 | runs-on: ${{ matrix.platform }} 26 | timeout-minutes: 15 27 | 28 | steps: 29 | - name: "Checkout Repository" 30 | uses: actions/checkout@v1 31 | 32 | - name: "Print Rust Version" 33 | run: | 34 | rustc -Vv 35 | cargo -Vv 36 | 37 | - name: "Run cargo build" 38 | run: cargo build 39 | 40 | - name: "Run cargo build --all-features" 41 | run: cargo build --all-features 42 | 43 | - name: "Run cargo test" 44 | run: cargo test 45 | 46 | - name: "Run cargo doc" 47 | run: cargo doc 48 | 49 | - name: 'Deny Warnings' 50 | run: cargo rustc -- -D warnings 51 | 52 | - name: "Install Bare Metal Rustup Target" 53 | run: rustup target add thumbv7em-none-eabihf 54 | - name: 'Verify no_std build' 55 | run: cargo build --target thumbv7em-none-eabihf 56 | 57 | check_formatting: 58 | name: "Check Formatting" 59 | runs-on: ubuntu-latest 60 | timeout-minutes: 2 61 | steps: 62 | - uses: actions/checkout@v1 63 | - run: rustup toolchain install nightly --profile minimal --component rustfmt 64 | - run: cargo +nightly fmt -- --check 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spinning_top" 3 | version = "0.3.0" 4 | authors = ["Philipp Oppermann "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "A simple spinlock crate based on the abstractions provided by `lock_api`." 8 | repository = "https://github.com/rust-osdev/spinning_top" 9 | 10 | [package.metadata.docs.rs] 11 | all-features = true 12 | rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] 13 | 14 | [features] 15 | arc_lock = ["lock_api/arc_lock"] 16 | owning_ref = ["lock_api/owning_ref"] 17 | 18 | [dependencies] 19 | lock_api = "0.4.7" 20 | 21 | [dev-dependencies] 22 | rand = "0.8" 23 | 24 | [package.metadata.release] 25 | pre-release-replacements = [ 26 | { file = "Changelog.md", search = "# Unreleased", replace = "# Unreleased\n\n# {{version}} – {{date}}", exactly = 1 }, 27 | ] 28 | pre-release-commit-message = "Release version {{version}}" 29 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | # 0.3.0 – 2023-10-19 4 | 5 | ## Breaking 6 | 7 | * [feat: add backoff feature](https://github.com/rust-osdev/spinning_top/pull/16) 8 | * [chore: remove `const_spinlock` function](https://github.com/rust-osdev/spinning_top/pull/20) 9 | * [chore: remove deprecated `nightly` feature](https://github.com/rust-osdev/spinning_top/pull/21) 10 | 11 | ## Improvements 12 | 13 | * [feat: add `RwSpinlock` readers-writer lock](https://github.com/rust-osdev/spinning_top/pull/18) 14 | * [feat: add `arc_lock` feature and typedefs](https://github.com/rust-osdev/spinning_top/pull/25) 15 | * [perf: inline everything](https://github.com/rust-osdev/spinning_top/pull/17) 16 | * [docs: fix typo](https://github.com/rust-osdev/spinning_top/pull/23) 17 | 18 | ## Other 19 | 20 | * [ci: build with all features](https://github.com/rust-osdev/spinning_top/pull/19) 21 | * [test: don't ignore statics example](https://github.com/rust-osdev/spinning_top/pull/22) 22 | 23 | # 0.2.5 – 2023-02-24 24 | 25 | - Upgrade `lock_api` to 0.4.7. This makes `Spinlock::new` a `const` function without needing nightly rust. 26 | 27 | # 0.2.4 – 2021-05-13 28 | 29 | - Define `MappedSpinlockGuard` alias [#12](https://github.com/rust-osdev/spinning_top/pull/12) 30 | - makes use of `SpinlockGuard::map` easier 31 | 32 | # 0.2.3 – 2021-04-01 33 | 34 | - Fix `spin_loop_hint` warning on Rust 1.51 35 | 36 | # 0.2.2 – 2020-08-24 37 | 38 | - Add owning_ref support ([#7](https://github.com/rust-osdev/spinning_top/pull/7)) 39 | 40 | # 0.2.1 – 2020-07-07 41 | 42 | - Implement `const_spinlock` convenience function ([#5](https://github.com/rust-osdev/spinning_top/pull/5)) 43 | 44 | # 0.2.0 – 2020-07-06 45 | 46 | - **Breaking:** Upgrade `lock_api` to 0.4.0 ([#3](https://github.com/rust-osdev/spinning_top/pull/3)) 47 | 48 | # 0.1.1 49 | 50 | - Implement `try_lock_weak` for use in `lock` loop ([#4](https://github.com/rust-osdev/spinning_top/pull/4)) 51 | 52 | # 0.1.0 53 | 54 | - Initial Commit 55 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Philipp Oppermann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spinning_top 2 | 3 | image of a spinning top 4 | 5 | [![Build Status](https://github.com/rust-osdev/spinning_top/workflows/Build/badge.svg)](https://github.com/rust-osdev/spinning_top/actions?query=workflow%3ABuild) [![Docs.rs Badge](https://docs.rs/spinning_top/badge.svg)](https://docs.rs/spinning_top/) 6 | 7 | A simple spinlock crate based on the abstractions provided by [`lock_api`]. 8 | 9 | [`lock_api`]: https://docs.rs/lock_api/ 10 | 11 | ## Example 12 | 13 | First, import the crate as a dependency in your `Cargo.toml`. Then you can use it in the following way: 14 | 15 | ```rust 16 | use spinning_top::Spinlock; 17 | 18 | fn main() { 19 | // Wrap some data in a spinlock 20 | let data = String::from("Hello"); 21 | let spinlock = Spinlock::new(data); 22 | make_uppercase(&spinlock); // only pass a shared reference 23 | // We have ownership of the spinlock, so we can extract the data without locking 24 | // Note: this consumes the spinlock 25 | let data = spinlock.into_inner(); 26 | assert_eq!(data.as_str(), "HELLO"); 27 | } 28 | 29 | fn make_uppercase(spinlock: &Spinlock) { 30 | // Lock the spinlock to get a mutable reference to the data 31 | let mut locked_data = spinlock.lock(); 32 | assert_eq!(locked_data.as_str(), "Hello"); 33 | locked_data.make_ascii_uppercase(); 34 | 35 | // the lock is automatically freed at the end of the scope 36 | } 37 | ``` 38 | 39 | `Spinlock::new` is a `const` function. This makes the `Spinlock` type 40 | usable in statics: 41 | 42 | ```rust 43 | use spinning_top::Spinlock; 44 | 45 | static DATA: Spinlock = Spinlock::new(0); 46 | 47 | fn main() { 48 | let mut data = DATA.lock(); 49 | *data += 1; 50 | assert_eq!(*data, 1); 51 | } 52 | ``` 53 | 54 | ## License 55 | 56 | Licensed under either of 57 | 58 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 59 | http://www.apache.org/licenses/LICENSE-2.0) 60 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 61 | 62 | at your option. 63 | 64 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 65 | -------------------------------------------------------------------------------- /img/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-osdev/spinning_top/3d0c3c6c4bfdf49d2cd905ee875c3bd4f521abd8/img/top.png -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides simple spinlocks based on the abstractions provided by the [`lock_api`] crate. 2 | //! 3 | //! [`lock_api`]: https://docs.rs/lock_api/ 4 | //! 5 | //! # Examples 6 | //! 7 | //! Use [`Spinlock`] for mutual exclusion: 8 | //! 9 | //! ``` 10 | //! use spinning_top::Spinlock; 11 | //! 12 | //! fn main() { 13 | //! let data = String::from("Hello"); 14 | //! // Wrap some data in a spinlock 15 | //! let spinlock = Spinlock::new(data); 16 | //! 17 | //! // Lock the spinlock to get a mutex guard for the data 18 | //! let mut locked_data = spinlock.lock(); 19 | //! // The guard implements the `Deref` trait, so we can use it like a `&String` 20 | //! assert_eq!(locked_data.as_str(), "Hello"); 21 | //! // It also implements `DerefMut` so mutation is possible too. This is safe 22 | //! // because the spinlock ensures mutual exclusion 23 | //! locked_data.make_ascii_uppercase(); 24 | //! assert_eq!(locked_data.as_str(), "HELLO"); 25 | //! 26 | //! // the guard automatically frees the lock at the end of the scope 27 | //! } 28 | //! ``` 29 | //! 30 | //! Use [`RwSpinlock`] if you need a readers-writer lock: 31 | //! 32 | //! ``` 33 | //! use spinning_top::RwSpinlock; 34 | //! 35 | //! let lock = RwSpinlock::new(5); 36 | //! 37 | //! // many reader locks can be held at once 38 | //! { 39 | //! let r1 = lock.read(); 40 | //! let r2 = lock.read(); 41 | //! assert_eq!(*r1, 5); 42 | //! assert_eq!(*r2, 5); 43 | //! } // read locks are dropped at this point 44 | //! 45 | //! // only one write lock may be held, however 46 | //! { 47 | //! let mut w = lock.write(); 48 | //! *w += 1; 49 | //! assert_eq!(*w, 6); 50 | //! } // write lock is dropped here 51 | //! ``` 52 | 53 | #![cfg_attr(not(test), no_std)] 54 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 55 | #![warn(missing_docs)] 56 | #![warn(missing_debug_implementations)] 57 | 58 | /// The spinlock implemenation is based on the abstractions provided by the `lock_api` crate. 59 | pub use lock_api; 60 | 61 | pub use rw_spinlock::{BackoffRwSpinlock, RawRwSpinlock, RwSpinlock}; 62 | pub use spinlock::{BackoffSpinlock, RawSpinlock, Spinlock}; 63 | 64 | /// Type aliases for guards. 65 | pub mod guard { 66 | #[cfg(feature = "arc_lock")] 67 | pub use super::rw_spinlock::{ 68 | ArcBackoffRwSpinlockReadGuard, ArcBackoffRwSpinlockUpgradableReadGuard, 69 | ArcBackoffRwSpinlockWriteGuard, ArcRwSpinlockReadGuard, ArcRwSpinlockUpgradableReadGuard, 70 | ArcRwSpinlockWriteGuard, 71 | }; 72 | pub use super::rw_spinlock::{ 73 | BackoffRwSpinlockReadGuard, BackoffRwSpinlockUpgradableReadGuard, 74 | BackoffRwSpinlockWriteGuard, RwSpinlockReadGuard, RwSpinlockUpgradableReadGuard, 75 | RwSpinlockWriteGuard, 76 | }; 77 | #[cfg(feature = "arc_lock")] 78 | pub use super::spinlock::{ArcBackoffSpinlockGuard, ArcSpinlockGuard}; 79 | pub use super::spinlock::{ 80 | BackoffSpinlockGuard, MappedBackoffSpinlockGuard, MappedSpinlockGuard, SpinlockGuard, 81 | }; 82 | } 83 | 84 | pub mod relax; 85 | mod rw_spinlock; 86 | mod spinlock; 87 | -------------------------------------------------------------------------------- /src/relax.rs: -------------------------------------------------------------------------------- 1 | //! Relax strategies. 2 | //! 3 | //! Relax strategies are used when the thread cannot acquire a spinlock. 4 | 5 | /// A relax strategy. 6 | /// 7 | /// `Relax` types are used to relax the current thread during contention. 8 | pub trait Relax: Default { 9 | /// Relaxes the current thread. 10 | fn relax(&mut self); 11 | } 12 | 13 | /// Rapid spinning. 14 | /// 15 | /// This emits [`core::hint::spin_loop`]. 16 | #[derive(Default, Debug)] 17 | pub struct Spin; 18 | 19 | impl Relax for Spin { 20 | #[inline] 21 | fn relax(&mut self) { 22 | core::hint::spin_loop(); 23 | } 24 | } 25 | 26 | /// Exponential backoff. 27 | /// 28 | /// This performs exponential backoff to avoid unnecessarily stressing the cache. 29 | //Adapted from . 30 | #[derive(Default, Debug)] 31 | pub struct Backoff { 32 | step: u8, 33 | } 34 | 35 | impl Backoff { 36 | const YIELD_LIMIT: u8 = 10; 37 | } 38 | 39 | impl Relax for Backoff { 40 | #[inline] 41 | fn relax(&mut self) { 42 | for _ in 0..1_u16 << self.step { 43 | core::hint::spin_loop(); 44 | } 45 | 46 | if self.step <= Self::YIELD_LIMIT { 47 | self.step += 1; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/rw_spinlock.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | use core::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | use lock_api::{ 5 | GuardSend, RawRwLock, RawRwLockDowngrade, RawRwLockRecursive, RawRwLockUpgrade, 6 | RawRwLockUpgradeDowngrade, 7 | }; 8 | 9 | use crate::relax::{Backoff, Relax, Spin}; 10 | 11 | /// A simple, spinning, read-preferring readers-writer lock. 12 | // Adapted from `spin::rwlock::RwLock`, but 13 | // - with separation of `UPGRADABLE` and `EXCLUSIVE`, 14 | // - with optional exponential backoff, 15 | // - with `impl RawRwLockRecursive`. 16 | // 17 | #[derive(Debug)] 18 | pub struct RawRwSpinlock { 19 | lock: AtomicUsize, 20 | relax: PhantomData, 21 | } 22 | 23 | /// Normal shared lock counter 24 | const SHARED: usize = 1 << 2; 25 | /// Special upgradable shared lock flag 26 | const UPGRADABLE: usize = 1 << 1; 27 | /// Exclusive lock flag 28 | const EXCLUSIVE: usize = 1; 29 | 30 | impl RawRwSpinlock { 31 | #[inline] 32 | fn is_locked_shared(&self) -> bool { 33 | self.lock.load(Ordering::Relaxed) & !(EXCLUSIVE | UPGRADABLE) != 0 34 | } 35 | 36 | #[inline] 37 | fn is_locked_upgradable(&self) -> bool { 38 | self.lock.load(Ordering::Relaxed) & UPGRADABLE == UPGRADABLE 39 | } 40 | 41 | /// Acquire a shared lock, returning the new lock value. 42 | #[inline] 43 | fn acquire_shared(&self) -> usize { 44 | let value = self.lock.fetch_add(SHARED, Ordering::Acquire); 45 | 46 | // An arbitrary cap that allows us to catch overflows long before they happen 47 | if value > usize::MAX / 2 { 48 | self.lock.fetch_sub(SHARED, Ordering::Relaxed); 49 | panic!("Too many shared locks, cannot safely proceed"); 50 | } 51 | 52 | value 53 | } 54 | } 55 | 56 | unsafe impl RawRwLock for RawRwSpinlock { 57 | #[allow(clippy::declare_interior_mutable_const)] 58 | const INIT: Self = Self { 59 | lock: AtomicUsize::new(0), 60 | relax: PhantomData, 61 | }; 62 | 63 | type GuardMarker = GuardSend; 64 | 65 | #[inline] 66 | fn lock_shared(&self) { 67 | let mut relax = R::default(); 68 | 69 | while !self.try_lock_shared() { 70 | relax.relax(); 71 | } 72 | } 73 | 74 | #[inline] 75 | fn try_lock_shared(&self) -> bool { 76 | let value = self.acquire_shared(); 77 | 78 | let acquired = value & EXCLUSIVE != EXCLUSIVE; 79 | 80 | if !acquired { 81 | unsafe { 82 | self.unlock_shared(); 83 | } 84 | } 85 | 86 | acquired 87 | } 88 | 89 | #[inline] 90 | unsafe fn unlock_shared(&self) { 91 | debug_assert!(self.is_locked_shared()); 92 | 93 | self.lock.fetch_sub(SHARED, Ordering::Release); 94 | } 95 | 96 | #[inline] 97 | fn lock_exclusive(&self) { 98 | let mut relax = R::default(); 99 | 100 | while !self.try_lock_exclusive() { 101 | relax.relax(); 102 | } 103 | } 104 | 105 | #[inline] 106 | fn try_lock_exclusive(&self) -> bool { 107 | self.lock 108 | .compare_exchange(0, EXCLUSIVE, Ordering::Acquire, Ordering::Relaxed) 109 | .is_ok() 110 | } 111 | 112 | #[inline] 113 | unsafe fn unlock_exclusive(&self) { 114 | debug_assert!(self.is_locked_exclusive()); 115 | 116 | self.lock.fetch_and(!EXCLUSIVE, Ordering::Release); 117 | } 118 | 119 | #[inline] 120 | fn is_locked(&self) -> bool { 121 | self.lock.load(Ordering::Relaxed) != 0 122 | } 123 | 124 | #[inline] 125 | fn is_locked_exclusive(&self) -> bool { 126 | self.lock.load(Ordering::Relaxed) & EXCLUSIVE == EXCLUSIVE 127 | } 128 | } 129 | 130 | unsafe impl RawRwLockRecursive for RawRwSpinlock { 131 | #[inline] 132 | fn lock_shared_recursive(&self) { 133 | self.lock_shared(); 134 | } 135 | 136 | #[inline] 137 | fn try_lock_shared_recursive(&self) -> bool { 138 | self.try_lock_shared() 139 | } 140 | } 141 | 142 | unsafe impl RawRwLockDowngrade for RawRwSpinlock { 143 | #[inline] 144 | unsafe fn downgrade(&self) { 145 | // Reserve the shared guard for ourselves 146 | self.acquire_shared(); 147 | 148 | unsafe { 149 | self.unlock_exclusive(); 150 | } 151 | } 152 | } 153 | 154 | unsafe impl RawRwLockUpgrade for RawRwSpinlock { 155 | #[inline] 156 | fn lock_upgradable(&self) { 157 | let mut relax = R::default(); 158 | 159 | while !self.try_lock_upgradable() { 160 | relax.relax(); 161 | } 162 | } 163 | 164 | #[inline] 165 | fn try_lock_upgradable(&self) -> bool { 166 | let value = self.lock.fetch_or(UPGRADABLE, Ordering::Acquire); 167 | 168 | let acquired = value & (UPGRADABLE | EXCLUSIVE) == 0; 169 | 170 | if !acquired && value & UPGRADABLE == 0 { 171 | unsafe { 172 | self.unlock_upgradable(); 173 | } 174 | } 175 | 176 | acquired 177 | } 178 | 179 | #[inline] 180 | unsafe fn unlock_upgradable(&self) { 181 | debug_assert!(self.is_locked_upgradable()); 182 | 183 | self.lock.fetch_and(!UPGRADABLE, Ordering::Release); 184 | } 185 | 186 | #[inline] 187 | unsafe fn upgrade(&self) { 188 | let mut relax = R::default(); 189 | 190 | while !self.try_upgrade() { 191 | relax.relax(); 192 | } 193 | } 194 | 195 | #[inline] 196 | unsafe fn try_upgrade(&self) -> bool { 197 | self.lock 198 | .compare_exchange(UPGRADABLE, EXCLUSIVE, Ordering::Acquire, Ordering::Relaxed) 199 | .is_ok() 200 | } 201 | } 202 | 203 | unsafe impl RawRwLockUpgradeDowngrade for RawRwSpinlock { 204 | #[inline] 205 | unsafe fn downgrade_upgradable(&self) { 206 | self.acquire_shared(); 207 | 208 | unsafe { 209 | self.unlock_upgradable(); 210 | } 211 | } 212 | 213 | #[inline] 214 | unsafe fn downgrade_to_upgradable(&self) { 215 | debug_assert!(self.is_locked_exclusive()); 216 | 217 | self.lock 218 | .fetch_xor(UPGRADABLE | EXCLUSIVE, Ordering::Release); 219 | } 220 | } 221 | 222 | /// A [`lock_api::RwLock`] based on [`RawRwSpinlock`]. 223 | pub type RwSpinlock = lock_api::RwLock, T>; 224 | 225 | /// A [`lock_api::RwLockReadGuard`] based on [`RawRwSpinlock`]. 226 | pub type RwSpinlockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawRwSpinlock, T>; 227 | 228 | /// A [`lock_api::RwLockUpgradableReadGuard`] based on [`RawRwSpinlock`]. 229 | pub type RwSpinlockUpgradableReadGuard<'a, T> = 230 | lock_api::RwLockUpgradableReadGuard<'a, RawRwSpinlock, T>; 231 | 232 | /// A [`lock_api::RwLockWriteGuard`] based on [`RawRwSpinlock`]. 233 | pub type RwSpinlockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawRwSpinlock, T>; 234 | 235 | /// A [`lock_api::ArcRwLockReadGuard`] based on [`RawRwSpinlock`]. 236 | #[cfg(feature = "arc_lock")] 237 | pub type ArcRwSpinlockReadGuard = lock_api::ArcRwLockReadGuard, T>; 238 | 239 | /// A [`lock_api::ArcRwLockUpgradableReadGuard`] based on [`RawRwSpinlock`]. 240 | #[cfg(feature = "arc_lock")] 241 | pub type ArcRwSpinlockUpgradableReadGuard = 242 | lock_api::ArcRwLockUpgradableReadGuard, T>; 243 | 244 | /// A [`lock_api::ArcRwLockWriteGuard`] based on [`RawRwSpinlock`]. 245 | #[cfg(feature = "arc_lock")] 246 | pub type ArcRwSpinlockWriteGuard = lock_api::ArcRwLockWriteGuard, T>; 247 | 248 | /// A [`lock_api::RwLock`] based on [`RawRwSpinlock`]`<`[`Backoff`]`>`. 249 | pub type BackoffRwSpinlock = lock_api::RwLock, T>; 250 | 251 | /// A [`lock_api::RwLockReadGuard`] based on [`RawRwSpinlock`]`<`[`Backoff`]`>`. 252 | pub type BackoffRwSpinlockReadGuard<'a, T> = 253 | lock_api::RwLockReadGuard<'a, RawRwSpinlock, T>; 254 | 255 | /// A [`lock_api::RwLockUpgradableReadGuard`] based on [`RawRwSpinlock`]`<`[`Backoff`]`>`. 256 | pub type BackoffRwSpinlockUpgradableReadGuard<'a, T> = 257 | lock_api::RwLockUpgradableReadGuard<'a, RawRwSpinlock, T>; 258 | 259 | /// A [`lock_api::RwLockWriteGuard`] based on [`RawRwSpinlock`]`<`[`Backoff`]`>`. 260 | pub type BackoffRwSpinlockWriteGuard<'a, T> = 261 | lock_api::RwLockWriteGuard<'a, RawRwSpinlock, T>; 262 | 263 | /// A [`lock_api::ArcRwLockReadGuard`] based on [`RawRwSpinlock`]`<`[`Backoff`]`>`. 264 | #[cfg(feature = "arc_lock")] 265 | pub type ArcBackoffRwSpinlockReadGuard = lock_api::ArcRwLockReadGuard, T>; 266 | 267 | /// A [`lock_api::ArcRwLockUpgradableReadGuard`] based on [`RawRwSpinlock`]`<`[`Backoff`]`>`. 268 | #[cfg(feature = "arc_lock")] 269 | pub type ArcBackoffRwSpinlockUpgradableReadGuard = 270 | lock_api::ArcRwLockUpgradableReadGuard, T>; 271 | 272 | /// A [`lock_api::ArcRwLockWriteGuard`] based on [`RawRwSpinlock`]`<`[`Backoff`]`>`. 273 | #[cfg(feature = "arc_lock")] 274 | pub type ArcBackoffRwSpinlockWriteGuard = 275 | lock_api::ArcRwLockWriteGuard, T>; 276 | 277 | // Adapted from `spin::rwlock`. 278 | #[cfg(test)] 279 | mod tests { 280 | use std::sync::atomic::{AtomicUsize, Ordering}; 281 | use std::sync::mpsc::channel; 282 | use std::sync::Arc; 283 | use std::{mem, thread}; 284 | 285 | use lock_api::{RwLockUpgradableReadGuard, RwLockWriteGuard}; 286 | 287 | use super::*; 288 | 289 | #[test] 290 | fn test_unlock_shared() { 291 | let m: RawRwSpinlock = RawRwSpinlock::INIT; 292 | m.lock_shared(); 293 | m.lock_shared(); 294 | m.lock_shared(); 295 | assert!(!m.try_lock_exclusive()); 296 | unsafe { 297 | m.unlock_shared(); 298 | m.unlock_shared(); 299 | } 300 | assert!(!m.try_lock_exclusive()); 301 | unsafe { 302 | m.unlock_shared(); 303 | } 304 | assert!(m.try_lock_exclusive()); 305 | } 306 | 307 | #[test] 308 | fn test_unlock_exclusive() { 309 | let m: RawRwSpinlock = RawRwSpinlock::INIT; 310 | m.lock_exclusive(); 311 | assert!(!m.try_lock_shared()); 312 | unsafe { 313 | m.unlock_exclusive(); 314 | } 315 | assert!(m.try_lock_shared()); 316 | } 317 | 318 | #[test] 319 | fn smoke() { 320 | let l = RwSpinlock::new(()); 321 | drop(l.read()); 322 | drop(l.write()); 323 | drop((l.read(), l.read())); 324 | drop(l.write()); 325 | } 326 | 327 | #[test] 328 | fn frob() { 329 | use rand::Rng; 330 | 331 | static R: RwSpinlock = RwSpinlock::new(0); 332 | const N: usize = 10; 333 | const M: usize = 1000; 334 | 335 | let (tx, rx) = channel::<()>(); 336 | for _ in 0..N { 337 | let tx = tx.clone(); 338 | thread::spawn(move || { 339 | let mut rng = rand::thread_rng(); 340 | for _ in 0..M { 341 | if rng.gen_bool(1.0 / N as f64) { 342 | drop(R.write()); 343 | } else { 344 | drop(R.read()); 345 | } 346 | } 347 | drop(tx); 348 | }); 349 | } 350 | drop(tx); 351 | let _ = rx.recv(); 352 | } 353 | 354 | #[test] 355 | fn test_rw_arc() { 356 | let arc = Arc::new(RwSpinlock::new(0)); 357 | let arc2 = arc.clone(); 358 | let (tx, rx) = channel(); 359 | 360 | thread::spawn(move || { 361 | let mut lock = arc2.write(); 362 | for _ in 0..10 { 363 | let tmp = *lock; 364 | *lock = -1; 365 | thread::yield_now(); 366 | *lock = tmp + 1; 367 | } 368 | tx.send(()).unwrap(); 369 | }); 370 | 371 | // Readers try to catch the writer in the act 372 | let mut children = Vec::new(); 373 | for _ in 0..5 { 374 | let arc3 = arc.clone(); 375 | children.push(thread::spawn(move || { 376 | let lock = arc3.read(); 377 | assert!(*lock >= 0); 378 | })); 379 | } 380 | 381 | // Wait for children to pass their asserts 382 | for r in children { 383 | assert!(r.join().is_ok()); 384 | } 385 | 386 | // Wait for writer to finish 387 | rx.recv().unwrap(); 388 | let lock = arc.read(); 389 | assert_eq!(*lock, 10); 390 | } 391 | 392 | #[test] 393 | fn test_rw_access_in_unwind() { 394 | let arc = Arc::new(RwSpinlock::new(1)); 395 | let arc2 = arc.clone(); 396 | let _ = thread::spawn(move || -> () { 397 | struct Unwinder { 398 | i: Arc>, 399 | } 400 | impl Drop for Unwinder { 401 | fn drop(&mut self) { 402 | let mut lock = self.i.write(); 403 | *lock += 1; 404 | } 405 | } 406 | let _u = Unwinder { i: arc2 }; 407 | panic!(); 408 | }) 409 | .join(); 410 | let lock = arc.read(); 411 | assert_eq!(*lock, 2); 412 | } 413 | 414 | #[test] 415 | fn test_rwlock_unsized() { 416 | let rw: &RwSpinlock<[i32]> = &RwSpinlock::new([1, 2, 3]); 417 | { 418 | let b = &mut *rw.write(); 419 | b[0] = 4; 420 | b[2] = 5; 421 | } 422 | let comp: &[i32] = &[4, 2, 5]; 423 | assert_eq!(&*rw.read(), comp); 424 | } 425 | 426 | #[test] 427 | fn test_rwlock_try_write() { 428 | let lock = RwSpinlock::new(0isize); 429 | let read_guard = lock.read(); 430 | 431 | let write_result = lock.try_write(); 432 | match write_result { 433 | None => (), 434 | Some(_) => assert!( 435 | false, 436 | "try_write should not succeed while read_guard is in scope" 437 | ), 438 | } 439 | 440 | drop(read_guard); 441 | } 442 | 443 | #[test] 444 | fn test_rw_try_read() { 445 | let m = RwSpinlock::new(0); 446 | mem::forget(m.write()); 447 | assert!(m.try_read().is_none()); 448 | } 449 | 450 | #[test] 451 | fn test_into_inner() { 452 | let m = RwSpinlock::new(Box::new(10)); 453 | assert_eq!(m.into_inner(), Box::new(10)); 454 | } 455 | 456 | #[test] 457 | fn test_into_inner_drop() { 458 | struct Foo(Arc); 459 | impl Drop for Foo { 460 | fn drop(&mut self) { 461 | self.0.fetch_add(1, Ordering::SeqCst); 462 | } 463 | } 464 | let num_drops = Arc::new(AtomicUsize::new(0)); 465 | let m = RwSpinlock::new(Foo(num_drops.clone())); 466 | assert_eq!(num_drops.load(Ordering::SeqCst), 0); 467 | { 468 | let _inner = m.into_inner(); 469 | assert_eq!(num_drops.load(Ordering::SeqCst), 0); 470 | } 471 | assert_eq!(num_drops.load(Ordering::SeqCst), 1); 472 | } 473 | 474 | #[test] 475 | fn test_upgrade_downgrade() { 476 | let m = RwSpinlock::new(()); 477 | { 478 | let _r = m.read(); 479 | let upg = m.try_upgradable_read().unwrap(); 480 | assert!(m.try_read().is_some()); 481 | assert!(m.try_write().is_none()); 482 | assert!(RwLockUpgradableReadGuard::try_upgrade(upg).is_err()); 483 | } 484 | { 485 | let w = m.write(); 486 | assert!(m.try_upgradable_read().is_none()); 487 | let _r = RwLockWriteGuard::downgrade(w); 488 | assert!(m.try_upgradable_read().is_some()); 489 | assert!(m.try_read().is_some()); 490 | assert!(m.try_write().is_none()); 491 | } 492 | { 493 | let _u = m.upgradable_read(); 494 | assert!(m.try_upgradable_read().is_none()); 495 | } 496 | 497 | assert!(RwLockUpgradableReadGuard::try_upgrade(m.try_upgradable_read().unwrap()).is_ok()); 498 | } 499 | } 500 | -------------------------------------------------------------------------------- /src/spinlock.rs: -------------------------------------------------------------------------------- 1 | // This implementation is based on: 2 | // https://github.com/Amanieu/parking_lot/tree/fa294cd677936bf365afa0497039953b10c722f5/lock_api 3 | // and 4 | // https://github.com/mvdnes/spin-rs/tree/7516c8037d3d15712ba4d8499ab075e97a19d778 5 | 6 | use core::marker::PhantomData; 7 | use core::sync::atomic::{AtomicBool, Ordering}; 8 | use lock_api::{GuardSend, RawMutex}; 9 | 10 | use crate::relax::{Backoff, Relax, Spin}; 11 | 12 | /// Provides mutual exclusion based on spinning on an `AtomicBool`. 13 | /// 14 | /// It's recommended to use this type either combination with [`lock_api::Mutex`] or 15 | /// through the [`Spinlock`] type. 16 | /// 17 | /// ## Example 18 | /// 19 | /// ```rust 20 | /// use lock_api::RawMutex; 21 | /// use spinning_top::RawSpinlock; 22 | /// 23 | /// let lock: RawSpinlock = RawSpinlock::INIT; 24 | /// assert_eq!(lock.try_lock(), true); // lock it 25 | /// assert_eq!(lock.try_lock(), false); // can't be locked a second time 26 | /// unsafe { lock.unlock(); } // unlock it 27 | /// assert_eq!(lock.try_lock(), true); // now it can be locked again 28 | #[derive(Debug)] 29 | pub struct RawSpinlock { 30 | /// Whether the spinlock is locked. 31 | locked: AtomicBool, 32 | relax: PhantomData, 33 | } 34 | 35 | impl RawSpinlock { 36 | // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` 37 | // when called in a loop. 38 | #[inline] 39 | fn try_lock_weak(&self) -> bool { 40 | // The Orderings are the same as try_lock, and are still correct here. 41 | self.locked 42 | .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) 43 | .is_ok() 44 | } 45 | } 46 | 47 | unsafe impl RawMutex for RawSpinlock { 48 | const INIT: RawSpinlock = RawSpinlock { 49 | locked: AtomicBool::new(false), 50 | relax: PhantomData, 51 | }; 52 | 53 | // A spinlock guard can be sent to another thread and unlocked there 54 | type GuardMarker = GuardSend; 55 | 56 | #[inline] 57 | fn lock(&self) { 58 | let mut relax = R::default(); 59 | 60 | while !self.try_lock_weak() { 61 | // Wait until the lock looks unlocked before retrying 62 | // Code from https://github.com/mvdnes/spin-rs/commit/d3e60d19adbde8c8e9d3199c7c51e51ee5a20bf6 63 | while self.is_locked() { 64 | // Tell the CPU that we're inside a busy-wait loop 65 | relax.relax(); 66 | } 67 | } 68 | } 69 | 70 | #[inline] 71 | fn try_lock(&self) -> bool { 72 | // Code taken from: 73 | // https://github.com/Amanieu/parking_lot/blob/fa294cd677936bf365afa0497039953b10c722f5/lock_api/src/lib.rs#L49-L53 74 | // 75 | // The reason for using a strong compare_exchange is explained here: 76 | // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107 77 | // 78 | // The second Ordering argument specfies the ordering when the compare_exchange 79 | // fails. Since we don't access any critical data if we fail to acquire the lock, 80 | // we can use a Relaxed ordering in this case. 81 | self.locked 82 | .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) 83 | .is_ok() 84 | } 85 | 86 | #[inline] 87 | unsafe fn unlock(&self) { 88 | self.locked.store(false, Ordering::Release); 89 | } 90 | 91 | #[inline] 92 | fn is_locked(&self) -> bool { 93 | // Relaxed is sufficient because this operation does not provide synchronization, only atomicity. 94 | self.locked.load(Ordering::Relaxed) 95 | } 96 | } 97 | 98 | /// A mutual exclusion (Mutex) type based on busy-waiting. 99 | /// 100 | /// Calling `lock` (or `try_lock`) on this type returns a [`SpinlockGuard`], which 101 | /// automatically frees the lock when it goes out of scope. 102 | /// 103 | /// ## Example 104 | /// 105 | /// ```rust 106 | /// use spinning_top::Spinlock; 107 | /// 108 | /// fn main() { 109 | /// // Wrap some data in a spinlock 110 | /// let data = String::from("Hello"); 111 | /// let spinlock = Spinlock::new(data); 112 | /// make_uppercase(&spinlock); // only pass a shared reference 113 | /// 114 | /// // We have ownership of the spinlock, so we can extract the data without locking 115 | /// // Note: this consumes the spinlock 116 | /// let data = spinlock.into_inner(); 117 | /// assert_eq!(data.as_str(), "HELLO"); 118 | /// } 119 | /// 120 | /// fn make_uppercase(spinlock: &Spinlock) { 121 | /// // Lock the spinlock to get a mutable reference to the data 122 | /// let mut locked_data = spinlock.lock(); 123 | /// assert_eq!(locked_data.as_str(), "Hello"); 124 | /// locked_data.make_ascii_uppercase(); 125 | /// 126 | /// // the lock is automatically freed at the end of the scope 127 | /// } 128 | /// ``` 129 | /// 130 | /// ## Usage in statics 131 | /// 132 | /// `Spinlock::new` is a `const` function. This makes the `Spinlock` type 133 | /// usable in statics: 134 | /// 135 | /// ```rust 136 | /// use spinning_top::Spinlock; 137 | /// 138 | /// static DATA: Spinlock = Spinlock::new(0); 139 | /// 140 | /// fn main() { 141 | /// let mut data = DATA.lock(); 142 | /// *data += 1; 143 | /// assert_eq!(*data, 1); 144 | /// } 145 | /// ``` 146 | pub type Spinlock = lock_api::Mutex, T>; 147 | 148 | /// A RAII guard that frees the spinlock when it goes out of scope. 149 | /// 150 | /// Allows access to the locked data through the [`core::ops::Deref`] and [`core::ops::DerefMut`] operations. 151 | /// 152 | /// ## Example 153 | /// 154 | /// ```rust 155 | /// use spinning_top::{guard::SpinlockGuard, Spinlock}; 156 | /// 157 | /// let spinlock = Spinlock::new(Vec::new()); 158 | /// 159 | /// // begin a new scope 160 | /// { 161 | /// // lock the spinlock to create a `SpinlockGuard` 162 | /// let mut guard: SpinlockGuard<_> = spinlock.lock(); 163 | /// 164 | /// // guard can be used like a `&mut Vec` since it implements `DerefMut` 165 | /// guard.push(1); 166 | /// guard.push(2); 167 | /// assert_eq!(guard.len(), 2); 168 | /// } // guard is dropped -> frees the spinlock again 169 | /// 170 | /// // spinlock is unlocked again 171 | /// assert!(spinlock.try_lock().is_some()); 172 | /// ``` 173 | pub type SpinlockGuard<'a, T> = lock_api::MutexGuard<'a, RawSpinlock, T>; 174 | 175 | /// A RAII guard returned by `SpinlockGuard::map`. 176 | /// 177 | /// ## Example 178 | /// ```rust 179 | /// use spinning_top::{ 180 | /// guard::{MappedSpinlockGuard, SpinlockGuard}, 181 | /// Spinlock, 182 | /// }; 183 | /// 184 | /// let spinlock = Spinlock::new(Some(3)); 185 | /// 186 | /// // Begin a new scope. 187 | /// { 188 | /// // Lock the spinlock to create a `SpinlockGuard`. 189 | /// let mut guard: SpinlockGuard<_> = spinlock.lock(); 190 | /// 191 | /// // Map the internal value of `guard`. `guard` is moved. 192 | /// let mut mapped: MappedSpinlockGuard<'_, _> = 193 | /// SpinlockGuard::map(guard, |g| g.as_mut().unwrap()); 194 | /// assert_eq!(*mapped, 3); 195 | /// 196 | /// *mapped = 5; 197 | /// assert_eq!(*mapped, 5); 198 | /// } // `mapped` is dropped -> frees the spinlock again. 199 | /// 200 | /// // The operation is reflected to the original lock. 201 | /// assert_eq!(*spinlock.lock(), Some(5)); 202 | /// ``` 203 | pub type MappedSpinlockGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawSpinlock, T>; 204 | 205 | /// A [`lock_api::ArcMutexGuard`] based on [`RawSpinlock`]`. 206 | #[cfg(feature = "arc_lock")] 207 | pub type ArcSpinlockGuard = lock_api::ArcMutexGuard, T>; 208 | 209 | /// A mutual exclusion (Mutex) type based on busy-waiting with exponential backoff. 210 | /// 211 | /// Calling `lock` (or `try_lock`) on this type returns a [`BackoffSpinlockGuard`], which 212 | /// automatically frees the lock when it goes out of scope. 213 | /// 214 | /// ## Example 215 | /// 216 | /// ```rust 217 | /// use spinning_top::BackoffSpinlock; 218 | /// 219 | /// fn main() { 220 | /// // Wrap some data in a spinlock 221 | /// let data = String::from("Hello"); 222 | /// let spinlock = BackoffSpinlock::new(data); 223 | /// make_uppercase(&spinlock); // only pass a shared reference 224 | /// 225 | /// // We have ownership of the spinlock, so we can extract the data without locking 226 | /// // Note: this consumes the spinlock 227 | /// let data = spinlock.into_inner(); 228 | /// assert_eq!(data.as_str(), "HELLO"); 229 | /// } 230 | /// 231 | /// fn make_uppercase(spinlock: &BackoffSpinlock) { 232 | /// // Lock the spinlock to get a mutable reference to the data 233 | /// let mut locked_data = spinlock.lock(); 234 | /// assert_eq!(locked_data.as_str(), "Hello"); 235 | /// locked_data.make_ascii_uppercase(); 236 | /// 237 | /// // the lock is automatically freed at the end of the scope 238 | /// } 239 | /// ``` 240 | /// 241 | /// ## Usage in statics 242 | /// 243 | /// `BackoffSpinlock::new` is a `const` function. This makes the `BackoffSpinlock` type 244 | /// usable in statics: 245 | /// 246 | /// ```rust 247 | /// use spinning_top::BackoffSpinlock; 248 | /// 249 | /// static DATA: BackoffSpinlock = BackoffSpinlock::new(0); 250 | /// 251 | /// fn main() { 252 | /// let mut data = DATA.lock(); 253 | /// *data += 1; 254 | /// assert_eq!(*data, 1); 255 | /// } 256 | /// ``` 257 | pub type BackoffSpinlock = lock_api::Mutex, T>; 258 | 259 | /// A RAII guard that frees the exponential backoff spinlock when it goes out of scope. 260 | /// 261 | /// Allows access to the locked data through the [`core::ops::Deref`] and [`core::ops::DerefMut`] operations. 262 | /// 263 | /// ## Example 264 | /// 265 | /// ```rust 266 | /// use spinning_top::{guard::BackoffSpinlockGuard, BackoffSpinlock}; 267 | /// 268 | /// let spinlock = BackoffSpinlock::new(Vec::new()); 269 | /// 270 | /// // begin a new scope 271 | /// { 272 | /// // lock the spinlock to create a `BackoffSpinlockGuard` 273 | /// let mut guard: BackoffSpinlockGuard<_> = spinlock.lock(); 274 | /// 275 | /// // guard can be used like a `&mut Vec` since it implements `DerefMut` 276 | /// guard.push(1); 277 | /// guard.push(2); 278 | /// assert_eq!(guard.len(), 2); 279 | /// } // guard is dropped -> frees the spinlock again 280 | /// 281 | /// // spinlock is unlocked again 282 | /// assert!(spinlock.try_lock().is_some()); 283 | /// ``` 284 | pub type BackoffSpinlockGuard<'a, T> = lock_api::MutexGuard<'a, RawSpinlock, T>; 285 | 286 | /// A RAII guard returned by `BackoffSpinlockGuard::map`. 287 | /// 288 | /// ## Example 289 | /// ```rust 290 | /// use spinning_top::{ 291 | /// guard::{BackoffSpinlockGuard, MappedBackoffSpinlockGuard}, 292 | /// BackoffSpinlock, 293 | /// }; 294 | /// 295 | /// let spinlock = BackoffSpinlock::new(Some(3)); 296 | /// 297 | /// // Begin a new scope. 298 | /// { 299 | /// // Lock the spinlock to create a `BackoffSpinlockGuard`. 300 | /// let mut guard: BackoffSpinlockGuard<_> = spinlock.lock(); 301 | /// 302 | /// // Map the internal value of `guard`. `guard` is moved. 303 | /// let mut mapped: MappedBackoffSpinlockGuard<'_, _> = 304 | /// BackoffSpinlockGuard::map(guard, |g| g.as_mut().unwrap()); 305 | /// assert_eq!(*mapped, 3); 306 | /// 307 | /// *mapped = 5; 308 | /// assert_eq!(*mapped, 5); 309 | /// } // `mapped` is dropped -> frees the spinlock again. 310 | /// 311 | /// // The operation is reflected to the original lock. 312 | /// assert_eq!(*spinlock.lock(), Some(5)); 313 | /// ``` 314 | pub type MappedBackoffSpinlockGuard<'a, T> = 315 | lock_api::MappedMutexGuard<'a, RawSpinlock, T>; 316 | 317 | /// A [`lock_api::ArcMutexGuard`] based on [`RawSpinlock`]`<`[`Backoff`]`>`. 318 | #[cfg(feature = "arc_lock")] 319 | pub type ArcBackoffSpinlockGuard = lock_api::ArcMutexGuard, T>; 320 | 321 | #[cfg(test)] 322 | mod tests { 323 | use super::*; 324 | 325 | #[test] 326 | fn create_and_lock() { 327 | let spinlock = Spinlock::new(42); 328 | let data = spinlock.try_lock(); 329 | assert!(data.is_some()); 330 | assert_eq!(*data.unwrap(), 42); 331 | } 332 | 333 | #[test] 334 | fn mutual_exclusion() { 335 | let spinlock = Spinlock::new(1); 336 | let data = spinlock.try_lock(); 337 | assert!(data.is_some()); 338 | assert!(spinlock.try_lock().is_none()); 339 | assert!(spinlock.try_lock().is_none()); // still None 340 | core::mem::drop(data); 341 | assert!(spinlock.try_lock().is_some()); 342 | } 343 | 344 | #[test] 345 | fn three_locks() { 346 | let spinlock1 = Spinlock::new(1); 347 | let spinlock2 = Spinlock::new(2); 348 | let spinlock3 = Spinlock::new(3); 349 | let data1 = spinlock1.try_lock(); 350 | let data2 = spinlock2.try_lock(); 351 | let data3 = spinlock3.try_lock(); 352 | assert!(data1.is_some()); 353 | assert!(data2.is_some()); 354 | assert!(data3.is_some()); 355 | assert!(spinlock1.try_lock().is_none()); 356 | assert!(spinlock1.try_lock().is_none()); // still None 357 | assert!(spinlock2.try_lock().is_none()); 358 | assert!(spinlock3.try_lock().is_none()); 359 | core::mem::drop(data3); 360 | assert!(spinlock3.try_lock().is_some()); 361 | } 362 | 363 | #[test] 364 | fn mapped_lock() { 365 | let spinlock = Spinlock::new([1, 2, 3]); 366 | let data = spinlock.lock(); 367 | let mut mapped = SpinlockGuard::map(data, |d| &mut d[0]); 368 | assert_eq!(*mapped, 1); 369 | *mapped = 4; 370 | assert_eq!(*mapped, 4); 371 | core::mem::drop(mapped); 372 | assert!(!spinlock.is_locked()); 373 | assert_eq!(*spinlock.lock(), [4, 2, 3]); 374 | } 375 | } 376 | --------------------------------------------------------------------------------