├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── TODO ├── benches └── cmp.rs ├── rustfmt.toml ├── src ├── clonemap.rs ├── existing_or_new.rs ├── lib.rs ├── map.rs ├── raw │ ├── config.rs │ ├── debug.rs │ ├── iterator.rs │ └── mod.rs ├── set.rs └── tests │ ├── acts_like_map.rs │ ├── acts_like_set.rs │ ├── compile_fail.rs │ └── mod.rs └── tests └── version.rs /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # Appveyor configuration template for Rust using rustup for Rust installation 2 | # https://github.com/starkat99/appveyor-rust 3 | 4 | ## Operating System (VM environment) ## 5 | 6 | # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. 7 | os: Visual Studio 2015 8 | 9 | ## Build Matrix ## 10 | 11 | # This configuration will setup a build for each channel & target combination (12 windows 12 | # combinations in all). 13 | # 14 | # There are 3 channels: stable, beta, and nightly. 15 | # 16 | # Alternatively, the full version may be specified for the channel to build using that specific 17 | # version (e.g. channel: 1.5.0) 18 | # 19 | # The values for target are the set of windows Rust build targets. Each value is of the form 20 | # 21 | # ARCH-pc-windows-TOOLCHAIN 22 | # 23 | # Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker 24 | # toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for 25 | # a description of the toolchain differences. 26 | # See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of 27 | # toolchains and host triples. 28 | # 29 | # Comment out channel/target combos you do not wish to build in CI. 30 | # 31 | # You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands 32 | # and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly 33 | # channels to enable unstable features when building for nightly. Or you could add additional 34 | # matrix entries to test different combinations of features. 35 | environment: 36 | matrix: 37 | 38 | ### MSVC Toolchains ### 39 | 40 | # Stable 64-bit MSVC 41 | - channel: stable 42 | target: x86_64-pc-windows-msvc 43 | # Stable 32-bit MSVC 44 | - channel: stable 45 | target: i686-pc-windows-msvc 46 | # Beta 64-bit MSVC 47 | # - channel: beta 48 | # target: x86_64-pc-windows-msvc 49 | # Beta 32-bit MSVC 50 | # - channel: beta 51 | # target: i686-pc-windows-msvc 52 | # Nightly 64-bit MSVC 53 | # - channel: nightly 54 | # target: x86_64-pc-windows-msvc 55 | #cargoflags: --features "unstable" 56 | # Nightly 32-bit MSVC 57 | # - channel: nightly 58 | # target: i686-pc-windows-msvc 59 | #cargoflags: --features "unstable" 60 | 61 | ### GNU Toolchains ### 62 | 63 | # Stable 64-bit GNU 64 | - channel: stable 65 | target: x86_64-pc-windows-gnu 66 | # Stable 32-bit GNU 67 | - channel: stable 68 | target: i686-pc-windows-gnu 69 | # Beta 64-bit GNU 70 | # - channel: beta 71 | # target: x86_64-pc-windows-gnu 72 | # Beta 32-bit GNU 73 | # - channel: beta 74 | # target: i686-pc-windows-gnu 75 | # Nightly 64-bit GNU 76 | # - channel: nightly 77 | # target: x86_64-pc-windows-gnu 78 | #cargoflags: --features "unstable" 79 | # Nightly 32-bit GNU 80 | # - channel: nightly 81 | # target: i686-pc-windows-gnu 82 | #cargoflags: --features "unstable" 83 | 84 | ### Allowed failures ### 85 | 86 | # See Appveyor documentation for specific details. In short, place any channel or targets you wish 87 | # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build 88 | # or test failure in the matching channels/targets from failing the entire build. 89 | #matrix: 90 | # allow_failures: 91 | # - channel: nightly 92 | 93 | # If you only care about stable channel build failures, uncomment the following line: 94 | #- channel: beta 95 | 96 | ## Install Script ## 97 | 98 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 99 | # specified by the 'channel' and 'target' environment variables from the build matrix. This uses 100 | # rustup to install Rust. 101 | # 102 | # For simple configurations, instead of using the build matrix, you can simply set the 103 | # default-toolchain and default-host manually here. 104 | install: 105 | - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 106 | - rustup-init -yv --default-toolchain %channel% --default-host %target% 107 | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin 108 | - rustc -vV 109 | - cargo -vV 110 | 111 | ## Build Script ## 112 | 113 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 114 | # the "directory does not contain a project or solution file" error. 115 | build: false 116 | 117 | # Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs 118 | #directly or perform other testing commands. Rust will automatically be placed in the PATH 119 | # environment variable. 120 | test_script: 121 | - cargo test --verbose --release --all --all-features %cargoflags% 122 | - cargo test --verbose --release --all %cargoflags% 123 | - cargo test --verbose --release %cargoflags% -- --ignored 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | core 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - 1.33.0 5 | - stable 6 | - beta 7 | - nightly 8 | os: 9 | - linux 10 | - osx 11 | matrix: 12 | allow_failures: 13 | - rust: nightly 14 | 15 | before_install: 16 | - set -e 17 | - export RUSTFLAGS="-D warnings" 18 | - if [ "$TRAVIS_RUST_VERSION" != "nightly" ]; then 19 | rustup self update && 20 | rustup component add rustfmt clippy && 21 | cargo clippy --version; 22 | fi 23 | 24 | script: 25 | - if [ "$TRAVIS_RUST_VERSION" != "nightly" ]; then 26 | cargo fmt --all -- --check && 27 | cargo clippy --all --tests --examples -- --deny clippy::all; 28 | fi 29 | - export RUST_BACKTRACE=1 30 | - export CARGO_INCREMENTAL=1 31 | - export PATH="$PATH":~/.cargo/bin 32 | - cargo test --release --all --all-features 33 | - cargo test --release --all 34 | - cargo test --release -- --ignored 35 | - cargo doc --no-deps 36 | - if [ "$TRAVIS_RUST_VERSION" = "nightly" ]; then 37 | cargo test --all --release --benches; 38 | fi 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.4 2 | 3 | * Adding the `CloneConMap`, a map-like type cloning elements instead of using 4 | `Arc`s. 5 | 6 | # 0.1.3 7 | 8 | * Support for rayon `FromParallelIterator` and `ParallelExtend` (under a feature 9 | flag). 10 | 11 | # 0.1.2 12 | 13 | * Adding the `ConSet` type to have sets as well as maps. 14 | * The `Debug` of `ConMap` is now available for `V: !Sized` too. 15 | 16 | # 0.1.1 17 | 18 | * Introducing 'static bounds to batch a soundness hole around delayed 19 | destructions. 20 | 21 | # 0.1.0 22 | 23 | * The Raw and `ConMap` types. 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "contrie" 3 | version = "0.1.4" 4 | authors = [ 5 | "Michal 'vorner' Vaner ", 6 | "Edoardo Rossi ", 7 | "Evan Cameron ", 8 | ] 9 | edition = "2018" 10 | description = "Concurrent map and set" 11 | documentation = "https://docs.rs/contrie" 12 | repository = "https://github.com/vorner/contrie" 13 | readme = "README.md" 14 | keywords = ["atomic", "concurrent", "map", "set", "lock-free"] 15 | categories = ["concurrency", "algorithms", "data-structures"] 16 | license = "Apache-2.0/MIT" 17 | 18 | [badges] 19 | travis-ci = { repository = "vorner/contrie" } 20 | appveyor = { repository = "vorner/contrie" } 21 | maintenance = { status = "actively-developed" } 22 | 23 | [dependencies] 24 | arrayvec = "~0.4" 25 | bitflags = "~1" 26 | crossbeam-epoch = "~0.7.2" 27 | # TODO: Consider what to do with the union feature. Why is it still requiring nightly? 28 | smallvec = "~0.6" 29 | rayon = { version = "~1", optional = true } 30 | 31 | [dev-dependencies] 32 | crossbeam-utils = "~0.6" 33 | proptest = "~0.9.3" 34 | rayon = "~1" 35 | rand = "~0.7" 36 | version-sync = "~0.8" 37 | 38 | [profile.test] 39 | # Some tests are rather slow. Furthermore, optimalisations tend to provoke races and UBs to 40 | # manifest, so we want to try that in tests if possible. 41 | opt-level = 1 42 | 43 | [package.metadata.docs.rs] 44 | all-features= true 45 | -------------------------------------------------------------------------------- /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 | Copyright (c) 2017 arc-swap developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConTrie 2 | 3 | [![Travis Build Status](https://api.travis-ci.org/vorner/contrie.png?branch=master)](https://travis-ci.org/vorner/contrie) 4 | [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/9e0mmfqqp4o9ap5c/branch/master?svg=true)](https://ci.appveyor.com/project/vorner/contrie/branch/master) 5 | 6 | A concurrent hash-trie map & set. 7 | 8 | Still in somewhat experimental state, large parts are missing and some API 9 | changes are to be expected (though it'll probably still stay being a concurrent 10 | map & set). 11 | 12 | Inspired by this [article] and [Wikipedia entry], though simplified 13 | significantly (at the cost of some features). 14 | 15 | Read [the documentation](https://docs.rs/contrie) before using, there are some 16 | quirks to be aware of. 17 | 18 | ## Practical performance & project status 19 | 20 | It turns out the data structure is somewhat memory hungry and not performing 21 | that well in practice. This, and my lack of time leads to this project being a 22 | bit neglected. 23 | 24 | That being said, it is possible the performance & memory consumption is due to 25 | the simplifications (this always uses full nodes, the article compresses them to 26 | contain only the relevant pointers) and if someone else wants to play with it 27 | and improve the state of the project, I'll happily accept pull requests doing 28 | so. 29 | 30 | ## License 31 | 32 | Licensed under either of 33 | 34 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 35 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 36 | 37 | at your option. 38 | 39 | ### Contribution 40 | 41 | Unless you explicitly state otherwise, any contribution intentionally 42 | submitted for inclusion in the work by you, as defined in the Apache-2.0 43 | license, shall be dual licensed as above, without any additional terms 44 | or conditions. 45 | 46 | [article]: https://www.researchgate.net/publication/221643801_Concurrent_Tries_with_Efficient_Non-Blocking_Snapshots 47 | [Wikipedia entry]: https://en.wikipedia.org/wiki/Ctrie 48 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Parametrize the things with specification about sequential consistency 2 | - So people can rely on relative order of insertions, etc. 3 | * Valgrind into CI 4 | * Split the raw/mod.rs into multiple files? It is definitely getting a bit long. 5 | * Some refactoring around the pointer juggling in raw 6 | - The manual casting seems a bit error prone 7 | - Should we have our own wrapper that loads and gives some king of Option>? 8 | * Manually implement further traits for ExistingOrNew 9 | - Like Display, Hex, etc… 10 | * Is the deref on ExistingOrNew an abuse? 11 | * Due to crossbeam-epoch, destruction of keys and values may be moved *past* the destructor of the map. 12 | - Add 'static bounds as a temporary solution ✔ 13 | - Explore if this can be worked around by using a separate Collector and flush it in the destructor 14 | -------------------------------------------------------------------------------- /benches/cmp.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use std::iter; 6 | 7 | use rand::prelude::*; 8 | 9 | fn vals(cnt: usize) -> Vec { 10 | iter::repeat_with(random).take(cnt).collect() 11 | } 12 | 13 | macro_rules! typed_bench { 14 | ($name: ident, $type: ty) => { 15 | mod $name { 16 | use test::{black_box, Bencher}; 17 | 18 | use super::vals; 19 | 20 | fn fill(cnt: usize) -> ($type, Vec) { 21 | let vals = vals(cnt); 22 | 23 | let map = vals.iter().cloned().map(|i| (i, i)).collect(); 24 | (map, vals) 25 | } 26 | 27 | fn lookup_n(cnt: usize, bencher: &mut Bencher) { 28 | let (map, mut to_lookup) = fill(cnt); 29 | to_lookup.drain(50..); 30 | to_lookup.extend(vals(50).into_iter()); 31 | 32 | bencher.iter(|| { 33 | for val in &to_lookup { 34 | black_box(map.get(&val)); 35 | } 36 | }); 37 | } 38 | 39 | #[bench] 40 | fn lookup_small(bencher: &mut Bencher) { 41 | lookup_n(100, bencher); 42 | } 43 | 44 | #[bench] 45 | fn lookup_mid(bencher: &mut Bencher) { 46 | lookup_n(10_000, bencher); 47 | } 48 | 49 | #[bench] 50 | fn lookup_huge(bencher: &mut Bencher) { 51 | lookup_n(10_000_000, bencher); 52 | } 53 | } 54 | }; 55 | } 56 | 57 | typed_bench!(hash_map, std::collections::HashMap); 58 | typed_bench!(btree_map, std::collections::BTreeMap); 59 | typed_bench!(contrie_map, contrie::ConMap); 60 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vorner/contrie/caf624de5cd9e66af1546fdcd8d9fd889d414c07/rustfmt.toml -------------------------------------------------------------------------------- /src/clonemap.rs: -------------------------------------------------------------------------------- 1 | //! The [`CloneConMap`][crate::CloneConMap] type and its helpers. 2 | 3 | use std::borrow::Borrow; 4 | use std::collections::hash_map::RandomState; 5 | use std::fmt::{Debug, Formatter, Result as FmtResult}; 6 | use std::hash::{BuildHasher, Hash}; 7 | use std::iter::FromIterator; 8 | use std::marker::PhantomData; 9 | 10 | #[cfg(feature = "rayon")] 11 | use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelExtend, ParallelIterator}; 12 | 13 | use crate::existing_or_new::ExistingOrNew; 14 | use crate::raw::config::Config; 15 | use crate::raw::{self, Raw}; 16 | 17 | #[derive(Clone)] 18 | struct CloneMapPayload((K, V)); 19 | 20 | impl Borrow for CloneMapPayload { 21 | fn borrow(&self) -> &K { 22 | let &(ref k, _) = &self.0; 23 | k 24 | } 25 | } 26 | 27 | struct CloneMapConfig(PhantomData<(K, V)>); 28 | 29 | impl Config for CloneMapConfig 30 | where 31 | K: Clone + Hash + Eq + 'static, 32 | V: Clone + 'static, 33 | { 34 | type Payload = CloneMapPayload; 35 | type Key = K; 36 | } 37 | 38 | /// The iterator of the [`CloneConMap`]. 39 | /// 40 | /// See the [`iter`][CloneConMap::iter] method for details. 41 | pub struct Iter<'a, K, V, S> 42 | where 43 | K: Clone + Hash + Eq + 'static, 44 | V: Clone + 'static, 45 | { 46 | inner: raw::iterator::Iter<'a, CloneMapConfig, S>, 47 | } 48 | 49 | impl<'a, K, V, S> Iterator for Iter<'a, K, V, S> 50 | where 51 | K: Clone + Hash + Eq + 'static, 52 | V: Clone + 'static, 53 | { 54 | type Item = (K, V); 55 | fn next(&mut self) -> Option<(K, V)> { 56 | self.inner.next().map(|p| (p.0).clone()) 57 | } 58 | } 59 | 60 | /// A concurrent map that clones its elements. 61 | /// 62 | /// This flavour stores the data as `(K, V)` tuples; it clones 63 | /// independently both the elements (to be intended as the key and the 64 | /// value) of the tuple. The return values of its functions are clones 65 | /// of the stored values. This makes this data structure suitable for 66 | /// types cheap to clone. 67 | /// 68 | /// Iteration returns cloned copies of its elements. The 69 | /// [`FromIterator`] and [`Extend`] traits accept tuples as arguments. 70 | /// Furthermore, the [`Extend`] is also implemented for shared 71 | /// references (to allow extending the same map concurrently from 72 | /// multiple threads). 73 | /// 74 | /// # Examples 75 | /// 76 | /// ```rust 77 | /// use contrie::CloneConMap; 78 | /// use crossbeam_utils::thread; 79 | /// 80 | /// let map = CloneConMap::new(); 81 | /// 82 | /// thread::scope(|s| { 83 | /// s.spawn(|_| { 84 | /// map.insert("hello", 1); 85 | /// }); 86 | /// s.spawn(|_| { 87 | /// map.insert("world", 2); 88 | /// }); 89 | /// }).unwrap(); 90 | /// assert_eq!(1, map.get("hello").unwrap().1); 91 | /// assert_eq!(2, map.get("world").unwrap().1); 92 | /// ``` 93 | /// 94 | /// ```rust 95 | /// use contrie::clonemap::{CloneConMap}; 96 | /// 97 | /// let map_1: CloneConMap> = CloneConMap::new(); 98 | /// 99 | /// map_1.insert(42, vec![1, 2, 3]); 100 | /// map_1.insert(43, vec![1, 2, 3, 4]); 101 | /// 102 | /// assert_eq!(3, map_1.get(&42).unwrap().1.len()); 103 | /// assert_eq!(4, map_1.get(&43).unwrap().1.len()); 104 | /// assert_eq!(None, map_1.get(&44)); 105 | /// 106 | /// let map_2 = CloneConMap::new(); 107 | /// map_2.insert(44, map_1.get(&43).unwrap().1); 108 | /// assert_eq!(4, map_2.get(&44).unwrap().1.len()); 109 | /// ``` 110 | pub struct CloneConMap 111 | where 112 | K: Clone + Hash + Eq + 'static, 113 | V: Clone + 'static, 114 | { 115 | raw: Raw, S>, 116 | } 117 | 118 | impl CloneConMap 119 | where 120 | K: Clone + Hash + Eq + 'static, 121 | V: Clone + 'static, 122 | { 123 | /// Creates a new empty map. 124 | pub fn new() -> Self { 125 | Self::with_hasher(RandomState::default()) 126 | } 127 | } 128 | 129 | impl CloneConMap 130 | where 131 | K: Clone + Hash + Eq + 'static, 132 | V: Clone + 'static, 133 | S: BuildHasher, 134 | { 135 | /// Inserts a new element as a tuple `(key, value)`. 136 | /// 137 | /// Any previous element with the same key is replaced and returned. 138 | pub fn insert(&self, key: K, value: V) -> Option<(K, V)> { 139 | let pin = crossbeam_epoch::pin(); 140 | self.raw 141 | .insert(CloneMapPayload((key, value)), &pin) 142 | .map(|p| p.0.clone()) 143 | } 144 | 145 | /// Looks up or inserts an element as a tuple `(key, value)`. 146 | /// 147 | /// It looks up an element. If it isn't present, the provided one is 148 | /// inserted instead. Either way, an element is returned. 149 | pub fn get_or_insert(&self, key: K, value: V) -> ExistingOrNew<(K, V)> { 150 | self.get_or_insert_with(key, || value) 151 | } 152 | 153 | /// Looks up or inserts a newly created element. 154 | /// 155 | /// It looks up an element. If it isn't present, the provided 156 | /// closure is used to create a new one insert it. Either way, an 157 | /// element is returned. 158 | /// 159 | /// # Quirks 160 | /// 161 | /// Due to races in case of concurrent accesses, the closure may be 162 | /// called even if the value is not subsequently inserted and an 163 | /// existing element is returned. This should be relatively rare 164 | /// (another thread must insert the new element between this method 165 | /// observes an empty slot and manages to insert the new element). 166 | pub fn get_or_insert_with(&self, key: K, create: F) -> ExistingOrNew<(K, V)> 167 | where 168 | F: FnOnce() -> V, 169 | { 170 | let pin = crossbeam_epoch::pin(); 171 | 172 | self.raw 173 | .get_or_insert_with( 174 | key, 175 | |key| { 176 | let value: V = create(); 177 | CloneMapPayload((key, value)) 178 | }, 179 | &pin, 180 | ) 181 | .map(|payload| (payload.0).clone()) 182 | } 183 | 184 | /// Looks up or inserts a default value of an element. 185 | /// 186 | /// This is like [get_or_insert_with][CloneConMap::get_or_insert_with], 187 | /// but a default value is used instead of manually providing a 188 | /// closure. 189 | pub fn get_or_insert_default(&self, key: K) -> ExistingOrNew<(K, V)> 190 | where 191 | V: Default, 192 | { 193 | self.get_or_insert_with(key, V::default) 194 | } 195 | } 196 | 197 | impl CloneConMap 198 | where 199 | K: Clone + Hash + Eq, 200 | V: Clone, 201 | S: BuildHasher, 202 | { 203 | /// Creates a new empty map, but with the provided hasher implementation. 204 | pub fn with_hasher(hasher: S) -> Self { 205 | Self { 206 | raw: Raw::with_hasher(hasher), 207 | } 208 | } 209 | 210 | /// Looks up an element. 211 | pub fn get(&self, key: &Q) -> Option<(K, V)> 212 | where 213 | Q: ?Sized + Eq + Hash, 214 | K: Borrow, 215 | { 216 | let pin = crossbeam_epoch::pin(); 217 | self.raw.get(key, &pin).map(|r| (r.0).clone()) 218 | } 219 | 220 | /// Removes an element identified by the given key, returning it. 221 | pub fn remove(&self, key: &Q) -> Option<(K, V)> 222 | where 223 | Q: ?Sized + Eq + Hash, 224 | K: Borrow, 225 | { 226 | let pin = crossbeam_epoch::pin(); 227 | self.raw.remove(key, &pin).map(|r| (r.0).clone()) 228 | } 229 | } 230 | 231 | impl CloneConMap 232 | where 233 | K: Clone + Hash + Eq, 234 | V: Clone, 235 | { 236 | /// Checks if the map is currently empty. 237 | /// 238 | /// Note that due to the nature of concurrent map, this is 239 | /// inherently racy ‒ another thread may add or remove elements 240 | /// between you call this method and act based on the result. 241 | pub fn is_empty(&self) -> bool { 242 | self.raw.is_empty() 243 | } 244 | 245 | /// Returns an iterator through the elements of the map. 246 | pub fn iter(&self) -> Iter { 247 | Iter { 248 | inner: raw::iterator::Iter::new(&self.raw), 249 | } 250 | } 251 | } 252 | 253 | impl Default for CloneConMap 254 | where 255 | K: Clone + Hash + Eq, 256 | V: Clone, 257 | { 258 | fn default() -> Self { 259 | Self::new() 260 | } 261 | } 262 | 263 | impl Debug for CloneConMap 264 | where 265 | K: Debug + Clone + Hash + Eq, 266 | V: Debug + Clone, 267 | { 268 | fn fmt(&self, fmt: &mut Formatter) -> FmtResult { 269 | fmt.debug_map().entries(self.iter()).finish() 270 | } 271 | } 272 | 273 | impl Clone for CloneConMap 274 | where 275 | K: Clone + Hash + Eq, 276 | V: Clone, 277 | S: Clone + BuildHasher, 278 | { 279 | fn clone(&self) -> Self { 280 | let builder = self.raw.hash_builder().clone(); 281 | let mut new = Self::with_hasher(builder); 282 | new.extend(self); 283 | new 284 | } 285 | } 286 | 287 | impl<'a, K, V, S> IntoIterator for &'a CloneConMap 288 | where 289 | K: Clone + Hash + Eq, 290 | V: Clone, 291 | { 292 | type Item = (K, V); 293 | type IntoIter = Iter<'a, K, V, S>; 294 | fn into_iter(self) -> Self::IntoIter { 295 | self.iter() 296 | } 297 | } 298 | 299 | impl<'a, K, V, S> Extend<(K, V)> for &'a CloneConMap 300 | where 301 | K: Clone + Hash + Eq, 302 | V: Clone, 303 | S: BuildHasher, 304 | { 305 | fn extend(&mut self, iter: T) 306 | where 307 | T: IntoIterator, 308 | { 309 | for (k, v) in iter { 310 | self.insert(k, v); 311 | } 312 | } 313 | } 314 | 315 | impl Extend<(K, V)> for CloneConMap 316 | where 317 | K: Clone + Hash + Eq, 318 | V: Clone, 319 | S: BuildHasher, 320 | { 321 | fn extend(&mut self, iter: T) 322 | where 323 | T: IntoIterator, 324 | { 325 | let mut me: &CloneConMap<_, _, _> = self; 326 | me.extend(iter); 327 | } 328 | } 329 | 330 | impl FromIterator<(K, V)> for CloneConMap 331 | where 332 | K: Clone + Hash + Eq, 333 | V: Clone, 334 | { 335 | fn from_iter(iter: T) -> Self 336 | where 337 | T: IntoIterator, 338 | { 339 | let mut me = CloneConMap::new(); 340 | me.extend(iter); 341 | me 342 | } 343 | } 344 | 345 | #[cfg(feature = "rayon")] 346 | impl ParallelExtend<(K, V)> for CloneConMap 347 | where 348 | K: Clone + Hash + Eq + Send + Sync, 349 | S: BuildHasher + Sync, 350 | V: Clone + Send + Sync, 351 | { 352 | fn par_extend(&mut self, par_iter: T) 353 | where 354 | T: IntoParallelIterator, 355 | { 356 | par_iter.into_par_iter().for_each(|(k, v)| { 357 | self.insert(k.clone(), v.clone()); 358 | }); 359 | } 360 | } 361 | 362 | #[cfg(feature = "rayon")] 363 | impl<'a, K, V, S> ParallelExtend<(K, V)> for &'a CloneConMap 364 | where 365 | K: Clone + Hash + Eq + Send + Sync, 366 | S: BuildHasher + Sync, 367 | V: Clone + Send + Sync, 368 | { 369 | fn par_extend(&mut self, par_iter: T) 370 | where 371 | T: IntoParallelIterator, 372 | { 373 | par_iter.into_par_iter().for_each(|(k, v)| { 374 | self.insert(k.clone(), v.clone()); 375 | }); 376 | } 377 | } 378 | 379 | #[cfg(feature = "rayon")] 380 | impl FromParallelIterator<(K, V)> for CloneConMap 381 | where 382 | K: Clone + Hash + Eq + Send + Sync, 383 | V: Clone + Send + Sync, 384 | { 385 | fn from_par_iter(par_iter: T) -> Self 386 | where 387 | T: IntoParallelIterator, 388 | { 389 | let mut me = CloneConMap::new(); 390 | me.par_extend(par_iter); 391 | me 392 | } 393 | } 394 | 395 | #[cfg(test)] 396 | mod tests { 397 | use crossbeam_utils::thread; 398 | use std::rc::Rc; 399 | 400 | #[cfg(feature = "rayon")] 401 | use rayon::prelude::*; 402 | 403 | use super::*; 404 | use crate::raw::tests::NoHasher; 405 | use crate::raw::LEVEL_CELLS; 406 | 407 | const TEST_THREADS: usize = 4; 408 | const TEST_BATCH: usize = 10000; 409 | const TEST_BATCH_SMALL: usize = 100; 410 | const TEST_REP: usize = 20; 411 | 412 | #[test] 413 | fn create_destroy() { 414 | let map: CloneConMap = CloneConMap::new(); 415 | drop(map); 416 | } 417 | 418 | #[test] 419 | fn debug_formatting() { 420 | let map: CloneConMap<&str, &str> = CloneConMap::new(); 421 | map.insert("hello", "world"); 422 | assert_eq!("{\"hello\": \"world\"}".to_owned(), format!("{:?}", map)); 423 | } 424 | 425 | #[test] 426 | fn lookup_empty() { 427 | let map: CloneConMap = CloneConMap::new(); 428 | assert!(map.get("hello").is_none()); 429 | } 430 | 431 | #[test] 432 | fn insert_lookup() { 433 | let map = CloneConMap::new(); 434 | assert!(map.insert("hello", "world").is_none()); 435 | assert!(map.get("world").is_none()); 436 | let found = map.get("hello").unwrap(); 437 | assert_eq!(("hello", "world"), found); 438 | } 439 | 440 | #[test] 441 | fn insert_overwrite_lookup() { 442 | let map = CloneConMap::new(); 443 | assert!(map.insert("hello", "world").is_none()); 444 | let old = map.insert("hello", "universe").unwrap(); 445 | assert_eq!(("hello", "world"), old); 446 | let found = map.get("hello").unwrap(); 447 | assert_eq!(("hello", "universe"), found); 448 | } 449 | 450 | // Insert a lot of things, to make sure we have multiple levels. 451 | #[test] 452 | fn insert_many() { 453 | let map = CloneConMap::new(); 454 | for i in 0..TEST_BATCH * LEVEL_CELLS { 455 | assert!(map.insert(i, i).is_none()); 456 | } 457 | 458 | for i in 0..TEST_BATCH * LEVEL_CELLS { 459 | assert_eq!(i, map.get(&i).unwrap().1); 460 | } 461 | } 462 | 463 | #[test] 464 | fn par_insert_many() { 465 | for _ in 0..TEST_REP { 466 | let map: CloneConMap = CloneConMap::new(); 467 | thread::scope(|s| { 468 | for t in 0..TEST_THREADS { 469 | let map = ↦ 470 | s.spawn(move |_| { 471 | for i in 0..TEST_BATCH { 472 | let num = t * TEST_BATCH + i; 473 | assert!(map.insert(num, num).is_none()); 474 | } 475 | }); 476 | } 477 | }) 478 | .unwrap(); 479 | 480 | for i in 0..TEST_BATCH * TEST_THREADS { 481 | assert_eq!(map.get(&i).unwrap().1, i); 482 | } 483 | } 484 | } 485 | 486 | #[test] 487 | fn par_get_many() { 488 | for _ in 0..TEST_REP { 489 | let map = CloneConMap::new(); 490 | for i in 0..TEST_BATCH * TEST_THREADS { 491 | assert!(map.insert(i, i).is_none()); 492 | } 493 | thread::scope(|s| { 494 | for t in 0..TEST_THREADS { 495 | let map = ↦ 496 | s.spawn(move |_| { 497 | for i in 0..TEST_BATCH { 498 | let num = t * TEST_BATCH + i; 499 | assert_eq!(map.get(&num).unwrap().1, num); 500 | } 501 | }); 502 | } 503 | }) 504 | .unwrap(); 505 | } 506 | } 507 | 508 | #[test] 509 | fn collisions() { 510 | let map = CloneConMap::with_hasher(NoHasher); 511 | // While their hash is the same under the hasher, they don't kick each other out. 512 | for i in 0..TEST_BATCH_SMALL { 513 | assert!(map.insert(i, i).is_none()); 514 | } 515 | // And all are present. 516 | for i in 0..TEST_BATCH_SMALL { 517 | assert_eq!(i, map.get(&i).unwrap().1); 518 | } 519 | // But reusing the key kicks the other one out. 520 | for i in 0..TEST_BATCH_SMALL { 521 | assert_eq!(i, map.insert(i, i + 1).unwrap().1); 522 | assert_eq!(i + 1, map.get(&i).unwrap().1); 523 | } 524 | } 525 | 526 | #[test] 527 | fn get_or_insert_empty() { 528 | let map = CloneConMap::new(); 529 | let val = map.get_or_insert("hello", 42); 530 | assert_eq!(42, val.1); 531 | assert_eq!("hello", val.0); 532 | assert!(val.is_new()); 533 | } 534 | 535 | #[test] 536 | fn get_or_insert_existing() { 537 | let map = CloneConMap::new(); 538 | assert!(map.insert("hello", 42).is_none()); 539 | let val = map.get_or_insert("hello", 0); 540 | // We still have the original 541 | assert_eq!(42, val.1); 542 | assert_eq!("hello", val.0); 543 | assert!(!val.is_new()); 544 | } 545 | 546 | #[test] 547 | fn get_or_insert_existing_with_counter() { 548 | let map = CloneConMap::new(); 549 | assert!(map.insert("hello", Rc::new(42)).is_none()); 550 | let val = map.get_or_insert("hello", Rc::new(0)); 551 | // We still have the original 552 | assert_eq!(&42, val.1.borrow()); 553 | assert_eq!("hello", val.0); 554 | assert_eq!(2, Rc::strong_count(&val.1)); 555 | let val = map.get_or_insert("hello", Rc::new(0)); 556 | assert_eq!(3, Rc::strong_count(&val.1)); 557 | assert!(!val.is_new()); 558 | } 559 | 560 | fn get_or_insert_many_inner(map: CloneConMap, len: usize) { 561 | for i in 0..len { 562 | let val = map.get_or_insert(i, i); 563 | assert_eq!(i, val.0); 564 | assert_eq!(i, val.1); 565 | assert!(val.is_new()); 566 | } 567 | 568 | for i in 0..len { 569 | let val = map.get_or_insert(i, 0); 570 | assert_eq!(i, val.0); 571 | assert_eq!(i, val.1); 572 | assert!(!val.is_new()); 573 | } 574 | } 575 | 576 | #[test] 577 | fn get_or_insert_many() { 578 | get_or_insert_many_inner(CloneConMap::new(), TEST_BATCH); 579 | } 580 | 581 | #[test] 582 | fn get_or_insert_collision() { 583 | get_or_insert_many_inner(CloneConMap::with_hasher(NoHasher), TEST_BATCH_SMALL); 584 | } 585 | 586 | #[test] 587 | fn simple_remove() { 588 | let map = CloneConMap::new(); 589 | assert!(map.remove(&42).is_none()); 590 | assert!(map.insert(42, "hello").is_none()); 591 | assert_eq!("hello", map.get(&42).unwrap().1); 592 | assert_eq!("hello", map.remove(&42).unwrap().1); 593 | assert!(map.get(&42).is_none()); 594 | assert!(map.is_empty()); 595 | assert!(map.remove(&42).is_none()); 596 | assert!(map.is_empty()); 597 | } 598 | 599 | fn remove_many_inner(mut map: CloneConMap, len: usize) { 600 | for i in 0..len { 601 | assert!(map.insert(i, i).is_none()); 602 | } 603 | for i in 0..len { 604 | assert_eq!(i, map.get(&i).unwrap().1); 605 | assert_eq!(i, map.remove(&i).unwrap().1); 606 | assert!(map.get(&i).is_none()); 607 | map.raw.assert_pruned(); 608 | } 609 | 610 | assert!(map.is_empty()); 611 | } 612 | 613 | #[test] 614 | fn remove_many() { 615 | remove_many_inner(CloneConMap::new(), TEST_BATCH); 616 | } 617 | 618 | #[test] 619 | fn remove_many_collision() { 620 | remove_many_inner(CloneConMap::with_hasher(NoHasher), TEST_BATCH_SMALL); 621 | } 622 | 623 | #[test] 624 | fn collision_remove_one_left() { 625 | let mut map = CloneConMap::with_hasher(NoHasher); 626 | map.insert(1, 1); 627 | map.insert(2, 2); 628 | 629 | map.raw.assert_pruned(); 630 | 631 | assert!(map.remove(&2).is_some()); 632 | map.raw.assert_pruned(); 633 | 634 | assert!(map.remove(&1).is_some()); 635 | 636 | map.raw.assert_pruned(); 637 | assert!(map.is_empty()); 638 | } 639 | 640 | #[test] 641 | fn remove_par() { 642 | let mut map = CloneConMap::new(); 643 | for i in 0..TEST_THREADS * TEST_BATCH { 644 | map.insert(i, i); 645 | } 646 | 647 | thread::scope(|s| { 648 | for t in 0..TEST_THREADS { 649 | let map = ↦ 650 | s.spawn(move |_| { 651 | for i in 0..TEST_BATCH { 652 | let num = t * TEST_BATCH + i; 653 | let val = map.remove(&num).unwrap(); 654 | assert_eq!(num, val.1); 655 | assert_eq!(num, val.0); 656 | } 657 | }); 658 | } 659 | }) 660 | .unwrap(); 661 | 662 | map.raw.assert_pruned(); 663 | assert!(map.is_empty()); 664 | } 665 | 666 | fn iter_test_inner(map: CloneConMap) { 667 | for i in 0..TEST_BATCH_SMALL { 668 | assert!(map.insert(i, i).is_none()); 669 | } 670 | 671 | let mut extracted = map.iter().map(|v| v.1).collect::>(); 672 | extracted.sort(); 673 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 674 | assert_eq!(expected, extracted); 675 | } 676 | 677 | #[test] 678 | fn iter() { 679 | let map = CloneConMap::new(); 680 | iter_test_inner(map); 681 | } 682 | 683 | #[test] 684 | fn iter_collision() { 685 | let map = CloneConMap::with_hasher(NoHasher); 686 | iter_test_inner(map); 687 | } 688 | 689 | #[test] 690 | fn collect() { 691 | let map = (0..TEST_BATCH_SMALL) 692 | .map(|i| (i, i)) 693 | .collect::>(); 694 | 695 | let mut extracted = map 696 | .iter() 697 | .map(|n| { 698 | assert_eq!(n.0, n.1); 699 | n.1 700 | }) 701 | .collect::>(); 702 | 703 | extracted.sort(); 704 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 705 | assert_eq!(expected, extracted); 706 | } 707 | 708 | #[test] 709 | fn par_extend() { 710 | let map = CloneConMap::new(); 711 | thread::scope(|s| { 712 | for t in 0..TEST_THREADS { 713 | let mut map = ↦ 714 | s.spawn(move |_| { 715 | let start = t * TEST_BATCH_SMALL; 716 | let iter = (start..start + TEST_BATCH_SMALL).map(|i| (i, i)); 717 | map.extend(iter); 718 | }); 719 | } 720 | }) 721 | .unwrap(); 722 | 723 | let mut extracted = map 724 | .iter() 725 | .map(|n| { 726 | assert_eq!(n.0, n.1); 727 | n.1 728 | }) 729 | .collect::>(); 730 | 731 | extracted.sort(); 732 | let expected = (0..TEST_THREADS * TEST_BATCH_SMALL).collect::>(); 733 | assert_eq!(expected, extracted); 734 | } 735 | 736 | #[cfg(feature = "rayon")] 737 | #[test] 738 | fn rayon_extend() { 739 | let mut map = CloneConMap::new(); 740 | map.par_extend((0..TEST_BATCH_SMALL).into_par_iter().map(|i| (i, i))); 741 | 742 | let mut extracted = map 743 | .iter() 744 | .map(|n| { 745 | assert_eq!(n.0, n.1); 746 | n.1 747 | }) 748 | .collect::>(); 749 | extracted.sort(); 750 | 751 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 752 | assert_eq!(expected, extracted); 753 | } 754 | 755 | #[cfg(feature = "rayon")] 756 | #[test] 757 | fn rayon_from_par_iter() { 758 | let map = CloneConMap::from_par_iter((0..TEST_BATCH_SMALL).into_par_iter().map(|i| (i, i))); 759 | let mut extracted = map 760 | .iter() 761 | .map(|n| { 762 | assert_eq!(n.0, n.1); 763 | n.1 764 | }) 765 | .collect::>(); 766 | extracted.sort(); 767 | 768 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 769 | assert_eq!(expected, extracted); 770 | } 771 | } 772 | -------------------------------------------------------------------------------- /src/existing_or_new.rs: -------------------------------------------------------------------------------- 1 | //! The [`ExistingOrNew`][crate::ExistingOrNew] enum. 2 | 3 | use std::ops::{Deref, DerefMut}; 4 | 5 | /// A simple enum to make a distinction if the returned value is an already existing one or a newly 6 | /// created instance. 7 | /// 8 | /// As it dereferences to the held value, it acts almost as the value `T` in many circumstances. 9 | /// Otherwise it can also be extracted. 10 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] 11 | pub enum ExistingOrNew { 12 | /// The entry already existed. 13 | Existing(T), 14 | /// A new entry had to be inserted to satisfy the request. 15 | New(T), 16 | } 17 | 18 | impl ExistingOrNew { 19 | /// Extracts the inner value. 20 | pub fn into_inner(self) -> T { 21 | match self { 22 | ExistingOrNew::Existing(get) => get, 23 | ExistingOrNew::New(insert) => insert, 24 | } 25 | } 26 | 27 | /// Applies a transformation to the value. 28 | /// 29 | /// This applies the function to the inner value, but preserves the information if it was a new 30 | /// or existing one. 31 | pub fn map U>(self, f: F) -> ExistingOrNew { 32 | match self { 33 | ExistingOrNew::Existing(get) => ExistingOrNew::Existing(f(get)), 34 | ExistingOrNew::New(insert) => ExistingOrNew::New(f(insert)), 35 | } 36 | } 37 | 38 | /// Checks if the value was newly created one. 39 | pub fn is_new(&self) -> bool { 40 | match self { 41 | ExistingOrNew::New(_) => true, 42 | ExistingOrNew::Existing(_) => false, 43 | } 44 | } 45 | } 46 | 47 | impl Deref for ExistingOrNew { 48 | type Target = T; 49 | fn deref(&self) -> &T { 50 | match self { 51 | ExistingOrNew::Existing(get) => get, 52 | ExistingOrNew::New(insert) => insert, 53 | } 54 | } 55 | } 56 | 57 | impl DerefMut for ExistingOrNew { 58 | fn deref_mut(&mut self) -> &mut T { 59 | match self { 60 | ExistingOrNew::Existing(get) => get, 61 | ExistingOrNew::New(insert) => insert, 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc( 2 | html_root_url = "https://docs.rs/contrie/0.1.4/contrie/", 3 | test(attr(deny(warnings))) 4 | )] 5 | // Note: we can't use forbid(unsafe_code). We do allow unsafe code in the raw submodule (but not 6 | // outside of it). 7 | #![deny(missing_docs, warnings, unsafe_code)] 8 | 9 | //! A concurrent trie. 10 | //! 11 | //! The crate provides implementation of a hash map and a hash set. Unlike the versions from the 12 | //! standard library, these allow concurrent accesses and *modifications* from multiple threads at 13 | //! once, without locking. Actually, even the inner implementation is [lock-free] and the lookups 14 | //! are [wait-free] (though even the lookup methods may invoke collecting garbage in 15 | //! [crossbeam-epoch] which while unlikely might contain non-wait-free functions). 16 | //! 17 | //! # Downsides 18 | //! 19 | //! The concurrent access does not come for free, if you can, you should prefer using the usual 20 | //! single-threaded alternatives or consider combining them with locks. In particular, there are 21 | //! several downsides. 22 | //! 23 | //! * Under the hood, the [crossbeam-epoch] is used to manage memory. This has the effect that 24 | //! removed elements are not deleted at precisely known moment. While the [crossbeam-epoch] is 25 | //! usually reasonably fast in collecting garbage, this might be unsuitable for object with 26 | //! observable side effects in their destructors (like, containing open files that need to be 27 | //! flushed and closed). 28 | //! * As even after removing an element this element might be still being accessed by another 29 | //! thread, there's no way to get an owned access to the original element once it is inserted. 30 | //! Depending on the flavour, the data structure either clones the data or returns [`Arc`]s to 31 | //! them. 32 | //! * They are slower in single-threaded usage and use more memory than their standard library 33 | //! counter-parts. While the mileage may differ, 2-3 times slowdown was measured in trivial 34 | //! benchmarks. 35 | //! * The order of modifications as seen by different threads may be different (if one thread 36 | //! inserts elements `a` and `b`, another thread may see `b` already inserted while `a` still not 37 | //! being present). In other words, the existence of values of different keys is considered 38 | //! independent on each other. This limitation might be lifted in the future by letting the user 39 | //! configure the desired sequentially consistent behaviour at the cost of further slowdown. 40 | //! * Iteration doesn't take a snapshot at a given time. In other words, if the data structure is 41 | //! modified during the iteration (even if by the same thread that iterates), the changes may or 42 | //! may not be reflected in the list of iterated elements. 43 | //! * Iteration pins an epoch for the whole time it iterates, possibly delaying releasing some 44 | //! memory. Therefore, it is advised not to hold onto iterators for extended periods of time. 45 | //! * Because the garbage collection of [crossbeam-epoch] can postpone destroying values for 46 | //! arbitrary time, the values and keys stored inside need to be owned (eg. `'static`). This 47 | //! limitation will likely be lifted eventually. 48 | //! 49 | //! # The gist of the data structure 50 | //! 51 | //! Internally, the data structure is an array-mapped trie. At each level there's an array of 16 52 | //! pointers. They get indexed by 4 bits of the hash of the key. The branches are kept as short as 53 | //! possible to still distinguish between different hashes (once a subtree contains only one value, 54 | //! it contains only the data leaf without further intermediate levels). These pointers can be 55 | //! updated atomically with compare-and-swap operations. 56 | //! 57 | //! The idea was inspired by this [article] and [wikipedia entry], but with severe simplifications. 58 | //! The simplifications were done to lower the number of traversed pointers during operations and 59 | //! to gain more confidence in correctness of the implementation, however at the cost of the 60 | //! snapshots for iteration and higher memory consumption. Pruning of the trie of unneeded branches 61 | //! on removals was preserved. 62 | //! 63 | //! For further technical details, arguments of correctness and similar, see the source code and 64 | //! comments in there, especially the [`raw`] module. 65 | //! 66 | //! # Examples 67 | //! 68 | //! ```rust 69 | //! use contrie::ConMap; 70 | //! use crossbeam_utils::thread; 71 | //! 72 | //! let map = ConMap::new(); 73 | //! 74 | //! thread::scope(|s| { 75 | //! s.spawn(|_| { 76 | //! map.insert(1, 2); 77 | //! }); 78 | //! s.spawn(|_| { 79 | //! map.insert(2, 3); 80 | //! }); 81 | //! }).unwrap(); 82 | //! 83 | //! assert_eq!(3, *map.get(&2).unwrap().value()); 84 | //! ``` 85 | //! 86 | //! # Features 87 | //! 88 | //! If compiled with the `rayon` feature, some parallel traits will be implemented for 89 | //! the types provided by this crate. 90 | //! 91 | //! [wait-free]: https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom 92 | //! [lock-free]: https://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom 93 | //! [crossbeam-epoch]: https://docs.rs/crossbeam-epoch 94 | //! [`Arc`]: std::sync::Arc 95 | //! [article]: https://www.researchgate.net/publication/221643801_Concurrent_Tries_with_Efficient_Non-Blocking_Snapshots 96 | //! [Wikipedia entry]: https://en.wikipedia.org/wiki/Ctrie 97 | 98 | pub mod clonemap; 99 | mod existing_or_new; 100 | pub mod map; 101 | pub mod raw; 102 | pub mod set; 103 | // Some integration-like tests live here, instead of crate/tests. This is because this allows cargo 104 | // to compile them in parallel with the crate and also run them more in parallel. And I like to get 105 | // all the test failures at once. 106 | // 107 | // Interface is tested through doctests anyway. 108 | #[cfg(test)] 109 | mod tests; 110 | 111 | pub use self::clonemap::CloneConMap; 112 | pub use self::existing_or_new::ExistingOrNew; 113 | pub use self::map::ConMap; 114 | pub use self::set::ConSet; 115 | -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | //! The [`ConMap`][crate::ConMap] type and its helpers. 2 | 3 | use std::borrow::Borrow; 4 | use std::collections::hash_map::RandomState; 5 | use std::fmt::{Debug, Formatter, Result as FmtResult}; 6 | use std::hash::{BuildHasher, Hash}; 7 | use std::iter::FromIterator; 8 | use std::marker::PhantomData; 9 | use std::sync::Arc; 10 | 11 | #[cfg(feature = "rayon")] 12 | use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelExtend, ParallelIterator}; 13 | 14 | use crate::existing_or_new::ExistingOrNew; 15 | use crate::raw::config::Config; 16 | use crate::raw::{self, Raw}; 17 | 18 | // :-( It would be nice if we could provide deref to (K, V). But that is incompatible with unsized 19 | // values. 20 | /// An element stored inside the [`ConMap`]. 21 | /// 22 | /// Or, more precisely, the [`Arc`] handles to these are stored in there. 23 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)] 24 | pub struct Element { 25 | key: K, 26 | value: V, 27 | } 28 | 29 | impl Element { 30 | /// Creates a new element with given key and value. 31 | pub fn new(key: K, value: V) -> Self { 32 | Self { key, value } 33 | } 34 | } 35 | 36 | impl Element { 37 | /// Provides access to the key. 38 | pub fn key(&self) -> &K { 39 | &self.key 40 | } 41 | 42 | /// Provides access to the value. 43 | pub fn value(&self) -> &V { 44 | &self.value 45 | } 46 | } 47 | 48 | struct MapPayload(Arc>); 49 | 50 | impl Clone for MapPayload { 51 | fn clone(&self) -> Self { 52 | MapPayload(Arc::clone(&self.0)) 53 | } 54 | } 55 | 56 | impl Borrow for MapPayload { 57 | fn borrow(&self) -> &K { 58 | self.0.key() 59 | } 60 | } 61 | 62 | struct MapConfig(PhantomData<(K, V)>); 63 | 64 | impl Config for MapConfig 65 | where 66 | V: ?Sized + 'static, 67 | K: Hash + Eq + 'static, 68 | { 69 | type Payload = MapPayload; 70 | type Key = K; 71 | } 72 | 73 | /// The iterator of the [`ConMap`]. 74 | /// 75 | /// See the [`iter`][ConMap::iter] method for details. 76 | pub struct Iter<'a, K, V, S> 77 | where 78 | // TODO: It would be great if the bounds wouldn't have to be on the struct, only on the impls 79 | K: Hash + Eq + 'static, 80 | V: ?Sized + 'static, 81 | { 82 | inner: raw::iterator::Iter<'a, MapConfig, S>, 83 | } 84 | 85 | impl<'a, K, V, S> Iterator for Iter<'a, K, V, S> 86 | where 87 | K: Hash + Eq + 'static, 88 | V: ?Sized + 'static, 89 | { 90 | type Item = Arc>; 91 | fn next(&mut self) -> Option>> { 92 | self.inner.next().map(|p| Arc::clone(&p.0)) 93 | } 94 | } 95 | 96 | // TODO: Bunch of derives? Which ones? And which one do we need to implement? 97 | /// A concurrent map. 98 | /// 99 | /// This flavour stores the data as [`Arc>`][Element]. This allows returning handles 100 | /// to the held values cheaply even if the data is larger or impossible to clone. This has several 101 | /// consequences: 102 | /// 103 | /// * It is sometimes less convenient to use. 104 | /// * It allows the values to be `?Sized` ‒ you can store trait objects or slices as the values 105 | /// (not the keys). 106 | /// * Entries can be shared between multiple maps. 107 | /// * Cloning of the map doesn't clone the data, it will point to the same objects. 108 | /// * There's another indirection in play. 109 | /// 110 | /// Iteration returns (cloned) handles to the elements. The [`FromIterator`] and [`Extend`] traits 111 | /// accept both tuples and element handles. Furthermore, the [`Extend`] is also implemented for 112 | /// shared references (to allow extending the same map concurrently from multiple threads). 113 | /// 114 | /// TODO: Support for rayon iterators/extend. 115 | /// 116 | /// If this is not suitable, the `CloneConMap` can be used instead (TODO: Implement it). 117 | /// 118 | /// # Examples 119 | /// 120 | /// ```rust 121 | /// use contrie::ConMap; 122 | /// use crossbeam_utils::thread; 123 | /// 124 | /// let map = ConMap::new(); 125 | /// 126 | /// thread::scope(|s| { 127 | /// s.spawn(|_| { 128 | /// map.insert("hello", 1); 129 | /// }); 130 | /// s.spawn(|_| { 131 | /// map.insert("world", 2); 132 | /// }); 133 | /// }).unwrap(); 134 | /// assert_eq!(1, *map.get("hello").unwrap().value()); 135 | /// assert_eq!(2, *map.get("world").unwrap().value()); 136 | /// ``` 137 | /// 138 | /// ```rust 139 | /// use std::sync::Arc; 140 | /// use contrie::map::{ConMap, Element}; 141 | /// let map_1: ConMap = ConMap::new(); 142 | /// 143 | /// map_1.insert_element(Arc::new(Element::new(42, [1, 2, 3]))); 144 | /// map_1.insert_element(Arc::new(Element::new(43, [1, 2, 3, 4]))); 145 | /// 146 | /// assert_eq!(3, map_1.get(&42).unwrap().value().len()); 147 | /// 148 | /// let map_2 = ConMap::new(); 149 | /// map_2.insert_element(map_1.get(&43).unwrap()); 150 | /// ``` 151 | pub struct ConMap 152 | where 153 | // TODO: It would be great if the bounds wouldn't have to be on the struct, only on the impls 154 | K: Hash + Eq + 'static, 155 | V: ?Sized + 'static, 156 | { 157 | raw: Raw, S>, 158 | } 159 | 160 | impl ConMap 161 | where 162 | K: Hash + Eq + 'static, 163 | V: ?Sized + 'static, 164 | { 165 | /// Creates a new empty map. 166 | pub fn new() -> Self { 167 | Self::with_hasher(RandomState::default()) 168 | } 169 | } 170 | 171 | // TODO: Once we have the unsized locals, this should be possible to move into the V: ?Sized block 172 | impl ConMap 173 | where 174 | K: Hash + Eq + 'static, 175 | V: 'static, 176 | S: BuildHasher, 177 | { 178 | /// Inserts a new element. 179 | /// 180 | /// Any previous element with the same key is replaced and returned. 181 | pub fn insert(&self, key: K, value: V) -> Option>> { 182 | self.insert_element(Arc::new(Element::new(key, value))) 183 | } 184 | 185 | /// Looks up or inserts an element. 186 | /// 187 | /// It looks up an element. If it isn't present, the provided one is inserted instead. Either 188 | /// way, an element is returned. 189 | pub fn get_or_insert(&self, key: K, value: V) -> ExistingOrNew>> { 190 | self.get_or_insert_with(key, || value) 191 | } 192 | 193 | /// Looks up or inserts a newly created element. 194 | /// 195 | /// It looks up an element. If it isn't present, the provided closure is used to create a new 196 | /// one insert it. Either way, an element is returned. 197 | /// 198 | /// # Quirks 199 | /// 200 | /// Due to races in case of concurrent accesses, the closure may be called even if the value is 201 | /// not subsequently inserted and an existing element is returned. This should be relatively 202 | /// rare (another thread must insert the new element between this method observes an empty slot 203 | /// and manages to insert the new element). 204 | pub fn get_or_insert_with(&self, key: K, create: F) -> ExistingOrNew>> 205 | where 206 | F: FnOnce() -> V, 207 | { 208 | self.get_or_insert_with_element(key, |key| { 209 | let value = create(); 210 | Arc::new(Element::new(key, value)) 211 | }) 212 | } 213 | 214 | /// Looks up or inserts a default value of an element. 215 | /// 216 | /// This is like [get_or_insert_with][ConMap::get_or_insert_with], but a default value is used 217 | /// instead of manually providing a closure. 218 | pub fn get_or_insert_default(&self, key: K) -> ExistingOrNew>> 219 | where 220 | V: Default, 221 | { 222 | self.get_or_insert_with(key, V::default) 223 | } 224 | } 225 | 226 | impl ConMap 227 | where 228 | K: Hash + Eq, 229 | V: ?Sized, 230 | S: BuildHasher, 231 | { 232 | /// Creates a new empty map, but with the provided hasher implementation. 233 | pub fn with_hasher(hasher: S) -> Self { 234 | Self { 235 | raw: Raw::with_hasher(hasher), 236 | } 237 | } 238 | 239 | /// Inserts a new element. 240 | /// 241 | /// This acts the same as [insert][ConMap::insert], but takes the already created element. It 242 | /// can be used when: 243 | /// 244 | /// * `V: ?Sized`. 245 | /// * You want to insert the same element into multiple maps. 246 | pub fn insert_element(&self, element: Arc>) -> Option>> { 247 | let pin = crossbeam_epoch::pin(); 248 | self.raw 249 | .insert(MapPayload(element), &pin) 250 | .map(|p| Arc::clone(&p.0)) 251 | } 252 | 253 | /// Looks up or inserts a new element. 254 | /// 255 | /// This is the same as [get_or_insert_with][ConMap::get_or_insert_with], but the closure 256 | /// returns a pre-created element. This can be used when: 257 | /// 258 | /// * `V: ?Sized`. 259 | /// * You want to insert the same element into multiple maps. 260 | pub fn get_or_insert_with_element( 261 | &self, 262 | key: &Q, 263 | create: F, 264 | ) -> ExistingOrNew>> 265 | where 266 | K: Borrow, 267 | F: FnOnce(&Q) -> Arc>, 268 | { 269 | let pin = crossbeam_epoch::pin(); 270 | self.raw 271 | .get_or_insert_with(key, |key| MapPayload(create(key)), &pin) 272 | .map(|payload| Arc::clone(&payload.0)) 273 | } 274 | 275 | /// Looks up an element. 276 | pub fn get(&self, key: &Q) -> Option>> 277 | where 278 | Q: ?Sized + Eq + Hash, 279 | K: Borrow, 280 | { 281 | let pin = crossbeam_epoch::pin(); 282 | self.raw.get(key, &pin).map(|r| Arc::clone(&r.0)) 283 | } 284 | 285 | /// Removes an element identified by the given key, returning it. 286 | pub fn remove(&self, key: &Q) -> Option>> 287 | where 288 | Q: ?Sized + Eq + Hash, 289 | K: Borrow, 290 | { 291 | let pin = crossbeam_epoch::pin(); 292 | self.raw.remove(key, &pin).map(|r| Arc::clone(&r.0)) 293 | } 294 | } 295 | 296 | impl ConMap 297 | where 298 | K: Hash + Eq, 299 | V: ?Sized, 300 | { 301 | /// Checks if the map is currently empty. 302 | /// 303 | /// Note that due to the nature of concurrent map, this is inherently racy ‒ another thread may 304 | /// add or remove elements between you call this method and act based on the result. 305 | pub fn is_empty(&self) -> bool { 306 | self.raw.is_empty() 307 | } 308 | 309 | /// Returns an iterator through the elements of the map. 310 | pub fn iter(&self) -> Iter { 311 | Iter { 312 | inner: raw::iterator::Iter::new(&self.raw), 313 | } 314 | } 315 | } 316 | 317 | impl Default for ConMap 318 | where 319 | K: Hash + Eq, 320 | V: ?Sized, 321 | { 322 | fn default() -> Self { 323 | Self::new() 324 | } 325 | } 326 | 327 | impl Debug for ConMap 328 | where 329 | K: Debug + Hash + Eq, 330 | V: Debug + ?Sized, 331 | { 332 | fn fmt(&self, fmt: &mut Formatter) -> FmtResult { 333 | let mut d = fmt.debug_map(); 334 | // TODO: As we return Arcs, it seem we can't use the iterator approach with .map :-( 335 | // This might hint to need for better iteration API? 336 | for n in self { 337 | // Hack: As of 1.37.0 the parameters to entry need to be &Sized. By using a double-ref, 338 | // we satisfy the compiler's pickiness in that regard. 339 | let val: &&V = &n.value(); 340 | d.entry(n.key() as &dyn Debug, val); 341 | } 342 | d.finish() 343 | } 344 | } 345 | 346 | impl Clone for ConMap 347 | where 348 | K: Hash + Eq, 349 | V: ?Sized, 350 | S: Clone + BuildHasher, 351 | { 352 | fn clone(&self) -> Self { 353 | let builder = self.raw.hash_builder().clone(); 354 | let mut new = Self::with_hasher(builder); 355 | new.extend(self); 356 | new 357 | } 358 | } 359 | 360 | impl<'a, K, V, S> IntoIterator for &'a ConMap 361 | where 362 | K: Hash + Eq, 363 | V: ?Sized, 364 | { 365 | type Item = Arc>; 366 | type IntoIter = Iter<'a, K, V, S>; 367 | fn into_iter(self) -> Self::IntoIter { 368 | self.iter() 369 | } 370 | } 371 | 372 | impl<'a, K, V, S> Extend>> for &'a ConMap 373 | where 374 | K: Hash + Eq, 375 | V: ?Sized, 376 | S: BuildHasher, 377 | { 378 | fn extend(&mut self, iter: T) 379 | where 380 | T: IntoIterator>>, 381 | { 382 | for n in iter { 383 | self.insert_element(n); 384 | } 385 | } 386 | } 387 | 388 | impl<'a, K, V, S> Extend<(K, V)> for &'a ConMap 389 | where 390 | K: Hash + Eq, 391 | S: BuildHasher, 392 | { 393 | fn extend(&mut self, iter: T) 394 | where 395 | T: IntoIterator, 396 | { 397 | self.extend(iter.into_iter().map(|(k, v)| Arc::new(Element::new(k, v)))); 398 | } 399 | } 400 | 401 | impl Extend>> for ConMap 402 | where 403 | K: Hash + Eq, 404 | V: ?Sized, 405 | S: BuildHasher, 406 | { 407 | fn extend(&mut self, iter: T) 408 | where 409 | T: IntoIterator>>, 410 | { 411 | let mut me: &ConMap<_, _, _> = self; 412 | me.extend(iter); 413 | } 414 | } 415 | 416 | impl Extend<(K, V)> for ConMap 417 | where 418 | K: Hash + Eq, 419 | S: BuildHasher, 420 | { 421 | fn extend(&mut self, iter: T) 422 | where 423 | T: IntoIterator, 424 | { 425 | let mut me: &ConMap<_, _, _> = self; 426 | me.extend(iter); 427 | } 428 | } 429 | 430 | impl FromIterator>> for ConMap 431 | where 432 | K: Hash + Eq, 433 | V: ?Sized, 434 | { 435 | fn from_iter(iter: T) -> Self 436 | where 437 | T: IntoIterator>>, 438 | { 439 | let mut me = ConMap::new(); 440 | me.extend(iter); 441 | me 442 | } 443 | } 444 | 445 | impl FromIterator<(K, V)> for ConMap 446 | where 447 | K: Hash + Eq, 448 | { 449 | fn from_iter(iter: T) -> Self 450 | where 451 | T: IntoIterator, 452 | { 453 | let mut me = ConMap::new(); 454 | me.extend(iter); 455 | me 456 | } 457 | } 458 | 459 | #[cfg(feature = "rayon")] 460 | impl<'a, K, V, S> ParallelExtend>> for &'a ConMap 461 | where 462 | K: Hash + Eq + Send + Sync, 463 | V: ?Sized + Send + Sync, 464 | S: BuildHasher + Sync, 465 | { 466 | fn par_extend(&mut self, par_iter: T) 467 | where 468 | T: IntoParallelIterator>>, 469 | { 470 | par_iter.into_par_iter().for_each(|n| { 471 | self.insert_element(n); 472 | }); 473 | } 474 | } 475 | 476 | #[cfg(feature = "rayon")] 477 | impl ParallelExtend<(K, V)> for ConMap 478 | where 479 | K: Hash + Eq + Send + Sync, 480 | S: BuildHasher + Sync, 481 | V: Send + Sync, 482 | { 483 | fn par_extend(&mut self, par_iter: T) 484 | where 485 | T: IntoParallelIterator, 486 | { 487 | self.par_extend( 488 | par_iter 489 | .into_par_iter() 490 | .map(|(k, v)| Arc::new(Element::new(k, v))), 491 | ); 492 | } 493 | } 494 | 495 | #[cfg(feature = "rayon")] 496 | impl ParallelExtend>> for ConMap 497 | where 498 | K: Hash + Eq + Send + Sync, 499 | V: ?Sized + Send + Sync, 500 | S: BuildHasher + Sync, 501 | { 502 | fn par_extend(&mut self, par_iter: T) 503 | where 504 | T: IntoParallelIterator>>, 505 | { 506 | let mut me: &ConMap<_, _, _> = self; 507 | me.par_extend(par_iter); 508 | } 509 | } 510 | 511 | #[cfg(feature = "rayon")] 512 | impl<'a, K, V, S> ParallelExtend<(K, V)> for &'a ConMap 513 | where 514 | K: Hash + Eq + Send + Sync, 515 | S: BuildHasher + Sync, 516 | V: Send + Sync, 517 | { 518 | fn par_extend(&mut self, par_iter: T) 519 | where 520 | T: IntoParallelIterator, 521 | { 522 | self.par_extend( 523 | par_iter 524 | .into_par_iter() 525 | .map(|(k, v)| Arc::new(Element::new(k, v))), 526 | ); 527 | } 528 | } 529 | 530 | #[cfg(feature = "rayon")] 531 | impl FromParallelIterator>> for ConMap 532 | where 533 | K: Hash + Eq + Send + Sync, 534 | V: ?Sized + Send + Sync, 535 | { 536 | fn from_par_iter(par_iter: T) -> Self 537 | where 538 | T: IntoParallelIterator>>, 539 | { 540 | let mut me = ConMap::new(); 541 | me.par_extend(par_iter); 542 | me 543 | } 544 | } 545 | 546 | #[cfg(feature = "rayon")] 547 | impl FromParallelIterator<(K, V)> for ConMap 548 | where 549 | K: Hash + Eq + Send + Sync, 550 | V: Send + Sync, 551 | { 552 | fn from_par_iter(par_iter: T) -> Self 553 | where 554 | T: IntoParallelIterator, 555 | { 556 | let mut me = ConMap::new(); 557 | me.par_extend(par_iter); 558 | me 559 | } 560 | } 561 | 562 | #[cfg(test)] 563 | mod tests { 564 | use crossbeam_utils::thread; 565 | 566 | #[cfg(feature = "rayon")] 567 | use rayon::prelude::*; 568 | 569 | use super::*; 570 | use crate::raw::tests::NoHasher; 571 | use crate::raw::LEVEL_CELLS; 572 | 573 | const TEST_THREADS: usize = 4; 574 | const TEST_BATCH: usize = 10000; 575 | const TEST_BATCH_SMALL: usize = 100; 576 | const TEST_REP: usize = 20; 577 | 578 | #[test] 579 | fn create_destroy() { 580 | let map: ConMap = ConMap::new(); 581 | drop(map); 582 | } 583 | 584 | #[test] 585 | fn lookup_empty() { 586 | let map: ConMap = ConMap::new(); 587 | assert!(map.get("hello").is_none()); 588 | } 589 | 590 | #[test] 591 | fn insert_lookup() { 592 | let map = ConMap::new(); 593 | assert!(map.insert("hello", "world").is_none()); 594 | assert!(map.get("world").is_none()); 595 | let found = map.get("hello").unwrap(); 596 | assert_eq!(Element::new("hello", "world"), *found); 597 | } 598 | 599 | #[test] 600 | fn insert_overwrite_lookup() { 601 | let map = ConMap::new(); 602 | assert!(map.insert("hello", "world").is_none()); 603 | let old = map.insert("hello", "universe").unwrap(); 604 | assert_eq!(Element::new("hello", "world"), *old); 605 | let found = map.get("hello").unwrap(); 606 | assert_eq!(Element::new("hello", "universe"), *found); 607 | } 608 | 609 | // Insert a lot of things, to make sure we have multiple levels. 610 | #[test] 611 | fn insert_many() { 612 | let map = ConMap::new(); 613 | for i in 0..TEST_BATCH * LEVEL_CELLS { 614 | assert!(map.insert(i, i).is_none()); 615 | } 616 | 617 | for i in 0..TEST_BATCH * LEVEL_CELLS { 618 | assert_eq!(i, *map.get(&i).unwrap().value()); 619 | } 620 | } 621 | 622 | #[test] 623 | fn par_insert_many() { 624 | for _ in 0..TEST_REP { 625 | let map: ConMap = ConMap::new(); 626 | thread::scope(|s| { 627 | for t in 0..TEST_THREADS { 628 | let map = ↦ 629 | s.spawn(move |_| { 630 | for i in 0..TEST_BATCH { 631 | let num = t * TEST_BATCH + i; 632 | assert!(map.insert(num, num).is_none()); 633 | } 634 | }); 635 | } 636 | }) 637 | .unwrap(); 638 | 639 | for i in 0..TEST_BATCH * TEST_THREADS { 640 | assert_eq!(*map.get(&i).unwrap().value(), i); 641 | } 642 | } 643 | } 644 | 645 | #[test] 646 | fn par_get_many() { 647 | for _ in 0..TEST_REP { 648 | let map = ConMap::new(); 649 | for i in 0..TEST_BATCH * TEST_THREADS { 650 | assert!(map.insert(i, i).is_none()); 651 | } 652 | thread::scope(|s| { 653 | for t in 0..TEST_THREADS { 654 | let map = ↦ 655 | s.spawn(move |_| { 656 | for i in 0..TEST_BATCH { 657 | let num = t * TEST_BATCH + i; 658 | assert_eq!(*map.get(&num).unwrap().value(), num); 659 | } 660 | }); 661 | } 662 | }) 663 | .unwrap(); 664 | } 665 | } 666 | 667 | #[test] 668 | fn collisions() { 669 | let map = ConMap::with_hasher(NoHasher); 670 | // While their hash is the same under the hasher, they don't kick each other out. 671 | for i in 0..TEST_BATCH_SMALL { 672 | assert!(map.insert(i, i).is_none()); 673 | } 674 | // And all are present. 675 | for i in 0..TEST_BATCH_SMALL { 676 | assert_eq!(i, *map.get(&i).unwrap().value()); 677 | } 678 | // But reusing the key kicks the other one out. 679 | for i in 0..TEST_BATCH_SMALL { 680 | assert_eq!(i, *map.insert(i, i + 1).unwrap().value()); 681 | assert_eq!(i + 1, *map.get(&i).unwrap().value()); 682 | } 683 | } 684 | 685 | #[test] 686 | fn get_or_insert_empty() { 687 | let map = ConMap::new(); 688 | let val = map.get_or_insert("hello", 42); 689 | assert_eq!(42, *val.value()); 690 | assert_eq!("hello", *val.key()); 691 | assert!(val.is_new()); 692 | } 693 | 694 | #[test] 695 | fn get_or_insert_existing() { 696 | let map = ConMap::new(); 697 | assert!(map.insert("hello", 42).is_none()); 698 | let val = map.get_or_insert("hello", 0); 699 | // We still have the original 700 | assert_eq!(42, *val.value()); 701 | assert_eq!("hello", *val.key()); 702 | assert!(!val.is_new()); 703 | } 704 | 705 | fn get_or_insert_many_inner(map: ConMap, len: usize) { 706 | for i in 0..len { 707 | let val = map.get_or_insert(i, i); 708 | assert_eq!(i, *val.key()); 709 | assert_eq!(i, *val.value()); 710 | assert!(val.is_new()); 711 | } 712 | 713 | for i in 0..len { 714 | let val = map.get_or_insert(i, 0); 715 | assert_eq!(i, *val.key()); 716 | assert_eq!(i, *val.value()); 717 | assert!(!val.is_new()); 718 | } 719 | } 720 | 721 | #[test] 722 | fn get_or_insert_many() { 723 | get_or_insert_many_inner(ConMap::new(), TEST_BATCH); 724 | } 725 | 726 | #[test] 727 | fn get_or_insert_collision() { 728 | get_or_insert_many_inner(ConMap::with_hasher(NoHasher), TEST_BATCH_SMALL); 729 | } 730 | 731 | #[test] 732 | fn simple_remove() { 733 | let map = ConMap::new(); 734 | assert!(map.remove(&42).is_none()); 735 | assert!(map.insert(42, "hello").is_none()); 736 | assert_eq!("hello", *map.get(&42).unwrap().value()); 737 | assert_eq!("hello", *map.remove(&42).unwrap().value()); 738 | assert!(map.get(&42).is_none()); 739 | assert!(map.is_empty()); 740 | assert!(map.remove(&42).is_none()); 741 | assert!(map.is_empty()); 742 | } 743 | 744 | fn remove_many_inner(mut map: ConMap, len: usize) { 745 | for i in 0..len { 746 | assert!(map.insert(i, i).is_none()); 747 | } 748 | for i in 0..len { 749 | assert_eq!(i, *map.get(&i).unwrap().value()); 750 | assert_eq!(i, *map.remove(&i).unwrap().value()); 751 | assert!(map.get(&i).is_none()); 752 | map.raw.assert_pruned(); 753 | } 754 | 755 | assert!(map.is_empty()); 756 | } 757 | 758 | #[test] 759 | fn remove_many() { 760 | remove_many_inner(ConMap::new(), TEST_BATCH); 761 | } 762 | 763 | #[test] 764 | fn remove_many_collision() { 765 | remove_many_inner(ConMap::with_hasher(NoHasher), TEST_BATCH_SMALL); 766 | } 767 | 768 | #[test] 769 | fn collision_remove_one_left() { 770 | let mut map = ConMap::with_hasher(NoHasher); 771 | map.insert(1, 1); 772 | map.insert(2, 2); 773 | 774 | map.raw.assert_pruned(); 775 | 776 | assert!(map.remove(&2).is_some()); 777 | map.raw.assert_pruned(); 778 | 779 | assert!(map.remove(&1).is_some()); 780 | 781 | map.raw.assert_pruned(); 782 | assert!(map.is_empty()); 783 | } 784 | 785 | #[test] 786 | fn remove_par() { 787 | let mut map = ConMap::new(); 788 | for i in 0..TEST_THREADS * TEST_BATCH { 789 | map.insert(i, i); 790 | } 791 | 792 | thread::scope(|s| { 793 | for t in 0..TEST_THREADS { 794 | let map = ↦ 795 | s.spawn(move |_| { 796 | for i in 0..TEST_BATCH { 797 | let num = t * TEST_BATCH + i; 798 | let val = map.remove(&num).unwrap(); 799 | assert_eq!(num, *val.value()); 800 | assert_eq!(num, *val.key()); 801 | } 802 | }); 803 | } 804 | }) 805 | .unwrap(); 806 | 807 | map.raw.assert_pruned(); 808 | assert!(map.is_empty()); 809 | } 810 | 811 | #[test] 812 | fn unsized_values() { 813 | let map: ConMap = ConMap::new(); 814 | assert!(map 815 | .insert_element(Arc::new(Element::new(42, [1, 2, 3]))) 816 | .is_none()); 817 | let found = map.get(&42).unwrap(); 818 | assert_eq!(&[1, 2, 3], found.value()); 819 | let inserted = map.get_or_insert_with_element(0, |k| { 820 | assert_eq!(0, k); 821 | Arc::new(Element::new(k, [])) 822 | }); 823 | assert_eq!(0, *inserted.key()); 824 | assert!(inserted.value().is_empty()); 825 | assert!(inserted.is_new()); 826 | let removed = map.remove(&0).unwrap(); 827 | assert_eq!(inserted.into_inner(), removed); 828 | } 829 | 830 | fn iter_test_inner(map: ConMap) { 831 | for i in 0..TEST_BATCH_SMALL { 832 | assert!(map.insert(i, i).is_none()); 833 | } 834 | 835 | let mut extracted = map.iter().map(|v| *v.value()).collect::>(); 836 | extracted.sort(); 837 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 838 | assert_eq!(expected, extracted); 839 | } 840 | 841 | #[test] 842 | fn iter() { 843 | let map = ConMap::new(); 844 | iter_test_inner(map); 845 | } 846 | 847 | #[test] 848 | fn iter_collision() { 849 | let map = ConMap::with_hasher(NoHasher); 850 | iter_test_inner(map); 851 | } 852 | 853 | #[test] 854 | fn collect() { 855 | let map = (0..TEST_BATCH_SMALL) 856 | .map(|i| (i, i)) 857 | .collect::>(); 858 | 859 | let mut extracted = map 860 | .iter() 861 | .map(|n| { 862 | assert_eq!(n.key(), n.value()); 863 | *n.value() 864 | }) 865 | .collect::>(); 866 | 867 | extracted.sort(); 868 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 869 | assert_eq!(expected, extracted); 870 | } 871 | 872 | #[test] 873 | fn par_extend() { 874 | let map = ConMap::new(); 875 | thread::scope(|s| { 876 | for t in 0..TEST_THREADS { 877 | let mut map = ↦ 878 | s.spawn(move |_| { 879 | let start = t * TEST_BATCH_SMALL; 880 | let iter = (start..start + TEST_BATCH_SMALL).map(|i| (i, i)); 881 | map.extend(iter); 882 | }); 883 | } 884 | }) 885 | .unwrap(); 886 | 887 | let mut extracted = map 888 | .iter() 889 | .map(|n| { 890 | assert_eq!(n.key(), n.value()); 891 | *n.value() 892 | }) 893 | .collect::>(); 894 | 895 | extracted.sort(); 896 | let expected = (0..TEST_THREADS * TEST_BATCH_SMALL).collect::>(); 897 | assert_eq!(expected, extracted); 898 | } 899 | 900 | #[cfg(feature = "rayon")] 901 | #[test] 902 | fn rayon_extend() { 903 | let mut map = ConMap::new(); 904 | map.par_extend((0..TEST_BATCH_SMALL).into_par_iter().map(|i| (i, i))); 905 | 906 | let mut extracted = map 907 | .iter() 908 | .map(|n| { 909 | assert_eq!(n.key(), n.value()); 910 | *n.value() 911 | }) 912 | .collect::>(); 913 | extracted.sort(); 914 | 915 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 916 | assert_eq!(expected, extracted); 917 | } 918 | 919 | #[cfg(feature = "rayon")] 920 | #[test] 921 | fn rayon_from_par_iter() { 922 | let map = ConMap::from_par_iter((0..TEST_BATCH_SMALL).into_par_iter().map(|i| (i, i))); 923 | let mut extracted = map 924 | .iter() 925 | .map(|n| { 926 | assert_eq!(n.key(), n.value()); 927 | *n.value() 928 | }) 929 | .collect::>(); 930 | extracted.sort(); 931 | 932 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 933 | assert_eq!(expected, extracted); 934 | } 935 | } 936 | -------------------------------------------------------------------------------- /src/raw/config.rs: -------------------------------------------------------------------------------- 1 | //! The [`Config`][crate::raw::config::Config] trait for specifying behaviour of 2 | //! [`Raw`][crate::raw::Raw]. 3 | use std::borrow::Borrow; 4 | use std::hash::Hash; 5 | use std::marker::PhantomData; 6 | 7 | // TODO: Allow our own hash, returning something else than just u64. Then the constants go here 8 | // too. 9 | // TODO: Should Hasher go here too? 10 | // TODO: Can we get rid of that Clone here? It is currently needed in the collision handling. 11 | /// Customization of the [`Raw`][crate::raw::Raw]. 12 | /// 13 | /// This specifies how the trie should act. Maybe some more customization will be possible in the 14 | /// future, but for now this allows tweaking what in how is stored. 15 | pub trait Config { 16 | /// The payload (eg. values) stored inside the trie. 17 | type Payload: Clone + Borrow + 'static; 18 | 19 | /// Each payload must contain a key as its part. This is the type for the key, which is used 20 | /// for hashing and identification of values in the tree. 21 | type Key: Hash + Eq; 22 | } 23 | 24 | /// A trivial config, where the payload and the key are the same thing. 25 | pub struct Trivial(PhantomData); 26 | 27 | impl Config for Trivial 28 | where 29 | T: Clone + Hash + Eq + 'static, 30 | { 31 | type Payload = T; 32 | type Key = T; 33 | } 34 | -------------------------------------------------------------------------------- /src/raw/debug.rs: -------------------------------------------------------------------------------- 1 | //! A module containing few debug utilities. 2 | //! 3 | //! In general, they are meant for debugging the *trie itself*, but it is exposed as potentially 4 | //! useful. 5 | 6 | use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; 7 | use std::sync::atomic::Ordering; 8 | 9 | use crossbeam_epoch::{self, Atomic, Guard}; 10 | 11 | use super::config::Config; 12 | use super::{load_data, nf, Inner, NodeFlags, Raw}; 13 | 14 | impl Raw 15 | where 16 | C: Config, 17 | { 18 | // Hack: &mut to make sure it is not shared between threads and nobody is modifying the thing 19 | // right now. 20 | /// Panics if the trie is not in consistent state and pruned well. 21 | /// 22 | /// Note that if the caller can get the mutable reference, it should be in pruned state, even 23 | /// though during modifications there might be temporary states which are not pruned. Due to 24 | /// unique access to it, other threads might not be modifying it at the moment. 25 | #[cfg(test)] 26 | pub(crate) fn assert_pruned(&mut self) { 27 | fn handle_ptr(ptr: &Atomic, data_cnt: &mut usize, seen_inner: &mut bool) { 28 | // Unprotected is fine, we are &mut so nobody else is allowed to do stuff to us at the 29 | // moment. 30 | let pin = unsafe { crossbeam_epoch::unprotected() }; 31 | // Relaxed is fine for the same reason ‒ we are &mut 32 | let sub = ptr.load(Ordering::Relaxed, &pin); 33 | let flags = nf(sub); 34 | 35 | assert!(!flags.contains(NodeFlags::CONDEMNED)); 36 | 37 | if sub.is_null() { 38 | // Do nothing here 39 | } else if flags.contains(NodeFlags::DATA) { 40 | let data = unsafe { load_data::(sub) }; 41 | assert!(!data.is_empty(), "Empty data nodes should not exist"); 42 | *data_cnt += data.len(); 43 | } else { 44 | let sub = unsafe { sub.deref() }; 45 | *seen_inner = true; 46 | check_node::(sub); 47 | } 48 | } 49 | fn check_node(node: &Inner) { 50 | let mut data_cnt = 0; 51 | let mut seen_inner = false; 52 | for ptr in &node.0 { 53 | handle_ptr::(ptr, &mut data_cnt, &mut seen_inner); 54 | } 55 | 56 | assert!( 57 | data_cnt > 1 || seen_inner, 58 | "This node should have been pruned" 59 | ); 60 | } 61 | 62 | handle_ptr::(&self.root, &mut 0, &mut false); 63 | } 64 | 65 | fn print_shape_ptr(ptr: &Atomic, fmt: &mut Formatter, pin: &Guard) -> FmtResult 66 | where 67 | C::Payload: Debug, 68 | { 69 | let ptr = ptr.load(Ordering::Acquire, pin); 70 | let flags = nf(ptr); 71 | write!(fmt, "{:?}/{:?}", ptr.as_raw(), flags)?; 72 | 73 | if ptr.is_null() { 74 | // Nothing 75 | } else if flags.contains(NodeFlags::DATA) { 76 | let data = unsafe { load_data::(ptr) }; 77 | write!(fmt, "{:?}", data)?; 78 | } else { 79 | let inner = unsafe { ptr.deref() }; 80 | write!(fmt, "(")?; 81 | for (idx, sub) in inner.0.iter().enumerate() { 82 | write!(fmt, " {:X}:", idx)?; 83 | Self::print_shape_ptr(sub, fmt, pin)?; 84 | } 85 | write!(fmt, " )")?; 86 | } 87 | Ok(()) 88 | } 89 | 90 | fn print_shape(&self, fmt: &mut Formatter) -> FmtResult 91 | where 92 | C::Payload: Debug, 93 | { 94 | let pin = crossbeam_epoch::pin(); 95 | Self::print_shape_ptr(&self.root, fmt, &pin) 96 | } 97 | } 98 | 99 | /// A pretty-printing wrapper around the raw trie. 100 | /// 101 | /// The structure, including the pointers and flags, is printed if this is used to wrap the raw 102 | /// trie. 103 | pub struct PrintShape<'a, C, S>(pub &'a Raw) 104 | where 105 | C: Config; 106 | 107 | impl Display for PrintShape<'_, C, S> 108 | where 109 | C: Config, 110 | C::Payload: Debug, 111 | { 112 | fn fmt(&self, fmt: &mut Formatter) -> FmtResult { 113 | self.0.print_shape(fmt) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/raw/iterator.rs: -------------------------------------------------------------------------------- 1 | //! Iteration of the [`Raw`][crate::raw::Raw] map. 2 | 3 | use std::marker::PhantomData; 4 | use std::mem; 5 | use std::sync::atomic::Ordering; 6 | 7 | use arrayvec::ArrayVec; 8 | use crossbeam_epoch::{Guard, Shared}; 9 | 10 | use super::config::Config; 11 | use super::{load_data, nf, Inner, NodeFlags, Raw, LEVEL_CELLS, MAX_LEVELS}; 12 | 13 | unsafe fn extend_lifetime<'a, 'b, T: 'a + 'b>(s: Shared<'a, T>) -> Shared<'b, T> { 14 | mem::transmute(s) 15 | } 16 | 17 | struct Level<'a> { 18 | ptr: Shared<'a, Inner>, 19 | idx: usize, 20 | } 21 | 22 | // Notes about the lifetimes: 23 | // The 'a here is actually a lie. We need two things from lifetimes: 24 | // * We must not outlive the map we are iterating through (because the drop just outright destroys 25 | // the data). 26 | // * The pointers must not outlive the pin we hold. 27 | // * We do not mind us (or the pin) moving around in memory, we are only interested in when its 28 | // destructor is called. The references don't actually point inside the pin itself. 29 | // 30 | // The lifetime of the pin is the same as of the pointers we store inside of us. We check the 31 | // lifetime relation of the map and us on the constructor, so we won't outlive the map. But 32 | // technically, the lifetime should be something like `'self`, but it's not possible to describe. 33 | // 34 | // Therefore we have to make very sure to never return a reference with the 'a lifetime. 35 | // 36 | // For the same technical reasons, we do the extend_lifetime thing. It would be great if someone 37 | // knew a better trick ‒ while this is probably correct, something the compiler could check would 38 | // be much better. 39 | 40 | /// An iterator-like structure for the raw trie. 41 | /// 42 | /// This wraps the map and provides borrowed instances of the payloads. Note that due to the 43 | /// borrowing from the iterator itself, it is not possible to create the true `Iterator`. As this 44 | /// is used to implement the iterators of the wrapper convenience types, this is not considered a 45 | /// serious limitation. 46 | /// 47 | /// # Quirks 48 | /// 49 | /// As noted in the crate-level documentation, changes to the content of the map done during the 50 | /// lifetime of the iterator (both in the current thread and other threads) may or may not be 51 | /// reflected in the returned values. 52 | pub struct Iter<'a, C, S> 53 | where 54 | C: Config, 55 | { 56 | pin: Guard, 57 | levels: ArrayVec<[Level<'a>; MAX_LEVELS + 1]>, 58 | _map: PhantomData<&'a Raw>, 59 | } 60 | 61 | impl<'a, C, S> Iter<'a, C, S> 62 | where 63 | C: Config, 64 | { 65 | /// Creates a new iterator, borrowing from the map. 66 | pub fn new<'m: 'a>(map: &'m Raw) -> Self { 67 | let mut levels = ArrayVec::new(); 68 | let pin = crossbeam_epoch::pin(); 69 | let ptr = map.root.load(Ordering::Acquire, &pin); 70 | let ptr = unsafe { extend_lifetime(ptr) }; 71 | levels.push(Level { ptr, idx: 0 }); 72 | Iter { 73 | pin, 74 | levels, 75 | _map: PhantomData, 76 | } 77 | } 78 | 79 | // Not an iterator because this borrows out of the iterator itself (and effectively its pin). 80 | /// Produces another value, just like `Iterator::next`, except the value is bound to the 81 | /// lifetime of the iterator structure. 82 | #[allow(clippy::should_implement_trait)] 83 | pub fn next(&mut self) -> Option<&C::Payload> { 84 | loop { 85 | let top = self.levels.last_mut()?; 86 | 87 | let flags = nf(top.ptr); 88 | if top.ptr.is_null() { 89 | self.levels.pop(); 90 | } else if flags.contains(NodeFlags::DATA) { 91 | let data = unsafe { load_data::(top.ptr) }; 92 | if top.idx < data.len() { 93 | let result = &data[top.idx]; 94 | top.idx += 1; 95 | return Some(result); 96 | } else { 97 | self.levels.pop(); 98 | } 99 | } else if top.idx < LEVEL_CELLS { 100 | let node = unsafe { top.ptr.deref() }; 101 | let ptr = node.0[top.idx].load(Ordering::Acquire, &self.pin); 102 | let ptr = unsafe { extend_lifetime(ptr) }; 103 | top.idx += 1; 104 | self.levels.push(Level { ptr, idx: 0 }); 105 | } else { 106 | self.levels.pop(); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/raw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_code)] 2 | //! The core implementation of the concurrent trie data structure. 3 | //! 4 | //! This module contains the [`Raw`][crate::raw::Raw] type, which is the engine of all the data 5 | //! structures in this crate. This is exposed to allow wrapping it into further APIs, but is 6 | //! probably not the best thing for general use. 7 | 8 | // # The data structure 9 | // 10 | // The data structure is inspired by the [article] and [Wikipedia entry], however severely 11 | // simplified. Compared to the article, what we don't do (if you don't want to read the article, 12 | // that's fine, explanation is below): 13 | // 14 | // * We don't have variable-sized inner nodes. This wastes some more space, but also allows us to 15 | // keep the same node around instead of creating a new one every time we want to add or remove a 16 | // pointer. 17 | // * We don't do snapshots for iterations. 18 | // * We got rid of the I-nodes. This gets rid of half of the pointer loads on the way to the 19 | // element, so in theory it should make the data structure about twice faster. 20 | // 21 | // By this simplification, we lose the ability of having consistent iterations and we use somewhat 22 | // more memory, but get faster data structure. More importantly, the data structure is simpler to 23 | // implement and simpler to prove correct which makes it more likely to actually trust it with some 24 | // data. 25 | // 26 | // ## How it works 27 | // 28 | // The heart of the data structure is a trie where keys are prefixes of the 64bit hash of the key. 29 | // Each inner node has 16 pointer slots, indexed by the next 4 bits of the hash. When we reach a 30 | // level where the prefix is unique, we stop (we don't have all 16 levels of inner nodes if we 31 | // don't have to) and place a data node. 32 | // 33 | // A data nodes contain one or more elements (more in case we get a hash collision on the whole 34 | // hash ‒ in that case, we store all the colliding elements in an array and distinguish by equality 35 | // of the keys in a linear search through the array). 36 | // 37 | // On lookup, we either find the correct element or stop at the first null pointer encountered. 38 | // 39 | // On insertion, if we find a null pointer, we atomically replace that pointer to a new data node 40 | // containing (only) the new element, using the CaS operation. In case we reach a collision or 41 | // replace an existing element, we create a new data node and replace the pointer, again using the 42 | // CaS operation. If we find a non-matching data node in our way, we need to insert another level ‒ 43 | // we create a brand new inner node, link the old node there and again, replace the pointer (then 44 | // retry with our insertion on the next level). 45 | // 46 | // Deletion looks up the element and either replaces the pointer to the data node with null (if it 47 | // was the last one), or creates a new data node without the element. 48 | // 49 | // ## Pruning 50 | // 51 | // If implemented as above, the data structure would work. However, deletions would leave unneeded 52 | // dead branches or branches that don't branch (eg. linear ones) behind. That would make the data 53 | // structure perform worse than necessary, because the lookups would have to traverse the dead or 54 | // non-branching branches and it would need more memory. Therefore, after we remove an element, we 55 | // want to walk the path back towards the root and remove the nodes that are no longer needed 56 | // (either remove the branch completely or contract the end that doesn't branch). 57 | // 58 | // If we, however, started to simply delete the nodes and replace the pointers with nulls, we would 59 | // get race conditions: 60 | // 61 | // 1. We check that the current node is empty (or has only one pointer to data in it) and therefore 62 | // is unneeded. 63 | // 2. After we do the check but before we manage to update the pointer that points to the current 64 | // node, some other thread adds a new pointer into the node. This would make it ineligible for 65 | // deletion, but we've already done our check. 66 | // 3. We don't know about the addition from the other thread and go ahead with the deletion. This 67 | // loses and leaks the added element, or any update the other thread has done. 68 | // 69 | // The original article used the I-nodes and another kind of nodes to solve this problem. We are 70 | // going to get inspired by what they did, but will do it inline in the array of pointers. 71 | // 72 | // On any sane system, data structures like our inner or data nodes are aligned to multiples of 73 | // some number (assuming we have at least 32bit system, it's at least multiple of 4 bytes). This 74 | // leaves two bits that are always 0 in the pointers. We can abuse these bits to store additional 75 | // flags (we use the utilities of crossbeam_utils to manage the bits). One of these bits, when set 76 | // to 1, will mean that the pointer is no longer allowed to be updated. 77 | // 78 | // So, when removing, we first check once if the inner node may be removed. If not, we're done. If 79 | // it looks like it may be, we walk the pointers again, but this time we atomically read and mark 80 | // them with the flag. This'll make sure nobody is allowed to sneak an update to it past our second 81 | // check (the first one is just an optimisation). So, if we pass the second check, we can safely 82 | // proceed and remove the node. Hurray. We can move one level up towards the root and repeat. 83 | // 84 | // In case the second check failed, we have already marked all the pointers that they are never 85 | // ever allowed to be updated again. We can't leave a node like that in the data structure forever. 86 | // Therefore, we create a new copy of the node, with clean pointers and replace the node with that 87 | // (in that case we can stop the processing at this level). 88 | // 89 | // So, what the other thread that wants to update the pointer but can't because it is marked does? 90 | // It can't wait for the thread that did the marking to finish its job, because then the algorithm 91 | // would no longer be lock-free. But it can decide to do its work for it and also do the pruning 92 | // (only of this particular one inner node; it won't walk further towards the root). It proceeds to 93 | // the second check stage ‒ marks all the pointers (even if they are already marked) and deciding 94 | // if it can remove it completely or if it needs to create a brand new copy. One of the threads 95 | // competing for the prune operation will succeed, the other will fail during the CaS update of the 96 | // parent pointer, but both can proceed because the pruning already happened. 97 | // 98 | // As this collision on node being prune is likely to be rare in practice and it is already 99 | // relatively complex (and hard to test for) situation, the other thread simply completely restarts 100 | // the operation instead of trying to get all the corner cases right. The removal thread, however, 101 | // proceeds towards the root even in the collision situation ‒ it is responsible for pruning as far 102 | // as possible and when going up, the corner cases don't actually happen (well, with the exception 103 | // of its whole branch being already removed or contracted away by another removal, but in that 104 | // case it'll just waste a little bit of effort in trying to remove stuff in places that are going 105 | // to be thrown away anyway ‒ but thanks to crossbeam_epoch, it is still valid memory). 106 | // 107 | // ## Iteration 108 | // 109 | // Similar to lookups, iteration doesn't have to care about the flags about non-updateable 110 | // pointers ‒ even the old, marked, pointers form a valid representation of the map at a certain 111 | // point of time, though not necessarily optimally small. 112 | // 113 | // Therefore, the iterator simply keeps a stack of nodes it is in, with indices into either the 114 | // pointer array or the array of elements in a data node and does a DFS through the data structure. 115 | // 116 | // # Safety 117 | // 118 | // The current module contains a lot of unsafe code. In general, there are two kinds of things that 119 | // could go wrong. Well, in addition to coding bugs, of course. 120 | // 121 | // ## Lifetimes & invalid pointers 122 | // 123 | // First, we simply never insert pointers that would be invalid at that time into the data 124 | // structure ‒ whatever gets inserted is just brand new allocated thing. This boils down to just 125 | // being careful and, as this is relatively short code, this is possible to accomplish. 126 | // 127 | // So, we must make sure nothing gets destroyed too soon. To accomplish this, we use the mechanism 128 | // of crossbeam_epoch. When we remove something from the data structure, the destruction is 129 | // postponed to when all the threads leave the current epoch. We never hold onto the pointers or 130 | // references past the current method and we bind the lifetimes of the references to the lifetime 131 | // of passed pin and us. 132 | // 133 | // There are two exceptions to this: 134 | // 135 | // * The destructor deletes things right away. But as it holds a mutable reference, we can't be 136 | // destroying anything for any other thread ‒ the other thread no longer holds reference to us. 137 | // * The iterator binds the lifetimes to both itself and the map, but it holds a pin alive, so the 138 | // same would still apply. 139 | // 140 | // ## Inter-thread synchronization of data. 141 | // 142 | // In general, we use release ordering when putting data into the map and consume ordering when 143 | // reading it out of it. The claim is, this is enough. But as the elements are independent on each 144 | // other and only the inner nodes we traverse during the operation play any role to us (and we 145 | // access the further nodes through the loaded pointers) and there's exactly one path from the root 146 | // to each element (therefore anyone getting the element must have touched the same pointers), this 147 | // is basically the definition of how release/consume synchronization edges work. 148 | // 149 | // There are some other orderings through the code, though: 150 | // 151 | // * Relaxed in case we *fail* a CaS update. However, in such case nothing is modified and we just 152 | // throw the data we've created ourselves out, so there's nothing to synchronize. 153 | // * Relaxed in the first check for pruning. This one does not look at the actual data behind the 154 | // pointers, it simply counts how many pointers are non-null. The second pass does the actual 155 | // proper synchronization. 156 | // * Relaxed in the is_empty. As we don't care what data it points to (only that it's not null), 157 | // this again doesn't have to synchronize anything. 158 | // * Relaxed in the destructor. As argued above (and at the destructor itself), we have gained a 159 | // unique access to the whole map. Therefore, the whole map, containing the data in it must have 160 | // been properly synchronized into the thread already and we are in a single-threaded scenario. 161 | // * AcqRel in the pruning. This is because we need to acquire the data pointed to in the case 162 | // we'll be making a copy and we'll have to re-release it later on. We also modify the value of 163 | // the pointer (with the flag), therefore anyone reading it after us only synchronizes against 164 | // us, so we also need to re-release it right now onto that pointer. 165 | // 166 | // [article]: https://www.researchgate.net/publication/221643801_Concurrent_Tries_with_Efficient_Non-Blocking_Snapshots 167 | // [Wikipedia entry]: https://en.wikipedia.org/wiki/Ctrie 168 | 169 | use std::borrow::Borrow; 170 | use std::hash::{BuildHasher, Hash, Hasher}; 171 | use std::marker::PhantomData; 172 | use std::mem; 173 | use std::sync::atomic::Ordering; 174 | 175 | use arrayvec::ArrayVec; 176 | use bitflags::bitflags; 177 | use crossbeam_epoch::{Atomic, Guard, Owned, Shared}; 178 | use smallvec::SmallVec; 179 | 180 | pub mod config; 181 | pub mod debug; 182 | pub mod iterator; 183 | 184 | use self::config::Config; 185 | use crate::existing_or_new::ExistingOrNew; 186 | 187 | // All directly written, some things are not const fn yet :-(. But tested below. 188 | pub(crate) const LEVEL_BITS: usize = 4; 189 | pub(crate) const LEVEL_MASK: u64 = 0b1111; 190 | pub(crate) const LEVEL_CELLS: usize = 16; 191 | pub(crate) const MAX_LEVELS: usize = mem::size_of::() * 8 / LEVEL_BITS; 192 | 193 | bitflags! { 194 | /// Flags that can be put onto a pointer pointing to a node, specifying some interesting 195 | /// things. 196 | /// 197 | /// Note that this lives inside the unused bits of a pointer. All nodes align at least to a 198 | /// machine word and we assume it's at least 32bits, so we have at least 2 bits. 199 | struct NodeFlags: usize { 200 | /// The Inner containing this pointer is condemned to replacement/pruning. 201 | /// 202 | /// Changing this pointer is pointer is forbidden, and the containing Inner needs to be 203 | /// replaced first with a clean one. 204 | const CONDEMNED = 0b01; 205 | /// The pointer points not to an inner node, but to data node. 206 | /// 207 | /// # Rationale 208 | /// 209 | /// The [`Inner`] nodes are quite large. On the other hand, the values are usually just 210 | /// [`Arc`][std::sync::Arc] and there's usually just one at each leaf. That leaves a lot of 211 | /// wasted space. 212 | /// 213 | /// Therefore, instead of having an enum, we have nodes of two distinct types. We recognize 214 | /// them by this flag in the pointer pointing to them. If it is a leaf with data, this flag 215 | /// is set and anyone accessing it knows it needs to type cast the pointer before using. 216 | const DATA = 0b10; 217 | } 218 | } 219 | 220 | /// Extracts [`NodeFlags`] from a pointer. 221 | fn nf(node: Shared) -> NodeFlags { 222 | NodeFlags::from_bits(node.tag()).expect("Invalid node flags") 223 | } 224 | 225 | /// Type-casts the pointer to a [`Data`] node. 226 | unsafe fn load_data<'a, C: Config>(node: Shared<'a, Inner>) -> &'a Data { 227 | assert!( 228 | nf(node).contains(NodeFlags::DATA), 229 | "Tried to load data from inner node pointer" 230 | ); 231 | (node.as_raw() as usize as *const Data) 232 | .as_ref() 233 | .expect("A null pointer with data flag found") 234 | } 235 | 236 | /// Moves a data node behind an [`Owned`] pointer, casts it and provides the correct flags. 237 | fn owned_data(data: Data) -> Owned { 238 | unsafe { 239 | Owned::::from_raw(Box::into_raw(Box::new(data)) as usize as *mut _) 240 | .with_tag(NodeFlags::DATA.bits()) 241 | } 242 | } 243 | 244 | /// Type-casts and drops the node as data. 245 | unsafe fn drop_data(ptr: Shared) { 246 | assert!( 247 | nf(ptr).contains(NodeFlags::DATA), 248 | "Tried to drop data from inner node pointer" 249 | ); 250 | drop(ptr.into_owned()); 251 | } 252 | 253 | /// An inner branching node of the trie. 254 | /// 255 | /// This is just a bunch of pointers to lower levels. 256 | #[derive(Default)] 257 | struct Inner([Atomic; LEVEL_CELLS]); 258 | 259 | // Instead of distinguishing the very common case of single leaf and collision list in our code, we 260 | // just handle everything as a list, possibly with 1 element. 261 | // 262 | // However, as the case with 1 element is much more probable, we don't want the Vec indirection 263 | // there, so we let SmallVec to handle it by not spilling in that case. As the spilled Vec needs 2 264 | // words in addition to the length (pointer and capacity), we have room for 2 Arcs in the not 265 | // spilled case too, so we as well might take advantage of it. 266 | // TODO: We want the union feature. 267 | // 268 | // Alternatively, we probably could use the raw allocator API and structure with len + [Arc<..>; 0]. 269 | // TODO: Compute the stack length based on the Payload size. 270 | type Data = SmallVec<[::Payload; 2]>; 271 | 272 | enum TraverseState<'a, C: Config, Q, F> { 273 | Empty, // Invalid temporary state. 274 | Created(C::Payload), 275 | Future { key: &'a Q, constructor: F }, 276 | } 277 | 278 | impl TraverseState<'_, C, Q, F> 279 | where 280 | C::Key: Borrow, 281 | F: FnOnce(&Q) -> C::Payload 282 | { 283 | fn key(&self) -> &Q { 284 | match self { 285 | TraverseState::Empty => unreachable!("Not supposed to live in the empty state"), 286 | TraverseState::Created(payload) => payload.borrow().borrow(), 287 | TraverseState::Future { key, .. } => key, 288 | } 289 | } 290 | fn payload(&mut self) -> C::Payload { 291 | let (new_val, result) = match mem::replace(self, TraverseState::Empty) { 292 | TraverseState::Empty => unreachable!("Not supposed to live in the empty state"), 293 | TraverseState::Created(payload) => (TraverseState::Created(payload.clone()), payload), 294 | TraverseState::Future { key, constructor } => { 295 | let payload = constructor(key); 296 | let created = TraverseState::Created(payload.clone()); 297 | (created, payload) 298 | } 299 | }; 300 | *self = new_val; 301 | result 302 | } 303 | fn data_owned(&mut self) -> Owned { 304 | let mut data = Data::::new(); 305 | data.push(self.payload()); 306 | owned_data::(data) 307 | } 308 | } 309 | 310 | #[derive(Copy, Clone, Eq, PartialEq)] 311 | enum TraverseMode { 312 | Overwrite, 313 | IfMissing, 314 | } 315 | 316 | /// How well pruning went. 317 | #[derive(Copy, Clone, Eq, PartialEq)] 318 | enum PruneResult { 319 | /// Removed the node completely, inserted NULL into the parent. 320 | Null, 321 | /// Contracted an edge, inserted a lone child. 322 | Singleton, 323 | /// Made a copy, as there were multiple pointers leading from the child. 324 | Copy, 325 | /// Failed to update the parent, some other thread updated it in the meantime. 326 | CasFail, 327 | } 328 | 329 | /// The raw hash trie data structure. 330 | /// 331 | /// This provides the low level data structure. It does provide the lock-free operations on some 332 | /// values. On the other hand, it does not provide user friendly interface. It is designed to 333 | /// separate the single implementation of the core algorithm and provide a way to wrap it into 334 | /// different interfaces for different use cases. 335 | /// 336 | /// It, however, can be used to fulfill some less common uses. 337 | /// 338 | /// The types stored inside and general behaviour is described by the [`Config`] type parameter and 339 | /// can be customized using that. 340 | /// 341 | /// As a general rule, this data structure takes the [`crossbeam_epoch`] [`Guard`] and returns 342 | /// borrowed data whenever appropriate. This allows cheaper manipulation if necessary or grouping 343 | /// multiple operations together. Note than even methods that would return owned values in 344 | /// single-threaded case (eg. [`insert`][Raw::insert] and [`remove`][Raw::remove] return borrowed 345 | /// values. This is because in concurrent situation some other thread might still be accessing 346 | /// them. They are scheduled for destruction once the epoch ends. 347 | /// 348 | /// For details of the internal implementation and correctness arguments, see the comments in 349 | /// source code (they probably don't belong into API documentation). 350 | pub struct Raw { 351 | hash_builder: S, 352 | root: Atomic, 353 | _data: PhantomData, 354 | } 355 | 356 | impl Raw 357 | where 358 | C: Config, 359 | S: BuildHasher, 360 | { 361 | /// Constructs an empty instance from the given hasher. 362 | pub fn with_hasher(hash_builder: S) -> Self { 363 | // Note: on any sane system, these assertions should actually never ever trigger no matter 364 | // what the user of the crate does. This is *internal* sanity check. If you ever find a 365 | // case where it *does* fail, open a bug report. 366 | assert!( 367 | mem::align_of::>().trailing_zeros() >= NodeFlags::all().bits().count_ones(), 368 | "BUG: Alignment of Data is not large enough to store the internal flags", 369 | ); 370 | assert!( 371 | mem::align_of::().trailing_zeros() >= NodeFlags::all().bits().count_ones(), 372 | "BUG: Alignment of Inner not large enough to store internal flags", 373 | ); 374 | Self { 375 | hash_builder, 376 | root: Atomic::null(), 377 | _data: PhantomData, 378 | } 379 | } 380 | 381 | /// Computes a hash (using the stored hasher) of a key. 382 | fn hash(&self, key: &Q) -> u64 383 | where 384 | Q: ?Sized + Hash, 385 | { 386 | let mut hasher = self.hash_builder.build_hasher(); 387 | key.hash(&mut hasher); 388 | hasher.finish() 389 | } 390 | 391 | /// Inserts a new value, replacing and returning any previously held value. 392 | pub fn insert<'s, 'p, 'r>( 393 | &'s self, 394 | payload: C::Payload, 395 | pin: &'p Guard, 396 | ) -> Option<&'r C::Payload> 397 | where 398 | 's: 'r, 399 | 'p: 'r, 400 | { 401 | self.traverse( 402 | // Any way to do it without the type parameters here? Older rustc doesn't like them. 403 | TraverseState:: C::Payload>::Created(payload), 404 | TraverseMode::Overwrite, 405 | pin, 406 | ) 407 | // TODO: Should we sanity-check this is Existing because it returns the previous value? 408 | .map(ExistingOrNew::into_inner) 409 | } 410 | 411 | /// Prunes the given node. 412 | /// 413 | /// * The parent points to the child node. 414 | /// * The child must be valid pointer, of course. 415 | /// 416 | /// The parent is made to point to either: 417 | /// * NULL if child is empty. 418 | /// * child's only child. 419 | /// * A copy of child. 420 | /// 421 | /// Returns how the pruning went. 422 | unsafe fn prune(pin: &Guard, parent: &Atomic, child: Shared) -> PruneResult { 423 | assert!( 424 | !nf(child).contains(NodeFlags::DATA), 425 | "Child passed to prune must not be data" 426 | ); 427 | let inner = child.as_ref().expect("Null child node passed to prune"); 428 | let mut allow_contract = true; 429 | let mut child_cnt = 0; 430 | let mut last_leaf = None; 431 | let mut new_child = Inner::default(); 432 | 433 | // 1. Mark all the cells in this one as condemned. 434 | // 2. Look how many non-null branches are leading from there. 435 | // 3. Construct a copy of the child *without* the tags on the way. 436 | for (new, grandchild) in new_child.0.iter_mut().zip(&inner.0) { 437 | // Acquire ‒ Besides potentially looking at the child, we'll need to republish the 438 | // child in our swap of the pointer (this one and also the one below, in the CAS). To 439 | // do that we'll have to have acquired it first. 440 | // 441 | // Note that we don't need SeqCst here nor in the CaS below. We don't care about the 442 | // order ‒ the tagging is just making sure this particular slot never ever changes the 443 | // pointer. The CaS changes the trie in content-equivalent way, so observing either the 444 | // old or the new way is fine. 445 | let gc = grandchild.fetch_or(NodeFlags::CONDEMNED.bits(), Ordering::AcqRel, pin); 446 | // The flags we insert into the new one should not contain condemned flag even if it 447 | // was already present here. 448 | let flags = nf(gc) & !NodeFlags::CONDEMNED; 449 | let gc = gc.with_tag(flags.bits()); 450 | if gc.is_null() { 451 | // Do nothing, just skip 452 | } else if flags.contains(NodeFlags::DATA) { 453 | last_leaf.replace(gc); 454 | let gc = load_data::(gc); 455 | child_cnt += gc.len(); 456 | } else { 457 | // If we have an inner node here, multiple leaves hang somewhere below there. More 458 | // importantly, we can't contrack the edge. 459 | allow_contract = false; 460 | child_cnt += 1; 461 | } 462 | 463 | *new = Atomic::from(gc); 464 | } 465 | 466 | // Now, decide what we want to put into the parent. 467 | let mut cleanup = None; 468 | let (insert, prune_result) = match (allow_contract, child_cnt, last_leaf) { 469 | // If there's exactly one leaf, we just contract the edge to lead there directly. Note 470 | // that we can't do that if this is not the leaf, because we would mess up the hash 471 | // matching on the way. But that's fine, we checked that above. 472 | (true, 1, Some(child)) => (child, PruneResult::Singleton), 473 | // If there's nothing, simply kill the node outright. 474 | (_, 0, None) => (Shared::null(), PruneResult::Null), 475 | // Many nodes (maybe somewhere below) ‒ someone must have inserted in between. But 476 | // we've already condemned this node, so create a new one and do the replacement. 477 | _ => { 478 | let new = Owned::new(new_child).into_shared(pin); 479 | // Note: we don't store Owned, because we may link it in. If we panicked before 480 | // disarming it, it would delete something linked in, which is bad. Instead, we 481 | // prefer deleting manually after the fact. 482 | cleanup = Some(new); 483 | (new, PruneResult::Copy) 484 | } 485 | }; 486 | 487 | assert_eq!( 488 | 0, 489 | child.tag(), 490 | "Attempt to replace condemned pointer or prune data node" 491 | ); 492 | // Orderings: We need to publish the new node. We don't need to acquire the previous value 493 | // to destroy, because we already have it in case of success and we don't care about it on 494 | // failure. 495 | let result = parent 496 | .compare_and_set(child, insert, (Ordering::Release, Ordering::Relaxed), pin) 497 | .is_ok(); 498 | if result { 499 | // We successfully unlinked the old child, so it's time to destroy it (as soon as 500 | // nobody is looking at it). 501 | pin.defer_destroy(child); 502 | prune_result 503 | } else { 504 | // We have failed to insert, so we need to clean up after ourselves. 505 | drop(cleanup.map(|c| Shared::into_owned(c))); 506 | PruneResult::CasFail 507 | } 508 | } 509 | 510 | /// Inner implementation of traversing the tree, creating missing branches and doing 511 | /// *something* at the leaf. 512 | fn traverse<'s, 'p, 'r, Q, F>( 513 | &'s self, 514 | mut state: TraverseState, 515 | mode: TraverseMode, 516 | pin: &'p Guard, 517 | ) -> Option> 518 | where 519 | 's: 'r, 520 | 'p: 'r, 521 | C::Key: Borrow, 522 | Q: Eq + Hash, 523 | F: FnOnce(&Q) -> C::Payload, 524 | { 525 | let hash = self.hash(state.key()); 526 | let mut shift = 0; 527 | let mut current = &self.root; 528 | let mut parent = None; 529 | loop { 530 | let node = current.load_consume(&pin); 531 | let flags = nf(node); 532 | 533 | let replace = |with: Owned, delete_previous| { 534 | // If we fail to set it, the `with` is dropped together with the Err case, freeing 535 | // whatever was inside it. 536 | let result = current.compare_and_set_weak( 537 | node, 538 | with, 539 | (Ordering::Release, Ordering::Relaxed), 540 | pin, 541 | ); 542 | match result { 543 | Ok(new) if !node.is_null() && delete_previous => { 544 | assert!(flags.contains(NodeFlags::DATA)); 545 | let node = Shared::from(node.as_raw() as usize as *const Data); 546 | unsafe { pin.defer_destroy(node) }; 547 | Some(new) 548 | } 549 | Ok(new) => Some(new), 550 | Err(e) => { 551 | if NodeFlags::from_bits(e.new.tag()) 552 | .expect("Invalid flags") 553 | .contains(NodeFlags::DATA) 554 | { 555 | unsafe { drop_data::(e.new.into_shared(&pin)) }; 556 | } 557 | // Else → just let e drop and destroy the owned in there 558 | None 559 | } 560 | } 561 | }; 562 | 563 | if flags.contains(NodeFlags::CONDEMNED) { 564 | // This one is going away. We are not allowed to modify the cell, we just have to 565 | // replace the inner node first. So, let's do some cleanup. 566 | // 567 | // TODO: In some cases we would not really *have* to do this (in particular, if we 568 | // just want to walk through and not modify it here at all, it's OK). 569 | unsafe { 570 | let (parent, child) = parent.expect("Condemned the root!"); 571 | Self::prune(&pin, parent, child); 572 | } 573 | // Either us or someone else modified the tree on our path. In many cases we 574 | // could just continue here, but some cases are complex. For now, we just restart 575 | // the whole traversal and try from the start, for simplicity. This should be rare 576 | // anyway, so complicating the code further probably is not worth it. 577 | shift = 0; 578 | current = &self.root; 579 | parent = None; 580 | } else if node.is_null() { 581 | // Not found, create it. 582 | if let Some(new) = replace(state.data_owned(), true) { 583 | if mode == TraverseMode::Overwrite { 584 | return None; 585 | } else { 586 | let new = unsafe { load_data::(new) }; 587 | return Some(ExistingOrNew::New(&new[0])); 588 | } 589 | } 590 | // else -> retry 591 | } else if flags.contains(NodeFlags::DATA) { 592 | let data = unsafe { load_data::(node) }; 593 | assert!(!data.is_empty(), "Empty data nodes must not be kept around"); 594 | if data[0].borrow().borrow() != state.key() && shift < mem::size_of_val(&hash) * 8 { 595 | assert!(data.len() == 1, "Collision node not deep enough"); 596 | // There's one data node at this pointer, but we want to place a different one 597 | // here too. So we create a new level, push the old one down. Note that we 598 | // check both that we are adding something else & that we still have some more 599 | // bits to distinguish by. 600 | 601 | // We need to add another level. Note: there *still* might be a collision. 602 | // Therefore, we just add the level and try again. 603 | let other_hash = self.hash(data[0].borrow()); 604 | let other_bits = (other_hash >> shift) & LEVEL_MASK; 605 | let mut inner = Inner::default(); 606 | inner.0[other_bits as usize] = Atomic::from(node); 607 | let split = Owned::new(inner); 608 | // No matter if it succeeds or fails, we try again. We'll either find the newly 609 | // inserted value here and continue with another level down, or it gets 610 | // destroyed and we try splitting again. 611 | replace(split, false); 612 | } else { 613 | // All the other cases: 614 | // * It has the same key 615 | // * There's already a collision on this level (because we've already run out of 616 | // bits previously). 617 | // * We've run out of the hash bits so there's nothing to split by any more. 618 | let mut result = data 619 | .iter() 620 | .find(|l| (*l).borrow().borrow() == state.key()) 621 | .map(ExistingOrNew::Existing); 622 | 623 | if result.is_none() || mode == TraverseMode::Overwrite { 624 | let mut new = Data::::with_capacity(data.len() + 1); 625 | new.extend( 626 | data.iter() 627 | .filter(|l| (*l).borrow() != state.key()) 628 | .cloned(), 629 | ); 630 | new.push(state.payload()); 631 | new.shrink_to_fit(); 632 | let new = owned_data::(new); 633 | if let Some(new) = replace(new, true) { 634 | if result.is_none() && mode == TraverseMode::IfMissing { 635 | let new = unsafe { load_data::(new) }; 636 | result = Some(ExistingOrNew::New(new.last().unwrap())); 637 | } 638 | } else { 639 | continue; 640 | } 641 | } 642 | 643 | return result; 644 | } 645 | } else { 646 | // An inner node, go one level deeper. 647 | let inner = unsafe { node.as_ref().expect("We just checked this is not NULL") }; 648 | let bits = (hash >> shift) & LEVEL_MASK; 649 | shift += LEVEL_BITS; 650 | parent = Some((current, node)); 651 | current = &inner.0[bits as usize]; 652 | } 653 | } 654 | } 655 | 656 | /// Looks up a value. 657 | pub fn get<'r, 's, 'p, Q>(&'s self, key: &Q, pin: &'p Guard) -> Option<&'r C::Payload> 658 | where 659 | 's: 'r, 660 | 'p: 's, 661 | Q: ?Sized + Eq + Hash, 662 | C::Key: Borrow, 663 | { 664 | let mut current = &self.root; 665 | let mut hash = self.hash(key); 666 | loop { 667 | let node = current.load_consume(pin); 668 | let flags = nf(node); 669 | if node.is_null() { 670 | return None; 671 | } else if flags.contains(NodeFlags::DATA) { 672 | return unsafe { load_data::(node) } 673 | .iter() 674 | .find(|l| (*l).borrow().borrow() == key); 675 | } else { 676 | let inner = unsafe { node.as_ref().expect("We just checked this is not NULL") }; 677 | let bits = hash & LEVEL_MASK; 678 | hash >>= LEVEL_BITS; 679 | current = &inner.0[bits as usize]; 680 | } 681 | } 682 | } 683 | 684 | /// Looks up a value or create (and insert) a new one. 685 | /// 686 | /// Either way, returns the value. 687 | pub fn get_or_insert_with<'s, 'p, 'r, Q, F>( 688 | &'s self, 689 | key: &Q, 690 | create: F, 691 | pin: &'p Guard, 692 | ) -> ExistingOrNew<&'r C::Payload> 693 | where 694 | 's: 'r, 695 | 'p: 'r, 696 | C::Key: Borrow, 697 | F: FnOnce(&Q) -> C::Payload, 698 | { 699 | let state = TraverseState::Future { 700 | key, 701 | constructor: create, 702 | }; 703 | self.traverse(state, TraverseMode::IfMissing, pin) 704 | .expect("Should have created one for me") 705 | } 706 | 707 | /// Removes a value identified by the key from the trie, returning it if it was found. 708 | pub fn remove<'r, 's, 'p, Q>(&'s self, key: &Q, pin: &'p Guard) -> Option<&'r C::Payload> 709 | where 710 | 's: 'r, 711 | 'p: 'r, 712 | Q: ?Sized + Eq + Hash, 713 | C::Key: Borrow, 714 | { 715 | let mut current = &self.root; 716 | let hash = self.hash(key); 717 | let mut shift = 0; 718 | let mut levels: ArrayVec<[_; MAX_LEVELS]> = ArrayVec::new(); 719 | let deleted = loop { 720 | let node = current.load_consume(&pin); 721 | let flags = nf(node); 722 | let replace = |with: Shared<_>| { 723 | let result = current.compare_and_set_weak( 724 | node, 725 | with, 726 | (Ordering::Release, Ordering::Relaxed), 727 | &pin, 728 | ); 729 | match result { 730 | Ok(_) => { 731 | assert!(flags.contains(NodeFlags::DATA)); 732 | unsafe { 733 | let node = Shared::from(node.as_raw() as usize as *const Data); 734 | pin.defer_destroy(node); 735 | } 736 | true 737 | } 738 | Err(ref e) if !e.new.is_null() => { 739 | assert!(nf(e.new).contains(NodeFlags::DATA)); 740 | unsafe { drop_data::(e.new) }; 741 | false 742 | } 743 | Err(_) => false, 744 | } 745 | }; 746 | 747 | if node.is_null() { 748 | // Nothing to delete, so just give up (without pruning). 749 | return None; 750 | } else if flags.contains(NodeFlags::CONDEMNED) { 751 | unsafe { 752 | let (current, node) = levels.pop().expect("Condemned the root"); 753 | Self::prune(&pin, current, node); 754 | } 755 | // Retry by starting over from the top, for similar reasons to the one in 756 | // insert. 757 | levels.clear(); 758 | shift = 0; 759 | current = &self.root; 760 | } else if flags.contains(NodeFlags::DATA) { 761 | let data = unsafe { load_data::(node) }; 762 | // Try deleting the thing. 763 | let mut deleted = None; 764 | let new = data 765 | .iter() 766 | .filter(|l| { 767 | if (*l).borrow().borrow() == key { 768 | deleted = Some(*l); 769 | false 770 | } else { 771 | true 772 | } 773 | }) 774 | .cloned() 775 | .collect::>(); 776 | 777 | if deleted.is_some() { 778 | let new = if new.is_empty() { 779 | Shared::null() 780 | } else { 781 | owned_data::(new).into_shared(&pin) 782 | }; 783 | if !replace(new) { 784 | continue; 785 | } 786 | } 787 | 788 | break deleted; 789 | } else { 790 | let inner = unsafe { node.as_ref().expect("We just checked for NULL") }; 791 | levels.push((current, node)); 792 | let bits = (hash >> shift) & LEVEL_MASK; 793 | shift += LEVEL_BITS; 794 | current = &inner.0[bits as usize]; 795 | } 796 | }; 797 | 798 | // Go from the top and try to clean up. 799 | if deleted.is_some() { 800 | for (parent, child) in levels.into_iter().rev() { 801 | let inner = unsafe { child.as_ref().expect("We just checked for NULL") }; 802 | 803 | // This is an optimisation ‒ replacing the thing is expensive, so we want to check 804 | // first (which is cheaper). 805 | let non_null = inner 806 | .0 807 | .iter() 808 | .filter(|ptr| !ptr.load(Ordering::Relaxed, &pin).is_null()) 809 | .count(); 810 | if non_null > 1 { 811 | // No reason to go into the upper levels. 812 | break; 813 | } 814 | 815 | // OK, we think we could remove this node. Try doing so. 816 | if let PruneResult::Copy = unsafe { Self::prune(&pin, parent, child) } { 817 | // Even though we tried to count how many pointers there are, someone must have 818 | // added some since. So there's no way we can prone anything higher up and we 819 | // give up. 820 | break; 821 | } 822 | // Else: 823 | // Just continue with higher levels. Even if someone made the contraction for 824 | // us, it should be safe to do so. 825 | } 826 | } 827 | 828 | deleted 829 | } 830 | } 831 | 832 | impl Raw { 833 | /// Checks for emptiness. 834 | pub fn is_empty(&self) -> bool { 835 | // This relies on proper branch pruning. 836 | // We can use the unprotected here, because we are not actually interested in where the 837 | // pointer points to. Therefore we can also use the Relaxed ordering. 838 | unsafe { 839 | self.root 840 | .load(Ordering::Relaxed, &crossbeam_epoch::unprotected()) 841 | .is_null() 842 | } 843 | } 844 | 845 | /// Access to the hash builder. 846 | pub fn hash_builder(&self) -> &S { 847 | &self.hash_builder 848 | } 849 | } 850 | 851 | impl Drop for Raw { 852 | fn drop(&mut self) { 853 | /* 854 | * Notes about unsafety here: 855 | * * We are in a destructor and that one is &mut self. There are no concurrent accesses to 856 | * this data structure any more, therefore we can safely assume we are the only ones 857 | * looking at the pointers inside. 858 | * * Therefore, using unprotected is also fine. 859 | * * Similarly, the Relaxed ordering here is fine too, as the whole data structure must 860 | * have been synchronized into our thread already by this time. 861 | * * The pointer inside this data structure is never dangling. 862 | */ 863 | unsafe fn drop_recursive(node: &Atomic) { 864 | let pin = crossbeam_epoch::unprotected(); 865 | let extract = node.load(Ordering::Relaxed, &pin); 866 | let flags = nf(extract); 867 | if extract.is_null() { 868 | // Skip 869 | } else if flags.contains(NodeFlags::DATA) { 870 | drop_data::(extract); 871 | } else { 872 | let owned = extract.into_owned(); 873 | for sub in &owned.0 { 874 | drop_recursive::(sub); 875 | } 876 | drop(owned); 877 | } 878 | } 879 | unsafe { drop_recursive::(&self.root) }; 880 | } 881 | } 882 | 883 | #[cfg(test)] 884 | pub(crate) mod tests { 885 | use std::ptr; 886 | 887 | use super::config::Trivial as TrivialConfig; 888 | use super::*; 889 | 890 | // A hasher to create collisions on purpose. Let's make the hash trie into a glorified array. 891 | // We allow tests in higher-level modules to reuse it for their tests. 892 | pub(crate) struct NoHasher; 893 | 894 | impl Hasher for NoHasher { 895 | fn finish(&self) -> u64 { 896 | 0 897 | } 898 | 899 | fn write(&mut self, _: &[u8]) {} 900 | } 901 | 902 | impl BuildHasher for NoHasher { 903 | type Hasher = NoHasher; 904 | 905 | fn build_hasher(&self) -> NoHasher { 906 | NoHasher 907 | } 908 | } 909 | 910 | #[derive(Clone, Copy, Debug, Default)] 911 | pub(crate) struct SplatHasher(u64); 912 | 913 | impl Hasher for SplatHasher { 914 | fn finish(&self) -> u64 { 915 | self.0 916 | } 917 | fn write(&mut self, value: &[u8]) { 918 | for val in value { 919 | for idx in 0..mem::size_of::() { 920 | self.0 ^= u64::from(*val) << (8 * idx); 921 | } 922 | } 923 | } 924 | } 925 | 926 | pub(crate) struct MakeSplatHasher; 927 | 928 | impl BuildHasher for MakeSplatHasher { 929 | type Hasher = SplatHasher; 930 | 931 | fn build_hasher(&self) -> SplatHasher { 932 | SplatHasher::default() 933 | } 934 | } 935 | 936 | /// Tests the test hasher. 937 | /// 938 | /// Because it was giving us some trouble ☹ 939 | #[test] 940 | fn splat_hasher() { 941 | let mut hasher = MakeSplatHasher.build_hasher(); 942 | hasher.write_u8(0); 943 | assert_eq!(0, hasher.finish()); 944 | hasher.write_u8(8); 945 | assert_eq!(0x0808_0808_0808_0808, hasher.finish()); 946 | } 947 | 948 | #[test] 949 | fn consts_consistent() { 950 | assert!(LEVEL_CELLS.is_power_of_two()); 951 | assert_eq!(LEVEL_BITS, LEVEL_MASK.count_ones() as usize); 952 | assert_eq!(LEVEL_BITS, (!LEVEL_MASK).trailing_zeros() as usize); 953 | assert_eq!(LEVEL_CELLS, 2usize.pow(LEVEL_BITS as u32)); 954 | } 955 | 956 | /// Pretend something left a condemned marker on one of the nodes when we insert. This will get 957 | /// cleaned up. 958 | /// 959 | /// And yes, the test abuses the fact that it knows how the specific hasher works and 960 | /// distributes the given values. 961 | #[test] 962 | fn prune_on_insert() { 963 | let mut map = Raw::, _>::with_hasher(MakeSplatHasher); 964 | let pin = crossbeam_epoch::pin(); 965 | for i in 0..LEVEL_CELLS as u8 { 966 | assert!(map.insert(i, &pin).is_none()); 967 | } 968 | 969 | eprintln!("{}", debug::PrintShape(&map)); 970 | 971 | // By now, we should have exactly one data node under each pointer under root. Sanity 972 | // check that (Relaxed is fine, we are in a single threaded test). 973 | let root = map.root.load(Ordering::Relaxed, &pin); 974 | let flags = nf(root); 975 | assert_eq!( 976 | NodeFlags::empty(), 977 | flags, 978 | "Root should be non-condemned inner node" 979 | ); 980 | assert!(!root.is_null()); 981 | let old_root = root.as_raw(); 982 | let root = unsafe { root.deref() }; 983 | 984 | for ptr in &root.0 { 985 | let ptr = ptr.load(Ordering::Relaxed, &pin); 986 | assert!(!ptr.is_null()); 987 | let flags = nf(ptr); 988 | assert_eq!( 989 | NodeFlags::DATA, 990 | flags, 991 | "Expected a data node, found {:?}", 992 | ptr 993 | ); 994 | } 995 | 996 | // Now, *start* condemning the node. Mark the first slot, the one we'll eventually use. 997 | root.0[0].fetch_or(NodeFlags::CONDEMNED.bits(), Ordering::Relaxed, &pin); 998 | 999 | // This touches the condemned slot, so it should trigger fixing stuff. 1000 | let old = map.insert(0, &pin); 1001 | assert_eq!(0, *old.unwrap()); 1002 | 1003 | // The condemned flag must have disappeared by now. 1004 | map.assert_pruned(); 1005 | 1006 | // And the root should have changed for a brand new one. 1007 | let new_root = map.root.load(Ordering::Relaxed, &pin).as_raw(); 1008 | assert!(!ptr::eq(old_root, new_root), "Condemned node not replaced"); 1009 | 1010 | // But all the content is preserved 1011 | for i in 0..LEVEL_CELLS as u8 { 1012 | assert_eq!(i, *map.get(&i, &pin).unwrap()); 1013 | } 1014 | } 1015 | 1016 | /// Creates an effectively empty map with a leftover (unpruned) but condemned node. 1017 | /// 1018 | /// As the algorithm goes, almost everyone who finds it is responsible for cleaning it up. 1019 | fn with_leftover() -> Raw, MakeSplatHasher> { 1020 | let map = Raw::, _>::with_hasher(MakeSplatHasher); 1021 | let pin = crossbeam_epoch::pin(); 1022 | 1023 | let i = Inner::default(); 1024 | i.0[0].fetch_or(NodeFlags::CONDEMNED.bits(), Ordering::Relaxed, &pin); 1025 | map.root.store(Owned::new(i), Ordering::Relaxed); 1026 | 1027 | // There's nothing in this map effectively, but it doesn't claim to be empty due to the 1028 | // non-null pointer. 1029 | assert!(iterator::Iter::new(&map).next().is_none()); 1030 | assert!(!map.is_empty()); 1031 | 1032 | map 1033 | } 1034 | 1035 | /// Similar as the above, but with empty condemned node. 1036 | /// 1037 | /// Here we put a fake node somewhere into the aether, make it condemned and see how it 1038 | /// disappears on insertion. 1039 | #[test] 1040 | fn prune_on_insert_empty() { 1041 | let mut map = with_leftover(); 1042 | let pin = crossbeam_epoch::pin(); 1043 | let old_root = map.root.load(Ordering::Relaxed, &pin).as_raw(); 1044 | 1045 | // Now, let's insert something so it meets the condemned mark 1046 | assert!(map.insert(0, &pin).is_none()); 1047 | 1048 | map.assert_pruned(); 1049 | let new_root = map.root.load(Ordering::Relaxed, &pin); 1050 | // It got replaced and the root is directly the data node 1051 | let new_flags = nf(new_root); 1052 | assert_eq!(NodeFlags::DATA, new_flags); 1053 | assert!( 1054 | !ptr::eq(old_root, new_root.as_raw()), 1055 | "Condemned node not replaced" 1056 | ); 1057 | } 1058 | 1059 | /// Test that if someone left a un-pruned node and remove finds it, it gets rid of it (even in 1060 | /// cases it does not actually remove anything in particular). 1061 | #[test] 1062 | fn prune_on_remove() { 1063 | let map = Raw::, _>::with_hasher(MakeSplatHasher); 1064 | let pin = crossbeam_epoch::pin(); 1065 | 1066 | let i_inner = Inner::default(); 1067 | let i_outer = Inner::default(); 1068 | i_outer.0[0].store( 1069 | Owned::new(i_inner).with_tag(NodeFlags::CONDEMNED.bits()), 1070 | Ordering::Relaxed, 1071 | ); 1072 | map.root.store(Owned::new(i_outer), Ordering::Relaxed); 1073 | 1074 | // There's nothing in this map effectively, but it doesn't claim to be empty due to the 1075 | // non-null pointer. 1076 | assert!(iterator::Iter::new(&map).next().is_none()); 1077 | assert!(!map.is_empty()); 1078 | 1079 | assert!(map.remove(&0, &pin).is_none()); 1080 | 1081 | eprintln!("{}", debug::PrintShape(&map)); 1082 | 1083 | assert_eq!(0, map.root.load(Ordering::Relaxed, &pin).tag()); 1084 | // Note: it is still *not* properly pruned. The inner node should have a thread it'll clean 1085 | // up later on. And we can't contract it as the one below is inner node, not data node. 1086 | } 1087 | } 1088 | -------------------------------------------------------------------------------- /src/set.rs: -------------------------------------------------------------------------------- 1 | //! The [`ConSet`] and other related structures. 2 | 3 | use std::borrow::Borrow; 4 | use std::collections::hash_map::RandomState; 5 | use std::fmt::{Debug, Formatter, Result as FmtResult}; 6 | use std::hash::{BuildHasher, Hash}; 7 | use std::iter::FromIterator; 8 | 9 | use crossbeam_epoch; 10 | 11 | #[cfg(feature = "rayon")] 12 | use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelExtend, ParallelIterator}; 13 | 14 | use crate::raw::config::Trivial as TrivialConfig; 15 | use crate::raw::{self, Raw}; 16 | 17 | /// A concurrent lock-free set. 18 | /// 19 | /// Note that due to the limitations described in the crate level docs, values returned by looking 20 | /// up (or misplacing or removing) are always copied using the `Clone` trait. Therefore, the set is 21 | /// more suitable for types that are cheap to copy (eg. `u64` or `IpAddr`). 22 | /// 23 | /// If you intend to store types that are more expensive to make copies of or are not `Clone`, you 24 | /// can wrap them in an `Arc` (eg. `Arc`). 25 | /// 26 | /// ```rust 27 | /// use contrie::ConSet; 28 | /// use crossbeam_utils::thread; 29 | /// 30 | /// let set = ConSet::new(); 31 | /// 32 | /// thread::scope(|s| { 33 | /// s.spawn(|_| { 34 | /// set.insert("hello"); 35 | /// }); 36 | /// s.spawn(|_| { 37 | /// set.insert("world"); 38 | /// }); 39 | /// }).unwrap(); 40 | /// 41 | /// assert_eq!(Some("hello"), set.get("hello")); 42 | /// assert_eq!(Some("world"), set.get("world")); 43 | /// assert_eq!(None, set.get("universe")); 44 | /// set.remove("world"); 45 | /// assert_eq!(None, set.get("world")); 46 | /// ``` 47 | /// 48 | /// ```rust 49 | /// use contrie::set::{ConSet}; 50 | /// let set: ConSet = ConSet::new(); 51 | /// 52 | /// set.insert(0); 53 | /// set.insert(1); 54 | /// 55 | /// assert!(set.contains(&1)); 56 | /// 57 | /// set.remove(&1); 58 | /// assert!(!set.contains(&1)); 59 | /// 60 | /// set.remove(&0); 61 | /// assert!(set.is_empty()); 62 | /// ``` 63 | pub struct ConSet 64 | where 65 | T: Clone + Hash + Eq + 'static, 66 | { 67 | raw: Raw, S>, 68 | } 69 | 70 | impl ConSet 71 | where 72 | T: Clone + Hash + Eq + 'static, 73 | { 74 | /// Creates a new empty set. 75 | pub fn new() -> Self { 76 | Self::with_hasher(RandomState::default()) 77 | } 78 | } 79 | 80 | impl ConSet 81 | where 82 | T: Clone + Hash + Eq + 'static, 83 | S: BuildHasher, 84 | { 85 | /// Creates a new empty set with the given hasher. 86 | pub fn with_hasher(hasher: S) -> Self { 87 | Self { 88 | raw: Raw::with_hasher(hasher), 89 | } 90 | } 91 | 92 | /// Inserts a new value into the set. 93 | /// 94 | /// It returns the previous value, if any was present. 95 | pub fn insert(&self, value: T) -> Option { 96 | let pin = crossbeam_epoch::pin(); 97 | self.raw.insert(value, &pin).cloned() 98 | } 99 | 100 | /// Looks up a value in the set. 101 | /// 102 | /// This creates a copy of the original value. 103 | pub fn get(&self, key: &Q) -> Option 104 | where 105 | Q: ?Sized + Eq + Hash, 106 | T: Borrow, 107 | { 108 | let pin = crossbeam_epoch::pin(); 109 | self.raw.get(key, &pin).cloned() 110 | } 111 | 112 | /// Checks if a value identified by the given key is present in the set. 113 | /// 114 | /// Note that by the time you can act on it, the presence of the value can change (eg. other 115 | /// thread can add or remove it in the meantime). 116 | pub fn contains(&self, key: &Q) -> bool 117 | where 118 | Q: ?Sized + Eq + Hash, 119 | T: Borrow, 120 | { 121 | let pin = crossbeam_epoch::pin(); 122 | self.raw.get(key, &pin).is_some() 123 | } 124 | 125 | /// Removes an element identified by the given key, returning it. 126 | pub fn remove(&self, key: &Q) -> Option 127 | where 128 | Q: ?Sized + Eq + Hash, 129 | T: Borrow, 130 | { 131 | let pin = crossbeam_epoch::pin(); 132 | self.raw.remove(key, &pin).cloned() 133 | } 134 | 135 | /// Checks if the set is currently empty. 136 | /// 137 | /// Note that due to being concurrent, the use-case of this method is mostly for debugging 138 | /// purposes, because the state can change between reading the value and acting on it. 139 | pub fn is_empty(&self) -> bool { 140 | self.raw.is_empty() 141 | } 142 | } 143 | 144 | impl Default for ConSet 145 | where 146 | T: Clone + Hash + Eq + 'static, 147 | { 148 | fn default() -> Self { 149 | Self::new() 150 | } 151 | } 152 | 153 | impl Debug for ConSet 154 | where 155 | T: Debug + Clone + Hash + Eq + 'static, 156 | { 157 | fn fmt(&self, fmt: &mut Formatter) -> FmtResult { 158 | fmt.debug_set().entries(self.iter()).finish() 159 | } 160 | } 161 | 162 | impl ConSet 163 | where 164 | T: Clone + Hash + Eq + 'static, 165 | { 166 | /// Returns an iterator through the elements of the set. 167 | pub fn iter(&self) -> Iter { 168 | Iter { 169 | inner: raw::iterator::Iter::new(&self.raw), 170 | } 171 | } 172 | } 173 | 174 | /// The iterator of the [`ConSet`]. 175 | /// 176 | /// See the [`iter`][ConSet::iter] method for details. 177 | pub struct Iter<'a, T, S> 178 | where 179 | T: Clone + Hash + Eq + 'static, 180 | { 181 | inner: raw::iterator::Iter<'a, TrivialConfig, S>, 182 | } 183 | 184 | impl<'a, T, S> Iterator for Iter<'a, T, S> 185 | where 186 | T: Clone + Hash + Eq + 'static, 187 | { 188 | type Item = T; 189 | 190 | fn next(&mut self) -> Option { 191 | self.inner.next().cloned() 192 | } 193 | } 194 | 195 | impl<'a, T, S> IntoIterator for &'a ConSet 196 | where 197 | T: Clone + Hash + Eq + 'static, 198 | { 199 | type Item = T; 200 | type IntoIter = Iter<'a, T, S>; 201 | 202 | fn into_iter(self) -> Self::IntoIter { 203 | self.iter() 204 | } 205 | } 206 | 207 | impl<'a, T, S> Extend for &'a ConSet 208 | where 209 | T: Clone + Hash + Eq + 'static, 210 | S: BuildHasher, 211 | { 212 | fn extend(&mut self, iter: I) 213 | where 214 | I: IntoIterator, 215 | { 216 | for n in iter { 217 | self.insert(n); 218 | } 219 | } 220 | } 221 | 222 | impl Extend for ConSet 223 | where 224 | T: Clone + Hash + Eq + 'static, 225 | S: BuildHasher, 226 | { 227 | fn extend(&mut self, iter: I) 228 | where 229 | I: IntoIterator, 230 | { 231 | let mut me: &ConSet<_, _> = self; 232 | me.extend(iter); 233 | } 234 | } 235 | 236 | impl FromIterator for ConSet 237 | where 238 | T: Clone + Hash + Eq + 'static, 239 | { 240 | fn from_iter(iter: I) -> Self 241 | where 242 | I: IntoIterator, 243 | { 244 | let mut me = ConSet::new(); 245 | me.extend(iter); 246 | me 247 | } 248 | } 249 | 250 | #[cfg(feature = "rayon")] 251 | impl<'a, T, S> ParallelExtend for &'a ConSet 252 | where 253 | T: Clone + Hash + Eq + Send + Sync, 254 | S: BuildHasher + Sync, 255 | { 256 | fn par_extend(&mut self, par_iter: I) 257 | where 258 | I: IntoParallelIterator, 259 | { 260 | par_iter.into_par_iter().for_each(|n| { 261 | self.insert(n); 262 | }); 263 | } 264 | } 265 | 266 | #[cfg(feature = "rayon")] 267 | impl ParallelExtend for ConSet 268 | where 269 | T: Clone + Hash + Eq + Send + Sync, 270 | S: BuildHasher + Sync, 271 | { 272 | fn par_extend(&mut self, par_iter: I) 273 | where 274 | I: IntoParallelIterator, 275 | { 276 | let mut me: &ConSet<_, _> = self; 277 | me.par_extend(par_iter); 278 | } 279 | } 280 | 281 | #[cfg(feature = "rayon")] 282 | impl FromParallelIterator for ConSet 283 | where 284 | T: Clone + Hash + Eq + Send + Sync, 285 | { 286 | fn from_par_iter(iter: I) -> Self 287 | where 288 | I: IntoParallelIterator, 289 | { 290 | let mut me = ConSet::new(); 291 | me.par_extend(iter); 292 | me 293 | } 294 | } 295 | 296 | #[cfg(test)] 297 | mod tests { 298 | use crossbeam_utils::thread; 299 | #[cfg(feature = "rayon")] 300 | use rayon::prelude::*; 301 | 302 | use super::*; 303 | use crate::raw::tests::NoHasher; 304 | use crate::raw::LEVEL_CELLS; 305 | 306 | const TEST_THREADS: usize = 4; 307 | const TEST_BATCH: usize = 10000; 308 | const TEST_BATCH_SMALL: usize = 100; 309 | const TEST_REP: usize = 20; 310 | 311 | #[test] 312 | fn debug_when_empty() { 313 | let set: ConSet = ConSet::new(); 314 | assert_eq!("{}", &format!("{:?}", set)); 315 | } 316 | 317 | #[test] 318 | fn debug_when_has_elements() { 319 | let set: ConSet<&str> = ConSet::new(); 320 | assert!(set.insert("hello").is_none()); 321 | assert!(set.insert("world").is_none()); 322 | let expected = "{\"hello\", \"world\"}"; 323 | let actual = &format!("{:?}", set); 324 | 325 | let mut expected_chars: Vec = expected.chars().collect(); 326 | expected_chars.sort(); 327 | let mut actual_chars: Vec = actual.chars().collect(); 328 | actual_chars.sort(); 329 | assert_eq!(expected_chars, actual_chars); 330 | } 331 | 332 | #[test] 333 | fn debug_when_elements_are_added_and_removed() { 334 | let set: ConSet<&str> = ConSet::new(); 335 | assert_eq!("{}", &format!("{:?}", set)); 336 | assert!(set.insert("hello").is_none()); 337 | assert!(set.insert("hello").is_some()); 338 | assert!(set.insert("hello").is_some()); 339 | assert_eq!("{\"hello\"}", &format!("{:?}", set)); 340 | assert!(set.remove("hello").is_some()); 341 | assert_eq!("{}", &format!("{:?}", set)); 342 | } 343 | 344 | #[test] 345 | fn create_destroy() { 346 | let set: ConSet = ConSet::new(); 347 | drop(set); 348 | } 349 | 350 | #[test] 351 | fn lookup_empty() { 352 | let set: ConSet = ConSet::new(); 353 | assert!(set.get("hello").is_none()); 354 | } 355 | 356 | #[test] 357 | fn insert_lookup() { 358 | let set = ConSet::new(); 359 | assert!(set.insert("hello").is_none()); 360 | assert!(set.get("world").is_none()); 361 | let found = set.get("hello").unwrap(); 362 | assert_eq!("hello", found); 363 | } 364 | 365 | // Insert a lot of things, to make sure we have multiple levels. 366 | #[test] 367 | fn insert_many() { 368 | let set = ConSet::new(); 369 | for i in 0..TEST_BATCH * LEVEL_CELLS { 370 | assert!(set.insert(i).is_none()); 371 | } 372 | 373 | for i in 0..TEST_BATCH * LEVEL_CELLS { 374 | assert_eq!(i, set.get(&i).unwrap()); 375 | } 376 | } 377 | 378 | #[test] 379 | fn par_insert_many() { 380 | for _ in 0..TEST_REP { 381 | let set: ConSet = ConSet::new(); 382 | thread::scope(|s| { 383 | for t in 0..TEST_THREADS { 384 | let set = &set; 385 | s.spawn(move |_| { 386 | for i in 0..TEST_BATCH { 387 | let num = t * TEST_BATCH + i; 388 | assert!(set.insert(num).is_none()); 389 | } 390 | }); 391 | } 392 | }) 393 | .unwrap(); 394 | 395 | for i in 0..TEST_BATCH * TEST_THREADS { 396 | assert_eq!(set.get(&i).unwrap(), i); 397 | } 398 | } 399 | } 400 | 401 | #[test] 402 | fn par_get_many() { 403 | for _ in 0..TEST_REP { 404 | let set = ConSet::new(); 405 | for i in 0..TEST_BATCH * TEST_THREADS { 406 | assert!(set.insert(i).is_none()); 407 | } 408 | thread::scope(|s| { 409 | for t in 0..TEST_THREADS { 410 | let set = &set; 411 | s.spawn(move |_| { 412 | for i in 0..TEST_BATCH { 413 | let num = t * TEST_BATCH + i; 414 | assert_eq!(set.get(&num).unwrap(), num); 415 | } 416 | }); 417 | } 418 | }) 419 | .unwrap(); 420 | } 421 | } 422 | 423 | #[test] 424 | fn no_collisions() { 425 | let set = ConSet::with_hasher(NoHasher); 426 | // While their hash is the same under the hasher, they don't kick each other out. 427 | for i in 0..TEST_BATCH_SMALL { 428 | assert!(set.insert(i).is_none()); 429 | } 430 | // And all are present. 431 | for i in 0..TEST_BATCH_SMALL { 432 | assert_eq!(i, set.get(&i).unwrap()); 433 | } 434 | // No key kicks another one out. 435 | for i in 0..TEST_BATCH_SMALL { 436 | assert_eq!(i, set.insert(i).unwrap()); 437 | } 438 | } 439 | 440 | #[test] 441 | fn simple_remove() { 442 | let set = ConSet::new(); 443 | assert!(set.remove(&42).is_none()); 444 | assert!(set.insert(42).is_none()); 445 | assert_eq!(42, set.get(&42).unwrap()); 446 | assert_eq!(42, set.remove(&42).unwrap()); 447 | assert!(set.get(&42).is_none()); 448 | assert!(set.is_empty()); 449 | assert!(set.remove(&42).is_none()); 450 | assert!(set.is_empty()); 451 | } 452 | 453 | fn remove_many_inner(mut set: ConSet, len: usize) { 454 | for i in 0..len { 455 | assert!(set.insert(i).is_none()); 456 | } 457 | for i in 0..len { 458 | assert_eq!(i, set.get(&i).unwrap()); 459 | assert_eq!(i, set.remove(&i).unwrap()); 460 | assert!(set.get(&i).is_none()); 461 | set.raw.assert_pruned(); 462 | } 463 | 464 | assert!(set.is_empty()); 465 | } 466 | 467 | #[test] 468 | fn remove_many() { 469 | remove_many_inner(ConSet::new(), TEST_BATCH); 470 | } 471 | 472 | #[test] 473 | fn remove_many_collision() { 474 | remove_many_inner(ConSet::with_hasher(NoHasher), TEST_BATCH_SMALL); 475 | } 476 | 477 | #[test] 478 | fn collision_remove_one_left() { 479 | let mut set = ConSet::with_hasher(NoHasher); 480 | set.insert(1); 481 | set.insert(2); 482 | 483 | set.raw.assert_pruned(); 484 | 485 | assert!(set.remove(&2).is_some()); 486 | set.raw.assert_pruned(); 487 | 488 | assert!(set.remove(&1).is_some()); 489 | 490 | set.raw.assert_pruned(); 491 | assert!(set.is_empty()); 492 | } 493 | 494 | #[test] 495 | fn collision_remove_one_left_with_str() { 496 | let mut set = ConSet::with_hasher(NoHasher); 497 | set.insert("hello"); 498 | set.insert("world"); 499 | 500 | set.raw.assert_pruned(); 501 | 502 | assert!(set.remove("world").is_some()); 503 | set.raw.assert_pruned(); 504 | 505 | assert!(set.remove("hello").is_some()); 506 | 507 | set.raw.assert_pruned(); 508 | assert!(set.is_empty()); 509 | } 510 | 511 | #[test] 512 | fn remove_par() { 513 | let mut set = ConSet::new(); 514 | for i in 0..TEST_THREADS * TEST_BATCH { 515 | set.insert(i); 516 | } 517 | 518 | thread::scope(|s| { 519 | for t in 0..TEST_THREADS { 520 | let set = &set; 521 | s.spawn(move |_| { 522 | for i in 0..TEST_BATCH { 523 | let num = t * TEST_BATCH + i; 524 | let val = set.remove(&num).unwrap(); 525 | assert_eq!(num, val); 526 | assert_eq!(num, val); 527 | } 528 | }); 529 | } 530 | }) 531 | .unwrap(); 532 | 533 | set.raw.assert_pruned(); 534 | assert!(set.is_empty()); 535 | } 536 | 537 | fn iter_test_inner(set: ConSet) { 538 | for i in 0..TEST_BATCH_SMALL { 539 | assert!(set.insert(i).is_none()); 540 | } 541 | 542 | let mut extracted = set.iter().collect::>(); 543 | 544 | extracted.sort(); 545 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 546 | assert_eq!(expected, extracted); 547 | } 548 | 549 | #[test] 550 | fn iter() { 551 | let set = ConSet::new(); 552 | iter_test_inner(set); 553 | } 554 | 555 | #[test] 556 | fn iter_collision() { 557 | let set = ConSet::with_hasher(NoHasher); 558 | iter_test_inner(set); 559 | } 560 | 561 | #[test] 562 | fn collect() { 563 | let set = (0..TEST_BATCH_SMALL).collect::>(); 564 | 565 | let mut extracted = set.iter().collect::>(); 566 | extracted.sort(); 567 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 568 | assert_eq!(expected, extracted); 569 | } 570 | 571 | #[test] 572 | fn par_extend() { 573 | let set = ConSet::new(); 574 | 575 | thread::scope(|s| { 576 | for t in 0..TEST_THREADS { 577 | let mut set = &set; 578 | s.spawn(move |_| { 579 | let start = t * TEST_BATCH_SMALL; 580 | let iter = start..start + TEST_BATCH_SMALL; 581 | set.extend(iter); 582 | }); 583 | } 584 | }) 585 | .unwrap(); 586 | 587 | let mut extracted = set.iter().collect::>(); 588 | 589 | extracted.sort(); 590 | let expected = (0..TEST_THREADS * TEST_BATCH_SMALL).collect::>(); 591 | 592 | assert_eq!(expected, extracted); 593 | } 594 | 595 | #[cfg(feature = "rayon")] 596 | #[test] 597 | fn rayon_extend() { 598 | let mut map = ConSet::new(); 599 | map.par_extend((0..TEST_BATCH_SMALL).into_par_iter()); 600 | 601 | let mut extracted = map.iter().collect::>(); 602 | extracted.par_sort(); 603 | 604 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 605 | assert_eq!(expected, extracted); 606 | } 607 | 608 | #[cfg(feature = "rayon")] 609 | #[test] 610 | fn rayon_from_par_iter() { 611 | let map = ConSet::from_par_iter((0..TEST_BATCH_SMALL).into_par_iter()); 612 | 613 | let mut extracted = map.iter().collect::>(); 614 | extracted.sort(); 615 | 616 | let expected = (0..TEST_BATCH_SMALL).collect::>(); 617 | assert_eq!(expected, extracted); 618 | } 619 | } 620 | -------------------------------------------------------------------------------- /src/tests/acts_like_map.rs: -------------------------------------------------------------------------------- 1 | //! In these tests, we make sure the ConTrie works as a HashMap in single threaded context, and 2 | //! sometimes in multithreaded too. 3 | //! 4 | //! To do that we simply generate a series of inserts, lookups and deletions and try them on both 5 | //! maps. They need to return the same things. 6 | //! 7 | //! Furthermore, each test is run in several instances, with keys in differently sized universe. 8 | //! The small ones likely generate only short hashes, but are more likely to reuse the same value. 9 | 10 | use std::collections::hash_map::RandomState; 11 | use std::collections::HashMap; 12 | use std::fmt::Debug; 13 | use std::hash::{BuildHasher, Hash}; 14 | 15 | use proptest::collection::vec; 16 | use proptest::prelude::*; 17 | 18 | use crate::raw::tests::{MakeSplatHasher, NoHasher}; 19 | use crate::ConMap; 20 | 21 | #[derive(Debug, Clone)] 22 | enum Instruction { 23 | Lookup(K), 24 | Remove(K), 25 | Insert(K, V), 26 | } 27 | 28 | impl Instruction 29 | where 30 | K: Arbitrary + Clone + Debug + Eq + Hash + 'static, 31 | V: Arbitrary + Clone + Debug + PartialEq + 'static, 32 | { 33 | fn strategy() -> impl Strategy { 34 | use Instruction::*; 35 | 36 | prop_oneof![ 37 | any::().prop_map(Lookup), 38 | any::().prop_map(Remove), 39 | any::<(K, V)>().prop_map(|(k, v)| Insert(k, v)), 40 | ] 41 | } 42 | 43 | fn run(instructions: Vec, hasher: H) -> Result<(), TestCaseError> { 44 | use Instruction::*; 45 | 46 | let trie = ConMap::new(); 47 | let mut map = HashMap::with_hasher(hasher); 48 | for ins in instructions { 49 | match ins { 50 | Lookup(key) => { 51 | let expected = map.get(&key); 52 | let found = trie.get(&key); 53 | prop_assert_eq!(expected, found.as_ref().map(|l| l.value())); 54 | } 55 | Remove(key) => { 56 | let expected = map.remove(&key); 57 | let found = trie.remove(&key); 58 | prop_assert_eq!(expected.as_ref(), found.as_ref().map(|l| l.value())); 59 | prop_assert_eq!(map.is_empty(), trie.is_empty()); 60 | } 61 | Insert(key, value) => { 62 | let expected = map.insert(key.clone(), value.clone()); 63 | let found = trie.insert(key, value); 64 | prop_assert_eq!(expected.as_ref(), found.as_ref().map(|l| l.value())); 65 | assert!(!map.is_empty()); 66 | } 67 | } 68 | } 69 | 70 | Ok(()) 71 | } 72 | } 73 | 74 | // TODO: Do the same set of tests with some lousy hasher? One that hashes a bit, but has a lot of 75 | // collisions? 76 | 77 | proptest! { 78 | #[test] 79 | fn small_keys(instructions in vec(Instruction::::strategy(), 1..10_000)) { 80 | Instruction::run(instructions, RandomState::default())?; 81 | } 82 | 83 | #[test] 84 | fn mid_keys_collisions(instructions in vec(Instruction::::strategy(), 1..100)) { 85 | Instruction::run(instructions, NoHasher)?; 86 | } 87 | 88 | #[test] 89 | fn mid_keys_bad_hasher(instructions in vec(Instruction::::strategy(), 1..1_000)) { 90 | Instruction::run(instructions, MakeSplatHasher)?; 91 | } 92 | 93 | #[test] 94 | fn mid_keys(instructions in vec(Instruction::::strategy(), 1..10_000)) { 95 | Instruction::run(instructions, RandomState::default())?; 96 | } 97 | 98 | #[test] 99 | fn large_keys(instructions in vec(Instruction::::strategy(), 1..10_000)) { 100 | Instruction::run(instructions, RandomState::default())?; 101 | } 102 | 103 | #[test] 104 | fn string_keys(instructions in vec(Instruction::::strategy(), 1..100)) { 105 | Instruction::run(instructions, RandomState::default())?; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/tests/acts_like_set.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::RandomState; 2 | use std::collections::HashSet; 3 | use std::hash::{BuildHasher, Hash}; 4 | 5 | use proptest::collection::vec; 6 | use proptest::prelude::*; 7 | use rayon::prelude::*; 8 | 9 | use crate::raw::tests::{MakeSplatHasher, NoHasher}; 10 | use crate::ConSet; 11 | 12 | fn insert_parallel_test< 13 | T: Clone + Hash + Eq + Send + Sync + 'static, 14 | H: BuildHasher + Send + Sync, 15 | >( 16 | values: Vec, 17 | hasher: H, 18 | ) -> Result<(), TestCaseError> { 19 | let set: HashSet<_> = values.iter().cloned().collect(); 20 | let trie = ConSet::with_hasher(hasher); 21 | values.into_par_iter().for_each(|v| { 22 | trie.insert(v); 23 | }); 24 | for v in set { 25 | prop_assert!(trie.contains(&v)); 26 | } 27 | 28 | Ok(()) 29 | } 30 | 31 | proptest! { 32 | #[test] 33 | fn insert_all_large(values in vec(any::(), 1..10_000)) { 34 | // Make them unique 35 | let set: HashSet<_> = values.iter().cloned().collect(); 36 | let trie = ConSet::new(); 37 | for v in values { 38 | trie.insert(v); 39 | } 40 | for v in &trie { 41 | prop_assert!(set.contains(&v)); 42 | } 43 | for v in set { 44 | prop_assert!(trie.get(&v).is_some()); 45 | } 46 | } 47 | 48 | #[test] 49 | fn insert_all_small_parallel(values in vec(any::(), 1..10_000)) { 50 | insert_parallel_test(values, RandomState::default())?; 51 | } 52 | 53 | #[test] 54 | fn insert_all_mid_parallel(values in vec(any::(), 1..10_000)) { 55 | insert_parallel_test(values, RandomState::default())?; 56 | } 57 | 58 | #[test] 59 | fn insert_all_mid_parallel_nohash(values in vec(any::(), 1..100)) { 60 | insert_parallel_test(values, NoHasher)?; 61 | } 62 | 63 | #[test] 64 | fn insert_all_mid_parallel_bad_hasher(values in vec(any::(), 1..1_000)) { 65 | insert_parallel_test(values, MakeSplatHasher)?; 66 | } 67 | 68 | #[test] 69 | fn insert_all_large_parallel(values in vec(any::(), 1..10_000)) { 70 | insert_parallel_test(values, RandomState::default())?; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/tests/compile_fail.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] // Allow the unused structs 2 | 3 | //! Compile fail tests 4 | //! 5 | //! Implemented in a minimal way, as doc tests in a hidden module. 6 | 7 | /// ```compile_fail 8 | /// use std::rc::Rc; 9 | /// 10 | /// use contrie::ConMap; 11 | /// use crossbeam_utils::thread; 12 | /// 13 | /// let map: ConMap> = ConMap::new(); 14 | /// 15 | /// thread::scope(|s| { 16 | /// s.spawn(|_| { 17 | /// drop(map); 18 | /// }); 19 | /// }).unwrap(); 20 | /// ``` 21 | /// 22 | /// Similar one, but with Arc should work fine, though. 23 | /// 24 | /// ``` 25 | /// use std::sync::Arc; 26 | /// 27 | /// use contrie::ConMap; 28 | /// use crossbeam_utils::thread; 29 | /// 30 | /// let map: ConMap> = ConMap::new(); 31 | /// 32 | /// thread::scope(|s| { 33 | /// s.spawn(|_| { 34 | /// drop(map); 35 | /// }); 36 | /// }).unwrap(); 37 | /// ``` 38 | struct ShouldNotBeSend; 39 | 40 | /// ```compile_fail 41 | /// use std::rc::Rc; 42 | /// 43 | /// use contrie::ConMap; 44 | /// use crossbeam_utils::thread; 45 | /// 46 | /// let map: ConMap> = ConMap::new(); 47 | /// 48 | /// thread::scope(|s| { 49 | /// s.spawn(|_| { 50 | /// map.get(&42); 51 | /// }); 52 | /// }).unwrap(); 53 | /// ``` 54 | /// 55 | /// Similar one, but with Arc should work fine, though. 56 | /// 57 | /// ``` 58 | /// use std::sync::Arc; 59 | /// 60 | /// use contrie::ConMap; 61 | /// use crossbeam_utils::thread; 62 | /// 63 | /// let map: ConMap> = ConMap::new(); 64 | /// 65 | /// thread::scope(|s| { 66 | /// s.spawn(|_| { 67 | /// map.get(&42); 68 | /// }); 69 | /// }).unwrap(); 70 | /// ``` 71 | struct ShouldNotSync; 72 | 73 | /// ```compile_fail 74 | /// use std::collections::hash_map::RandomState; 75 | /// 76 | /// use contrie::raw::config::Trivial; 77 | /// use contrie::raw::Raw; 78 | /// 79 | /// let map: Raw, RandomState> = Raw::with_hasher(RandomState::default()); 80 | /// let pin = crossbeam_epoch::pin(); 81 | /// let element = map.get(&42, &pin); 82 | /// drop(map); 83 | /// // Must not outlive the map 84 | /// assert!(element.is_none()); 85 | /// ``` 86 | /// 87 | /// This one should be fine, as we don't drop the map. 88 | /// 89 | /// ``` 90 | /// use std::collections::hash_map::RandomState; 91 | /// 92 | /// use contrie::raw::config::Trivial; 93 | /// use contrie::raw::Raw; 94 | /// 95 | /// let map: Raw, RandomState> = Raw::with_hasher(RandomState::default()); 96 | /// let pin = crossbeam_epoch::pin(); 97 | /// let element = map.get(&42, &pin); 98 | /// assert!(element.is_none()); 99 | /// ``` 100 | struct CantExtendBeyondDestroy; 101 | 102 | /// Dropping map makes it impossible to borrow. 103 | /// 104 | /// ```compile_fail 105 | /// use std::collections::hash_map::RandomState; 106 | /// 107 | /// use contrie::raw::config::Trivial; 108 | /// use contrie::raw::iterator::Iter; 109 | /// use contrie::raw::Raw; 110 | /// 111 | /// let map: Raw, RandomState> = Raw::with_hasher(RandomState::default()); 112 | /// let mut iter = Iter::new(&map); 113 | /// let element = iter.next(); 114 | /// drop(map); 115 | /// // Must not outlive the map 116 | /// assert!(element.is_none()); 117 | /// ``` 118 | /// 119 | /// We are not allowed to drop the iterator either. 120 | /// 121 | /// ```compile_fail 122 | /// use std::collections::hash_map::RandomState; 123 | /// 124 | /// use contrie::raw::config::Trivial; 125 | /// use contrie::raw::iterator::Iter; 126 | /// use contrie::raw::Raw; 127 | /// 128 | /// let map: Raw, RandomState> = Raw::with_hasher(RandomState::default()); 129 | /// let mut iter = Iter::new(&map); 130 | /// let element = iter.next(); 131 | /// drop(iter); 132 | /// // Must not outlive the iterator 133 | /// assert!(element.is_none()); 134 | /// ``` 135 | /// 136 | /// But if we don't drop anything, everything is fine. 137 | /// ``` 138 | /// use std::collections::hash_map::RandomState; 139 | /// 140 | /// use contrie::raw::config::Trivial; 141 | /// use contrie::raw::iterator::Iter; 142 | /// use contrie::raw::Raw; 143 | /// 144 | /// let map: Raw, RandomState> = Raw::with_hasher(RandomState::default()); 145 | /// let mut iter = Iter::new(&map); 146 | /// let element = iter.next(); 147 | /// assert!(element.is_none()); 148 | /// ``` 149 | struct DoesntOutliveIterator; 150 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod acts_like_map; 2 | mod acts_like_set; 3 | mod compile_fail; 4 | -------------------------------------------------------------------------------- /tests/version.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate version_sync; 3 | 4 | #[test] 5 | fn test_readme_deps() { 6 | assert_markdown_deps_updated!("README.md"); 7 | } 8 | 9 | #[test] 10 | fn test_html_root_url() { 11 | assert_html_root_url_updated!("src/lib.rs"); 12 | } 13 | --------------------------------------------------------------------------------