├── tests └── loom.rs ├── .gitignore ├── .github └── workflows │ ├── msrv.yml │ ├── loom.yml │ ├── coverage.yml │ ├── features.yml │ ├── nostd.yml │ ├── miri.yml │ ├── asan.yml │ ├── minimal.yml │ ├── test.yml │ ├── style.yml │ └── lsan.yml ├── Cargo.toml ├── src ├── sync.rs └── lib.rs ├── README.md └── LICENSE /tests/loom.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /.github/workflows/msrv.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: Minimum Supported Rust Version 6 | jobs: 7 | check: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | profile: minimal 13 | toolchain: 1.56.0 14 | override: true 15 | - uses: actions/checkout@v2 16 | - name: cargo +1.56.0 check 17 | uses: actions-rs/cargo@v1 18 | with: 19 | command: check 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sento" 3 | version = "0.1.1" 4 | authors = ["Jon Gjengset "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | 8 | description = "A lock-free, append-only atomic pool." 9 | repository = "https://github.com/jonhoo/sento.git" 10 | 11 | keywords = ["pool", "atomic", "linked-list", "no_std"] 12 | categories = ["concurrency", "data-structures", "no-std"] 13 | 14 | [features] 15 | std = [] 16 | 17 | default = ["std"] 18 | 19 | [dependencies] 20 | 21 | [target.'cfg(loom)'.dependencies] 22 | loom = "0.5.5" 23 | -------------------------------------------------------------------------------- /.github/workflows/loom.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: Loom 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | toolchain: stable 13 | profile: minimal 14 | - uses: actions/checkout@v2 15 | - name: cargo test --test loom 16 | uses: actions-rs/cargo@v1 17 | with: 18 | command: test 19 | args: --release --test loom 20 | env: 21 | LOOM_MAX_PREEMPTIONS: 2 22 | RUSTFLAGS: "--cfg loom" 23 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: coverage 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | container: 10 | image: xd009642/tarpaulin 11 | options: --security-opt seccomp=unconfined 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Generate code coverage 15 | run: | 16 | cargo tarpaulin --verbose --all-features --timeout 120 --out Xml 17 | - name: Upload to codecov.io 18 | uses: codecov/codecov-action@v2 19 | with: 20 | fail_ci_if_error: true 21 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(not(feature = "std"), loom))] 2 | compile_error!("loom requires the standard library"); 3 | 4 | #[cfg(loom)] 5 | pub(crate) mod atomic { 6 | pub(crate) use loom::sync::atomic::{AtomicPtr, AtomicUsize}; 7 | } 8 | #[cfg(loom)] 9 | pub(crate) use loom::thread::yield_now; 10 | 11 | #[cfg(not(loom))] 12 | pub(crate) mod atomic { 13 | #[cfg(target_pointer_width = "64")] 14 | pub use core::sync::atomic::AtomicU64; 15 | pub(crate) use core::sync::atomic::{AtomicPtr, AtomicUsize}; 16 | } 17 | #[cfg(all(not(loom), feature = "std"))] 18 | pub(crate) use std::thread::yield_now; 19 | -------------------------------------------------------------------------------- /.github/workflows/features.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: cargo hack 6 | jobs: 7 | check: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | profile: minimal 13 | toolchain: stable 14 | - uses: actions/checkout@v2 15 | - name: Install cargo-hack 16 | uses: actions-rs/install@v0.1 17 | with: 18 | crate: cargo-hack 19 | version: latest 20 | use-tool-cache: true 21 | - name: cargo hack 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: hack 25 | args: --feature-powerset check 26 | -------------------------------------------------------------------------------- /.github/workflows/nostd.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: no-std 6 | jobs: 7 | nostd: 8 | runs-on: ubuntu-latest 9 | name: ${{ matrix.target }} 10 | strategy: 11 | matrix: 12 | target: [thumbv7m-none-eabi, aarch64-unknown-none] 13 | steps: 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | target: ${{ matrix.target }} 19 | - uses: actions/checkout@v2 20 | - name: cargo check 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: check 24 | args: --target ${{ matrix.target }} --no-default-features 25 | -------------------------------------------------------------------------------- /.github/workflows/miri.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: Miri 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - run: | 11 | echo "NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)" >> $GITHUB_ENV 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | profile: minimal 15 | toolchain: ${{ env.NIGHTLY }} 16 | override: true 17 | components: miri 18 | - uses: actions/checkout@v2 19 | - name: cargo miri test 20 | uses: actions-rs/cargo@v1 21 | with: 22 | command: miri 23 | args: test 24 | env: 25 | MIRIFLAGS: "-Zmiri-tag-raw-pointers" 26 | -------------------------------------------------------------------------------- /.github/workflows/asan.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: Address sanitizer 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | profile: minimal 13 | toolchain: nightly 14 | override: true 15 | - uses: actions/checkout@v2 16 | - name: cargo test -Zsanitizer=address 17 | uses: actions-rs/cargo@v1 18 | with: 19 | command: test 20 | # only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945 21 | args: --lib --tests --all-features --target x86_64-unknown-linux-gnu 22 | env: 23 | ASAN_OPTIONS: "detect_odr_violation=0:detect_leaks=0" 24 | RUSTFLAGS: "-Z sanitizer=address" 25 | -------------------------------------------------------------------------------- /.github/workflows/minimal.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: With dependencies at minimal versions 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | profile: minimal 13 | toolchain: nightly 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | - uses: actions/checkout@v2 19 | - name: cargo update -Zminimal-versions 20 | uses: actions-rs/cargo@v1 21 | with: 22 | command: update 23 | toolchain: nightly 24 | args: -Zminimal-versions 25 | - name: cargo test 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: test 29 | args: --all-features 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: cargo test 6 | jobs: 7 | test: 8 | runs-on: ${{ matrix.os }} 9 | name: ${{ matrix.os }} / ${{ matrix.toolchain }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | toolchain: [stable] 15 | include: 16 | - os: ubuntu-latest 17 | toolchain: beta 18 | - os: ubuntu-latest 19 | toolchain: nightly 20 | steps: 21 | - uses: actions-rs/toolchain@v1 22 | with: 23 | profile: minimal 24 | toolchain: ${{ matrix.toolchain }} 25 | - uses: actions/checkout@v2 26 | - name: cargo test 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: test 30 | args: --all-features 31 | -------------------------------------------------------------------------------- /.github/workflows/style.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: lint 6 | jobs: 7 | style: 8 | runs-on: ubuntu-latest 9 | name: ${{ matrix.toolchain }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | toolchain: [stable, beta] 14 | steps: 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: ${{ matrix.toolchain }} 19 | components: rustfmt, clippy 20 | - uses: actions/checkout@v2 21 | - name: cargo fmt --check 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: fmt 25 | args: --check 26 | - name: cargo doc 27 | uses: actions-rs/cargo@v1 28 | if: always() 29 | with: 30 | command: doc 31 | args: --no-deps --all-features 32 | - name: cargo clippy 33 | uses: actions-rs/clippy-check@v1 34 | if: always() 35 | with: 36 | token: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/lsan.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | name: Leak sanitizer 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | profile: minimal 13 | toolchain: nightly 14 | override: true 15 | - uses: actions/checkout@v2 16 | - run: | 17 | # to get the symbolizer for debug symbol resolution 18 | sudo apt install llvm 19 | # to fix buggy leak analyzer: 20 | # https://github.com/japaric/rust-san#unrealiable-leaksanitizer 21 | sed -i '/\[features\]/i [profile.dev]' Cargo.toml 22 | sed -i '/profile.dev/a opt-level = 1' Cargo.toml 23 | cat Cargo.toml 24 | name: Enable debug symbols 25 | - name: cargo test -Zsanitizer=leak 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: test 29 | args: --all-features --target x86_64-unknown-linux-gnu 30 | env: 31 | RUSTFLAGS: "-Z sanitizer=leak" 32 | LSAN_OPTIONS: "suppressions=lsan-suppressions.txt" 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/sento.svg)](https://crates.io/crates/sento) 2 | [![Documentation](https://docs.rs/sento/badge.svg)](https://docs.rs/sento/) 3 | [![codecov](https://codecov.io/gh/jonhoo/sento/branch/main/graph/badge.svg?token=8FYF6JKJ8W)](https://codecov.io/gh/jonhoo/sento) 4 | [![Dependency status](https://deps.rs/repo/github/jonhoo/sento/status.svg)](https://deps.rs/repo/github/jonhoo/sento) 5 | 6 | A lock-free, append-only atomic pool. 7 | 8 | This library implements an atomic, append-only collection of items, 9 | where individual items can be acquired and relased so they are always 10 | associated with at most one owner. Thus, each item is at all times 11 | either free or acquired, and the library presents operations for 12 | acquiring a free item or for releasing an already acquired one. 13 | 14 | ## License 15 | 16 | Licensed under Apache License, Version 2.0 ([LICENSE](LICENSE) or http://www.apache.org/licenses/LICENSE-2.0). 17 | 18 | ## Contribution 19 | 20 | Unless you explicitly state otherwise, any contribution intentionally 21 | submitted for inclusion in the work by you, as defined in the Apache-2.0 22 | license, shall be licensed as above, without any additional terms or 23 | conditions. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A lock-free, append-only atomic pool. 2 | //! 3 | //! This library implements an atomic, append-only collection of items, where individual items can 4 | //! be acquired and relased so they are always associated with at most one owner. Thus, each item 5 | //! is at all times either free or acquired, and the library presents operations for acquiring a 6 | //! free item or for releasing an already acquired one. 7 | //! 8 | //! The implementation is inspired by the one used in [folly's hazard pointer implementation], 9 | //! originally ported into [`haphazard`]. It consists of two linked lists implemented using a 10 | //! single shared node type, where each node holds both a pointer to the next node in the overall 11 | //! list, and a "skip pointer" to the next _available (non-acquired) node in the list. This enables 12 | //! acquiring to be efficient and atomic. 13 | //! 14 | //! [`haphazard`]: https://crates.io/crates/haphazard 15 | //! [folly's hazard pointer implementation]: https://github.com/facebook/folly/blob/594b7e770176003d0f6b4cf725dd02a09cba533c/folly/synchronization/HazptrRec.h#L35-L36 16 | //! 17 | //! # Examples 18 | //! 19 | //! ## Basic operation 20 | //! 21 | //! ```rust 22 | //! use sento::Pool; 23 | //! type Value = i32; 24 | //! let pool = Pool::::new(); 25 | //! let v1 = pool.acquire(); // this will allocate a new Value with Value::default() 26 | //! let v2 = pool.acquire(); // so will this 27 | //! 28 | //! // we can get a long-lived shared reference to the value 29 | //! let v1_ref = v1.into_ref(); 30 | //! 31 | //! // by releasing v1, it is now "free" and will be used rather than allocating a new Value. 32 | //! // note, however, that v1 will not be deallocated until pool is dropped! 33 | //! pool.release(v1); 34 | //! let v1_again = pool.acquire(); 35 | //! 36 | //! // note that the semantics of acquire and release are up to you. 37 | //! // .release does not require that you surrender the released reference, 38 | //! // since the referenced value still lives in the same place. 39 | //! assert_eq!(v1_ref as *const _, &*v1_again as *const _); 40 | //! 41 | //! // when the Pool is dropped, it also frees all the nodes, 42 | //! // so at that point you can't access the values any more. 43 | //! drop(pool); 44 | //! ``` 45 | 46 | #![deny(unsafe_op_in_unsafe_fn)] 47 | #![warn(missing_docs)] 48 | #![warn(rustdoc::broken_intra_doc_links, rust_2018_idioms)] 49 | #![cfg_attr(not(feature = "std"), no_std)] 50 | 51 | // XXX: how do we ensure items are returned to the _same_ pool? 52 | static DISCRIMINATOR: AtomicUsize = AtomicUsize::new(0); 53 | 54 | extern crate alloc; 55 | 56 | mod sync; 57 | 58 | use crate::sync::atomic::{AtomicPtr, AtomicUsize}; 59 | use alloc::boxed::Box; 60 | use core::{marker::PhantomData, sync::atomic::Ordering}; 61 | 62 | // Make AtomicPtr usable with loom API. 63 | trait WithMut { 64 | fn with_mut(&mut self, f: impl FnOnce(&mut *mut T) -> R) -> R; 65 | } 66 | impl WithMut for core::sync::atomic::AtomicPtr { 67 | fn with_mut(&mut self, f: impl FnOnce(&mut *mut T) -> R) -> R { 68 | f(self.get_mut()) 69 | } 70 | } 71 | 72 | const LOCK_BIT: usize = 1; 73 | 74 | /// An item from a [`Pool`]. 75 | /// 76 | /// Only exposed in public interfaces as [`Acquired`]. 77 | #[derive(Debug)] 78 | pub struct Node { 79 | /// The value being stored in the node. 80 | v: T, 81 | 82 | /// The next `Node` in the list of all `Node`s in a `Pool`. 83 | /// This value never changes once set. 84 | next: AtomicPtr, 85 | 86 | /// The next non-acquired `Node` in the associatd `Pool`. 87 | available_next: AtomicPtr, 88 | 89 | /// A unique identifier for the `Pool` this `Node` was created by. 90 | discriminator: usize, 91 | } 92 | 93 | /// An item acquired from a [`Pool`]. 94 | /// 95 | /// This type dereferences to a `T`. 96 | /// If you want a longer-lived reference to the underlying `T`, use [`Acquired::into_ref`]. 97 | /// 98 | /// To release this `T` back to the [`Pool`], use [`Pool::release`]. 99 | #[derive(Debug)] 100 | #[repr(transparent)] 101 | pub struct Acquired<'pool, T>(&'pool Node); 102 | 103 | impl AsRef for Acquired<'_, T> { 104 | fn as_ref(&self) -> &T { 105 | self.into_ref() 106 | } 107 | } 108 | 109 | impl core::ops::Deref for Acquired<'_, T> { 110 | type Target = T; 111 | fn deref(&self) -> &T { 112 | self.into_ref() 113 | } 114 | } 115 | 116 | impl<'pool, T> Acquired<'pool, T> { 117 | /// Extract a shared reference to the acquired `T`. 118 | /// 119 | /// Note that the returned reference outlives this `Acquired`. While it will remain _valid_ 120 | /// until the associated [`Pool`] is dropped, there's no guarantee that that `T` will not be 121 | /// released and acquired elsewhere during that time. 122 | #[inline] 123 | pub fn into_ref(&self) -> &'pool T { 124 | &self.0.v 125 | } 126 | } 127 | 128 | // Macro to make new const only when not in loom. 129 | macro_rules! new_node { 130 | ($($decl:tt)*) => { 131 | $($decl)*(discriminator: usize, v: T) -> Self { 132 | Self { 133 | v, 134 | next: AtomicPtr::new(core::ptr::null_mut()), 135 | available_next: AtomicPtr::new(core::ptr::null_mut()), 136 | discriminator, 137 | } 138 | } 139 | }; 140 | } 141 | 142 | impl Node { 143 | #[cfg(not(loom))] 144 | new_node!(const fn new); 145 | #[cfg(loom)] 146 | new_node!(fn new); 147 | } 148 | 149 | /// A shareable pool of `T`s that can be acquired and released without locks. 150 | /// 151 | /// Use [`Pool::acquire`] to acquire a `T`, and [`Pool::release`] to return one. 152 | pub struct Pool { 153 | head: AtomicPtr>, 154 | head_released: AtomicPtr>, 155 | discriminator: usize, 156 | count: AtomicUsize, 157 | } 158 | 159 | // Sharing a Pool in isolation is fine. 160 | // Sharing a Pool enables sharing a T, so we require T: Sync. 161 | // Sharing a Pool does _not_ enable moving a T (without unsafe code), so no T: Send is needed. 162 | unsafe impl Sync for Pool where T: Sync {} 163 | 164 | // Sending a Pool in isolation is fine. 165 | // Sending a Pool also makes &T available on that other thread, so we require T: Sync. 166 | // Dropping a Pool will drop the Ts too, so we require T: Send. 167 | unsafe impl Send for Pool where T: Sync + Send {} 168 | 169 | impl core::fmt::Debug for Pool 170 | where 171 | T: core::fmt::Debug, 172 | { 173 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 174 | f.debug_list().entries(self.iter()).finish() 175 | } 176 | } 177 | 178 | impl Default for Pool { 179 | fn default() -> Self { 180 | Self::new() 181 | } 182 | } 183 | 184 | impl Pool { 185 | /// Allocate a new, empty [`Pool`]. 186 | pub fn new() -> Self { 187 | Self { 188 | head: AtomicPtr::new(core::ptr::null_mut()), 189 | head_released: AtomicPtr::new(core::ptr::null_mut()), 190 | count: AtomicUsize::new(0), 191 | discriminator: DISCRIMINATOR.fetch_add(1, Ordering::AcqRel), 192 | } 193 | } 194 | 195 | /// The number of `T`s in the pool, including both acquired and free items. 196 | /// 197 | /// Since this is a concurrent collection, this number is only a lower bound. 198 | pub fn size(&self) -> usize { 199 | self.count.load(Ordering::Acquire) 200 | } 201 | 202 | // TODO: Add a pub fn try_acquire that returns <= N without calling acquire_new 203 | 204 | fn try_acquire_available(&self) -> (*const Node, usize) { 205 | debug_assert!(N >= 1); 206 | debug_assert_eq!(core::ptr::null::>() as usize, 0); 207 | 208 | loop { 209 | let avail = self.head_released.load(Ordering::Acquire); 210 | if avail.is_null() { 211 | return (avail, 0); 212 | } 213 | debug_assert_ne!(avail, LOCK_BIT as *mut _); 214 | if (avail as usize & LOCK_BIT) == 0 { 215 | // The available list is not currently locked. 216 | // 217 | // XXX: This could be a fetch_or and allow progress even if there's a new (but 218 | // unlocked) head. However, `AtomicPtr` doesn't support fetch_or at the moment, so 219 | // we'd have to convert it to an `AtomicUsize`. This will in turn make Miri fail 220 | // (with -Zmiri-tag-raw-pointers, which we want enabled) to track the provenance of 221 | // the pointer in question through the int-to-ptr conversion. The workaround is 222 | // probably to mock a type that is `AtomicUsize` with `fetch_or` with 223 | // `#[cfg(not(miri))]`, but is `AtomicPtr` with `compare_exchange` with 224 | // `#[cfg(miri)]`. It ain't pretty, but should do the job. The issue is tracked in 225 | // https://github.com/rust-lang/miri/issues/1993. 226 | if self 227 | .head_released 228 | .compare_exchange_weak( 229 | avail, 230 | with_lock_bit(avail), 231 | Ordering::AcqRel, 232 | Ordering::Relaxed, 233 | ) 234 | .is_ok() 235 | { 236 | // Safety: We hold the lock on the available list. 237 | let (rec, n) = unsafe { self.try_acquire_available_locked::(avail) }; 238 | debug_assert!(n >= 1, "head_available was not null"); 239 | debug_assert!(n <= N); 240 | return (rec, n); 241 | } else { 242 | #[cfg(not(any(loom, feature = "std")))] 243 | core::hint::spin_loop(); 244 | #[cfg(any(loom, feature = "std"))] 245 | crate::sync::yield_now(); 246 | } 247 | } 248 | } 249 | } 250 | 251 | /// # Safety 252 | /// 253 | /// Must already hold the lock on the available list 254 | unsafe fn try_acquire_available_locked( 255 | &self, 256 | head: *const Node, 257 | ) -> (*const Node, usize) { 258 | debug_assert!(N >= 1); 259 | debug_assert!(!head.is_null()); 260 | 261 | let mut tail = head; 262 | let mut n = 1; 263 | let mut next = unsafe { &*tail }.available_next.load(Ordering::Relaxed); 264 | 265 | while !next.is_null() && n < N { 266 | debug_assert_eq!((next as usize) & LOCK_BIT, 0); 267 | tail = next; 268 | next = unsafe { &*tail }.available_next.load(Ordering::Relaxed); 269 | n += 1; 270 | } 271 | 272 | // NOTE: This releases the lock 273 | self.head_released.store(next, Ordering::Release); 274 | unsafe { &*tail } 275 | .available_next 276 | .store(core::ptr::null_mut(), Ordering::Relaxed); 277 | 278 | (head, n) 279 | } 280 | } 281 | 282 | impl Pool 283 | where 284 | T: Default, 285 | { 286 | /// Acquire a `T` from the pool. 287 | /// 288 | /// If no pre-existing `T` is available, allocates a new `T` using `T::default()`. 289 | /// 290 | /// Remember to return the acquired `T`s to the pool using [`Pool::release`]. 291 | pub fn acquire(&self) -> Acquired<'_, T> { 292 | self.acquire_many::<1>().into_iter().next().expect("N = 1") 293 | } 294 | 295 | /// Acquire `N` `T`s from the pool. 296 | /// 297 | /// If not enough pre-existing `T`s are available, remaining `T`s are allocated using 298 | /// `T::default()`. 299 | /// 300 | /// Remember to return the acquired `T`s to the pool using [`Pool::release_many`]. 301 | pub fn acquire_many(&self) -> [Acquired<'_, T>; N] { 302 | debug_assert!(N >= 1); 303 | 304 | let (mut head, n) = self.try_acquire_available::(); 305 | assert!(n <= N); 306 | 307 | let mut tail = core::ptr::null(); 308 | [(); N].map(|_| { 309 | if !head.is_null() { 310 | tail = head; 311 | // Safety: Nodes are never deallocated. 312 | let rec = unsafe { &*head }; 313 | head = rec.available_next.load(Ordering::Relaxed); 314 | Acquired(rec) 315 | } else { 316 | let rec = self.acquire_new(); 317 | // Make sure we also link in the newly allocated nodes. 318 | if !tail.is_null() { 319 | unsafe { &*tail } 320 | .available_next 321 | .store(rec as *const _ as *mut _, Ordering::Relaxed); 322 | } 323 | tail = rec as *const _; 324 | Acquired(rec) 325 | } 326 | }) 327 | } 328 | 329 | pub(crate) fn acquire_new(&self) -> &Node 330 | where 331 | T: Default, 332 | { 333 | // No free Nodes -- need to allocate a new one 334 | let node = Box::into_raw(Box::new(Node::new(self.discriminator, T::default()))); 335 | // And stick it at the head of the linked list 336 | let mut head = self.head.load(Ordering::Acquire); 337 | loop { 338 | // Safety: hazptr was never shared, so &mut is ok. 339 | unsafe { &mut *node }.next.with_mut(|p| *p = head); 340 | match self.head.compare_exchange_weak( 341 | head, 342 | node, 343 | // NOTE: Folly uses Release, but needs to be both for the load on success. 344 | Ordering::AcqRel, 345 | Ordering::Acquire, 346 | ) { 347 | Ok(_) => { 348 | // NOTE: Folly uses SeqCst because it's the default, not clear if 349 | // necessary. 350 | self.count.fetch_add(1, Ordering::SeqCst); 351 | // Safety: Nodes are never de-allocated while the domain lives. 352 | break unsafe { &*node }; 353 | } 354 | Err(head_now) => { 355 | // Head has changed, try again with that as our next ptr. 356 | head = head_now 357 | } 358 | } 359 | } 360 | } 361 | } 362 | 363 | impl Pool { 364 | /// Release a `T` back to the pool. 365 | /// 366 | /// This will make it available to subsequent calls to [`Pool::acquire`]. 367 | /// 368 | /// # Panics 369 | /// 370 | /// If the [`Acquired`] item was obtained from a different [`Pool`]. 371 | pub fn release(&self, rec: Acquired<'_, T>) { 372 | let rec = rec.0; 373 | assert!(rec.available_next.load(Ordering::Relaxed).is_null()); 374 | self.push_available(rec, rec); 375 | } 376 | 377 | /// Release multiple `T`s back to the pool at once. 378 | /// 379 | /// This will make it available to subsequent calls to [`Pool::acquire`]. 380 | /// 381 | /// # Panics 382 | /// 383 | /// If the [`Acquired`] item was obtained from a different [`Pool`]. 384 | pub fn release_many(&self, recs: [Acquired<'_, T>; N]) { 385 | if N == 0 { 386 | return; 387 | } 388 | 389 | let head = recs[0].0; 390 | let tail = recs.last().expect("N > 0").0; 391 | assert!(tail.available_next.load(Ordering::Relaxed).is_null()); 392 | self.push_available(head, tail); 393 | } 394 | 395 | fn push_available(&self, head: &Node, tail: &Node) { 396 | debug_assert!(tail.available_next.load(Ordering::Relaxed).is_null()); 397 | if cfg!(debug_assertions) { 398 | let mut node = head; 399 | loop { 400 | assert_eq!( 401 | self.discriminator, node.discriminator, 402 | "Tried to call Pool::release with Acquired object \ 403 | that was obtained from a different Pool instance" 404 | ); 405 | let next = node.available_next.load(Ordering::Acquire); 406 | if next.is_null() { 407 | break; 408 | } else { 409 | // Safety: Nodes are never deallocated. 410 | node = unsafe { &*next }; 411 | } 412 | } 413 | assert_eq!(node as *const _, tail as *const _); 414 | } 415 | debug_assert_eq!(head as *const _ as usize & LOCK_BIT, 0); 416 | loop { 417 | let avail = self.head_released.load(Ordering::Acquire); 418 | if (avail as usize & LOCK_BIT) == 0 { 419 | tail.available_next 420 | .store(avail as *mut _, Ordering::Relaxed); 421 | if self 422 | .head_released 423 | .compare_exchange_weak( 424 | avail, 425 | head as *const _ as *mut _, 426 | Ordering::AcqRel, 427 | Ordering::Relaxed, 428 | ) 429 | .is_ok() 430 | { 431 | break; 432 | } 433 | } else { 434 | #[cfg(not(any(loom, feature = "std")))] 435 | core::hint::spin_loop(); 436 | #[cfg(any(loom, feature = "std"))] 437 | crate::sync::yield_now(); 438 | } 439 | } 440 | } 441 | } 442 | 443 | /// An iterator over all the `T`s in the pool (whether acquired or not). 444 | pub struct PoolIter<'a, T> { 445 | list: PhantomData<&'a Pool>, 446 | head: *const Node, 447 | } 448 | 449 | impl Pool { 450 | /// Iterate over all the `T`s in the pool (whether acquired or not). 451 | pub fn iter(&self) -> PoolIter<'_, T> { 452 | PoolIter { 453 | list: PhantomData, 454 | head: self.head.load(Ordering::Acquire), 455 | } 456 | } 457 | } 458 | 459 | impl<'a, T> IntoIterator for &'a Pool { 460 | type IntoIter = PoolIter<'a, T>; 461 | type Item = &'a T; 462 | fn into_iter(self) -> Self::IntoIter { 463 | self.iter() 464 | } 465 | } 466 | 467 | impl<'pool, T> Iterator for PoolIter<'pool, T> { 468 | type Item = &'pool T; 469 | 470 | fn next(&mut self) -> Option { 471 | if self.head.is_null() { 472 | None 473 | } else { 474 | // Safety: Nodes are never de-allocated while the domain lives. 475 | let n = unsafe { &*self.head }; 476 | let v = &n.v; 477 | self.head = n.next.load(Ordering::Relaxed); 478 | Some(v) 479 | } 480 | } 481 | } 482 | 483 | impl Drop for Pool { 484 | fn drop(&mut self) { 485 | let mut node: *mut Node = self.head.with_mut(|p| *p); 486 | while !node.is_null() { 487 | // Safety: we have &mut self, so no-one holds any of our hazard pointers any more, 488 | // as all holders are tied to 'domain (which must have expired to create the &mut). 489 | let mut n: Box> = unsafe { Box::from_raw(node) }; 490 | node = n.next.with_mut(|p| *p); 491 | drop(n); 492 | } 493 | } 494 | } 495 | 496 | // Helpers to set and unset the lock bit on a `*mut Node` without losing pointer 497 | // provenance. See https://github.com/rust-lang/miri/issues/1993 for details. 498 | fn with_lock_bit(ptr: *mut Node) -> *mut Node { 499 | int_to_ptr_with_provenance(ptr as usize | LOCK_BIT, ptr) 500 | } 501 | #[allow(dead_code)] 502 | fn without_lock_bit(ptr: *mut Node) -> *mut Node { 503 | int_to_ptr_with_provenance(ptr as usize & !LOCK_BIT, ptr) 504 | } 505 | fn int_to_ptr_with_provenance(addr: usize, prov: *mut T) -> *mut T { 506 | let ptr = prov.cast::(); 507 | ptr.wrapping_add(addr.wrapping_sub(ptr as usize)).cast() 508 | } 509 | 510 | #[cfg(test)] 511 | mod tests { 512 | use super::Pool; 513 | use core::{ 514 | ptr::null_mut, 515 | sync::atomic::{AtomicU8, Ordering}, 516 | }; 517 | 518 | #[test] 519 | fn simple() { 520 | let list = Pool::::new(); 521 | let rec1 = list.acquire(); 522 | rec1.store(1, Ordering::Release); 523 | let rec2 = list.acquire(); 524 | rec2.store(2, Ordering::Release); 525 | list.release(rec1); 526 | let rec3 = list.acquire(); 527 | assert_eq!(rec3.load(Ordering::Acquire), 1); 528 | list.release(rec2); 529 | } 530 | 531 | #[test] 532 | fn acquire_many_skips_used_nodes() { 533 | let list = Pool::<()>::new(); 534 | let rec1 = list.acquire(); 535 | let rec2 = list.acquire(); 536 | let rec3 = list.acquire(); 537 | 538 | assert_eq!( 539 | rec3.0.next.load(Ordering::Relaxed), 540 | rec2.0 as *const _ as *mut _ 541 | ); 542 | assert_eq!( 543 | rec2.0.next.load(Ordering::Relaxed), 544 | rec1.0 as *const _ as *mut _ 545 | ); 546 | assert_eq!(rec1.0.next.load(Ordering::Relaxed), core::ptr::null_mut()); 547 | list.release(rec1); 548 | list.release(rec3); 549 | 550 | let [one, two, three] = list.acquire_many(); 551 | 552 | assert_eq!( 553 | one.0.available_next.load(Ordering::Relaxed), 554 | two.0 as *const _ as *mut _ 555 | ); 556 | assert_eq!( 557 | two.0.available_next.load(Ordering::Relaxed), 558 | three.0 as *const _ as *mut _ 559 | ); 560 | assert_eq!( 561 | three.0.available_next.load(Ordering::Relaxed), 562 | core::ptr::null_mut(), 563 | ); 564 | 565 | // one was previously rec3 566 | // two was previously rec1 567 | // so proper ordering for next is three -> rec3/one -> rec2 -> rec1/two 568 | assert_eq!( 569 | three.0.next.load(Ordering::Relaxed), 570 | one.0 as *const _ as *mut _ 571 | ); 572 | assert_eq!( 573 | one.0.next.load(Ordering::Relaxed), 574 | rec2.0 as *const _ as *mut _ 575 | ); 576 | assert_eq!( 577 | rec2.0.next.load(Ordering::Relaxed), 578 | two.0 as *const _ as *mut _ 579 | ); 580 | } 581 | 582 | #[test] 583 | fn acquire_many_orders_nodes_between_acquires() { 584 | let list = Pool::<()>::new(); 585 | let rec1 = list.acquire(); 586 | let rec2 = list.acquire(); 587 | 588 | assert_eq!( 589 | rec2.0.next.load(Ordering::Relaxed), 590 | rec1.0 as *const _ as *mut _ 591 | ); 592 | list.release(rec2); 593 | 594 | // one was previously rec2 595 | // two is a new node 596 | let [one, two] = list.acquire_many(); 597 | 598 | assert_eq!( 599 | one.0.available_next.load(Ordering::Relaxed), 600 | two.0 as *const _ as *mut _ 601 | ); 602 | assert_eq!( 603 | two.0.available_next.load(Ordering::Relaxed), 604 | core::ptr::null_mut(), 605 | ); 606 | 607 | assert_eq!( 608 | two.0.next.load(Ordering::Relaxed), 609 | one.0 as *const _ as *mut _ 610 | ); 611 | assert_eq!( 612 | one.0.next.load(Ordering::Relaxed), 613 | rec1.0 as *const _ as *mut _ 614 | ); 615 | } 616 | 617 | #[test] 618 | fn acquire_many_properly_orders_reused_nodes() { 619 | let list = Pool::<()>::new(); 620 | let rec1 = list.acquire(); 621 | let rec2 = list.acquire(); 622 | let rec3 = list.acquire(); 623 | 624 | // rec3 -> rec2 -> rec1 625 | assert_eq!(rec1.0.next.load(Ordering::Relaxed), core::ptr::null_mut(),); 626 | assert_eq!( 627 | rec2.0.next.load(Ordering::Relaxed), 628 | rec1.0 as *const _ as *mut _ 629 | ); 630 | assert_eq!( 631 | rec3.0.next.load(Ordering::Relaxed), 632 | rec2.0 as *const _ as *mut _ 633 | ); 634 | 635 | // rec1 available_next -> null 636 | list.release(rec1); 637 | // rec2 available_next -> rec1 638 | list.release(rec2); 639 | // rec3 available_next -> rec2 640 | list.release(rec3); 641 | 642 | // one is rec3 643 | // two is rec2 644 | // three is rec1 645 | let [one, two, three, four, five] = list.acquire_many(); 646 | 647 | assert_eq!( 648 | one.0.available_next.load(Ordering::Relaxed), 649 | two.0 as *const _ as *mut _ 650 | ); 651 | assert_eq!( 652 | two.0.available_next.load(Ordering::Relaxed), 653 | three.0 as *const _ as *mut _ 654 | ); 655 | assert_eq!( 656 | three.0.available_next.load(Ordering::Relaxed), 657 | four.0 as *const _ as *mut _ 658 | ); 659 | assert_eq!( 660 | four.0.available_next.load(Ordering::Relaxed), 661 | five.0 as *const _ as *mut _ 662 | ); 663 | assert_eq!( 664 | five.0.available_next.load(Ordering::Relaxed), 665 | core::ptr::null_mut(), 666 | ); 667 | 668 | assert_eq!( 669 | five.0.next.load(Ordering::Relaxed), 670 | four.0 as *const _ as *mut _ 671 | ); 672 | assert_eq!( 673 | four.0.next.load(Ordering::Relaxed), 674 | one.0 as *const _ as *mut _ 675 | ); 676 | assert_eq!( 677 | one.0.next.load(Ordering::Relaxed), 678 | two.0 as *const _ as *mut _ 679 | ); 680 | assert_eq!( 681 | two.0.next.load(Ordering::Relaxed), 682 | three.0 as *const _ as *mut _ 683 | ); 684 | assert_eq!(three.0.next.load(Ordering::Relaxed), null_mut()); 685 | } 686 | } 687 | --------------------------------------------------------------------------------