├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── azure-pipelines.yml ├── benchmark ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── plot.r ├── read-throughput.png ├── src │ └── main.rs ├── write-throughput.png └── write-with-refresh.png ├── codecov.yml ├── rustfmt.toml ├── src ├── aliasing.rs ├── inner.rs ├── lib.rs ├── read.rs ├── read │ ├── factory.rs │ └── read_ref.rs ├── stable_hash_eq.rs ├── values.rs └── write.rs └── tests ├── lib.rs └── quick.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "evmap" 3 | version = "11.0.0-alpha.7" 4 | authors = ["Jon Gjengset "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | description = "A lock-free, eventually consistent, concurrent multi-value map." 9 | repository = "https://github.com/jonhoo/evmap.git" 10 | 11 | keywords = ["map","multi-value","lock-free"] 12 | categories = ["concurrency", "data-structures"] 13 | 14 | [badges] 15 | azure-devops = { project = "jonhoo/jonhoo", pipeline = "evmap", build = "30" } 16 | codecov = { repository = "jonhoo/evmap", branch = "master", service = "github" } 17 | maintenance = { status = "passively-maintained" } 18 | 19 | [features] 20 | default = [] 21 | indexed = ["indexmap"] 22 | eviction = ["indexed", "rand"] 23 | amortize = ["indexmap-amortized", "hashbag/amortize"] 24 | 25 | [dependencies] 26 | indexmap = { version = "1.6.1", optional = true } 27 | indexmap-amortized = { version = "1.6.1", optional = true } 28 | smallvec = "1.0.0" 29 | hashbag = "0.1.3" 30 | rand = { version = "0.7", default-features = false, features = ["alloc"], optional = true } 31 | left-right = { version = "0.11.0" } 32 | 33 | [dev-dependencies] 34 | quickcheck = "0.9" 35 | quickcheck_macros = "0.9" 36 | rand = "0.7" 37 | 38 | [package.metadata.docs.rs] 39 | all-features = true 40 | rustdoc-args = ["--cfg", "docsrs"] 41 | -------------------------------------------------------------------------------- /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 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jon Gjengset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://dev.azure.com/jonhoo/jonhoo/_apis/build/status/evmap?branchName=master)](https://dev.azure.com/jonhoo/jonhoo/_build/latest?definitionId=30&branchName=master) 2 | [![Codecov](https://codecov.io/github/jonhoo/evmap/coverage.svg?branch=master)](https://codecov.io/gh/jonhoo/evmap) 3 | [![Crates.io](https://img.shields.io/crates/v/evmap.svg)](https://crates.io/crates/evmap) 4 | [![Documentation](https://docs.rs/evmap/badge.svg)](https://docs.rs/evmap/) 5 | 6 | A lock-free, eventually consistent, concurrent multi-value map. 7 | 8 | This map implementation allows reads and writes to execute entirely in parallel, with no 9 | implicit synchronization overhead. Reads never take locks on their critical path, and neither 10 | do writes assuming there is a single writer (multi-writer is possible using a `Mutex`), which 11 | significantly improves performance under contention. It is backed by the 12 | concurrency primitive [`left-right`](https://crates.io/crates/left-right). 13 | 14 | The trade-off exposed by this module is one of eventual consistency: writes are not visible to 15 | readers except following explicit synchronization. Specifically, readers only see the 16 | operations that preceeded the last call to `WriteHandle::flush` by a writer. This lets 17 | writers decide how stale they are willing to let reads get. They can refresh the map after 18 | every write to emulate a regular concurrent `HashMap`, or they can refresh only occasionally to 19 | reduce the synchronization overhead at the cost of stale reads. 20 | 21 | For read-heavy workloads, the scheme used by this module is particularly useful. Writers can 22 | afford to refresh after every write, which provides up-to-date reads, and readers remain fast 23 | as they do not need to ever take locks. 24 | 25 | The map is multi-value, meaning that every key maps to a *collection* of values. This 26 | introduces some memory cost by adding a layer of indirection through a `Vec` for each value, 27 | but enables more advanced use. This choice was made as it would not be possible to emulate such 28 | functionality on top of the semantics of this map (think about it -- what would the operational 29 | log contain?). 30 | 31 | To facilitate more advanced use-cases, each of the two maps also carry some customizeable 32 | meta-information. The writers may update this at will, and when a refresh happens, the current 33 | meta will also be made visible to readers. This could be useful, for example, to indicate what 34 | time the refresh happened. 35 | 36 | ## Performance 37 | 38 | **These benchmarks are outdated at this point, but communicate the right 39 | point. Hopefully I'll have a chance to update them again some time 40 | soon.** 41 | 42 | I've run some benchmarks of evmap against a standard Rust `HashMap` protected 43 | by a [reader-writer 44 | lock](https://doc.rust-lang.org/std/sync/struct.RwLock.html), as well as 45 | against [chashmap](https://crates.io/crates/chashmap) — a crate which provides 46 | "concurrent hash maps, based on bucket-level multi-reader locks". The 47 | benchmarks were run using the binary in [benchmark/](benchmark/src/main.rs) on 48 | a 40-core machine with Intel(R) Xeon(R) CPU E5-2660 v3 @ 2.60GHz CPUs. 49 | 50 | The benchmark runs a number of reader and writer threads in tight loops, each 51 | of which does a read or write to a random key in the map respectively. Results 52 | for both uniform and skewed distributions are provided below. The benchmark 53 | measures the average number of reads and writes per second as the number of 54 | readers and writers increases. 55 | 56 | Preliminary results show that `evmap` performs well under contention, 57 | especially on the read side. This benchmark represents the worst-case usage of 58 | `evmap` in which every write also does a `refresh`. If the map is refreshed 59 | less often, performance increases (see bottom plot). 60 | 61 | ![Read throughput](benchmark/read-throughput.png) 62 | ![Write throughput](benchmark/write-throughput.png) 63 | ![Write throughput](benchmark/write-with-refresh.png) 64 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - template: default.yml@templates 3 | parameters: 4 | minrust: 1.48.0 5 | codecov_token: $(CODECOV_TOKEN_SECRET) 6 | - job: benchmark 7 | displayName: "Check that benchmark compiles" 8 | pool: 9 | vmImage: ubuntu-latest 10 | steps: 11 | - template: install-rust.yml@templates 12 | - script: cargo check 13 | displayName: cargo check benchmark/ 14 | workingDirectory: "benchmark" 15 | - job: miri 16 | displayName: "Run miri on test suite" 17 | pool: 18 | vmImage: ubuntu-latest 19 | steps: 20 | - bash: | 21 | echo '##vso[task.setvariable variable=nightly]nightly-'$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri) 22 | displayName: "Determine latest miri nightly" 23 | - template: install-rust.yml@templates 24 | parameters: 25 | rust: $(nightly) 26 | components: 27 | - miri 28 | - script: cargo miri setup 29 | displayName: cargo miri setup 30 | - script: cargo miri test 31 | displayName: cargo miri test 32 | env: 33 | QUICKCHECK_TESTS: 500 34 | - job: asan 35 | displayName: "Run address sanitizer on test suite" 36 | pool: 37 | vmImage: ubuntu-latest 38 | steps: 39 | - template: install-rust.yml@templates 40 | parameters: 41 | rust: nightly 42 | - bash: | 43 | sudo ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer 44 | displayName: Enable debug symbols 45 | # only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945 46 | - script: | 47 | env ASAN_OPTIONS="detect_odr_violation=0" RUSTFLAGS="-Z sanitizer=address" cargo test --lib --tests --target x86_64-unknown-linux-gnu 48 | displayName: cargo -Z sanitizer=address test 49 | - job: lsan 50 | displayName: "Run leak sanitizer on test suite" 51 | pool: 52 | vmImage: ubuntu-latest 53 | steps: 54 | - template: install-rust.yml@templates 55 | parameters: 56 | rust: nightly 57 | - bash: | 58 | sudo ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer 59 | sed -i '/\[features\]/i [profile.dev]' Cargo.toml 60 | sed -i '/profile.dev/a opt-level = 1' Cargo.toml 61 | cat Cargo.toml 62 | displayName: Enable debug symbols 63 | - script: | 64 | env RUSTFLAGS="-Z sanitizer=leak" cargo test --target x86_64-unknown-linux-gnu 65 | displayName: cargo -Z sanitizer=leak test 66 | 67 | resources: 68 | repositories: 69 | - repository: templates 70 | type: github 71 | name: crate-ci/azure-pipelines 72 | ref: refs/heads/v0.4 73 | endpoint: jonhoo 74 | -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | results.log 2 | */ 3 | -------------------------------------------------------------------------------- /benchmark/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi", 10 | ] 11 | 12 | [[package]] 13 | name = "atty" 14 | version = "0.2.14" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 17 | dependencies = [ 18 | "hermit-abi", 19 | "libc", 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "1.2.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "0.1.10" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 34 | 35 | [[package]] 36 | name = "chashmap" 37 | version = "2.2.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45" 40 | dependencies = [ 41 | "owning_ref", 42 | "parking_lot 0.4.8", 43 | ] 44 | 45 | [[package]] 46 | name = "clap" 47 | version = "2.33.3" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 50 | dependencies = [ 51 | "ansi_term", 52 | "atty", 53 | "bitflags", 54 | "strsim", 55 | "textwrap", 56 | "unicode-width", 57 | "vec_map", 58 | ] 59 | 60 | [[package]] 61 | name = "cloudabi" 62 | version = "0.0.3" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 65 | dependencies = [ 66 | "bitflags", 67 | ] 68 | 69 | [[package]] 70 | name = "concurrent-map-bench" 71 | version = "0.1.0" 72 | dependencies = [ 73 | "chashmap", 74 | "clap", 75 | "evmap", 76 | "parking_lot 0.10.2", 77 | "rand 0.7.3", 78 | "zipf", 79 | ] 80 | 81 | [[package]] 82 | name = "evmap" 83 | version = "11.0.0-alpha.5" 84 | dependencies = [ 85 | "hashbag", 86 | "left-right", 87 | "smallvec 1.5.1", 88 | ] 89 | 90 | [[package]] 91 | name = "fuchsia-cprng" 92 | version = "0.1.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 95 | 96 | [[package]] 97 | name = "getrandom" 98 | version = "0.1.15" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" 101 | dependencies = [ 102 | "cfg-if", 103 | "libc", 104 | "wasi", 105 | ] 106 | 107 | [[package]] 108 | name = "hashbag" 109 | version = "0.1.3" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "a9be661681f30f8ef0f5bd6a2bff28b16cbcffc0e1b79d18558b74d5e1817fbb" 112 | 113 | [[package]] 114 | name = "hermit-abi" 115 | version = "0.1.17" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" 118 | dependencies = [ 119 | "libc", 120 | ] 121 | 122 | [[package]] 123 | name = "left-right" 124 | version = "0.11.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "d2aff789af956df5f1115e2fbec21b511bdd3a0964b169839b5b23c6f37300b3" 127 | dependencies = [ 128 | "slab", 129 | ] 130 | 131 | [[package]] 132 | name = "libc" 133 | version = "0.2.80" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" 136 | 137 | [[package]] 138 | name = "lock_api" 139 | version = "0.3.4" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 142 | dependencies = [ 143 | "scopeguard", 144 | ] 145 | 146 | [[package]] 147 | name = "maybe-uninit" 148 | version = "2.0.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 151 | 152 | [[package]] 153 | name = "owning_ref" 154 | version = "0.3.3" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" 157 | dependencies = [ 158 | "stable_deref_trait", 159 | ] 160 | 161 | [[package]] 162 | name = "parking_lot" 163 | version = "0.4.8" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" 166 | dependencies = [ 167 | "owning_ref", 168 | "parking_lot_core 0.2.14", 169 | ] 170 | 171 | [[package]] 172 | name = "parking_lot" 173 | version = "0.10.2" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" 176 | dependencies = [ 177 | "lock_api", 178 | "parking_lot_core 0.7.2", 179 | ] 180 | 181 | [[package]] 182 | name = "parking_lot_core" 183 | version = "0.2.14" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" 186 | dependencies = [ 187 | "libc", 188 | "rand 0.4.6", 189 | "smallvec 0.6.13", 190 | "winapi", 191 | ] 192 | 193 | [[package]] 194 | name = "parking_lot_core" 195 | version = "0.7.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" 198 | dependencies = [ 199 | "cfg-if", 200 | "cloudabi", 201 | "libc", 202 | "redox_syscall", 203 | "smallvec 1.5.1", 204 | "winapi", 205 | ] 206 | 207 | [[package]] 208 | name = "ppv-lite86" 209 | version = "0.2.10" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 212 | 213 | [[package]] 214 | name = "rand" 215 | version = "0.4.6" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 218 | dependencies = [ 219 | "fuchsia-cprng", 220 | "libc", 221 | "rand_core 0.3.1", 222 | "rdrand", 223 | "winapi", 224 | ] 225 | 226 | [[package]] 227 | name = "rand" 228 | version = "0.7.3" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 231 | dependencies = [ 232 | "getrandom", 233 | "libc", 234 | "rand_chacha", 235 | "rand_core 0.5.1", 236 | "rand_hc", 237 | ] 238 | 239 | [[package]] 240 | name = "rand_chacha" 241 | version = "0.2.2" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 244 | dependencies = [ 245 | "ppv-lite86", 246 | "rand_core 0.5.1", 247 | ] 248 | 249 | [[package]] 250 | name = "rand_core" 251 | version = "0.3.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 254 | dependencies = [ 255 | "rand_core 0.4.2", 256 | ] 257 | 258 | [[package]] 259 | name = "rand_core" 260 | version = "0.4.2" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 263 | 264 | [[package]] 265 | name = "rand_core" 266 | version = "0.5.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 269 | dependencies = [ 270 | "getrandom", 271 | ] 272 | 273 | [[package]] 274 | name = "rand_hc" 275 | version = "0.2.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 278 | dependencies = [ 279 | "rand_core 0.5.1", 280 | ] 281 | 282 | [[package]] 283 | name = "rdrand" 284 | version = "0.4.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 287 | dependencies = [ 288 | "rand_core 0.3.1", 289 | ] 290 | 291 | [[package]] 292 | name = "redox_syscall" 293 | version = "0.1.57" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 296 | 297 | [[package]] 298 | name = "scopeguard" 299 | version = "1.1.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 302 | 303 | [[package]] 304 | name = "slab" 305 | version = "0.4.2" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 308 | 309 | [[package]] 310 | name = "smallvec" 311 | version = "0.6.13" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" 314 | dependencies = [ 315 | "maybe-uninit", 316 | ] 317 | 318 | [[package]] 319 | name = "smallvec" 320 | version = "1.5.1" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" 323 | 324 | [[package]] 325 | name = "stable_deref_trait" 326 | version = "1.2.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 329 | 330 | [[package]] 331 | name = "strsim" 332 | version = "0.8.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 335 | 336 | [[package]] 337 | name = "textwrap" 338 | version = "0.11.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 341 | dependencies = [ 342 | "unicode-width", 343 | ] 344 | 345 | [[package]] 346 | name = "unicode-width" 347 | version = "0.1.8" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 350 | 351 | [[package]] 352 | name = "vec_map" 353 | version = "0.8.2" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 356 | 357 | [[package]] 358 | name = "wasi" 359 | version = "0.9.0+wasi-snapshot-preview1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 362 | 363 | [[package]] 364 | name = "winapi" 365 | version = "0.3.9" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 368 | dependencies = [ 369 | "winapi-i686-pc-windows-gnu", 370 | "winapi-x86_64-pc-windows-gnu", 371 | ] 372 | 373 | [[package]] 374 | name = "winapi-i686-pc-windows-gnu" 375 | version = "0.4.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 378 | 379 | [[package]] 380 | name = "winapi-x86_64-pc-windows-gnu" 381 | version = "0.4.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 384 | 385 | [[package]] 386 | name = "zipf" 387 | version = "6.1.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "9e12b8667a4fff63d236f8363be54392f93dbb13616be64a83e761a9319ab589" 390 | dependencies = [ 391 | "rand 0.7.3", 392 | ] 393 | -------------------------------------------------------------------------------- /benchmark/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | # don't include in the left-right/evmap workspace 3 | 4 | [package] 5 | name = "concurrent-map-bench" 6 | version = "0.1.0" 7 | authors = ["Jon Gjengset "] 8 | edition = "2018" 9 | publish = false 10 | 11 | [dependencies] 12 | evmap = { path = "../" } 13 | chashmap = "2.1.0" 14 | clap = "2.20.3" 15 | zipf = "6" 16 | rand = "0.7" 17 | parking_lot = "0.10.1" 18 | 19 | [profile.release] 20 | lto = true 21 | opt-level = 3 22 | codegen-units = 1 23 | debug = false 24 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | To reproduce results, run with: 2 | 3 | ```shell 4 | cargo build --release 5 | rm results.log 6 | for w in 1 2 4; do 7 | for d in uniform skewed; do 8 | for r in 1 2 4 8 16 32; do 9 | target/release/concurrent-map-bench -r $r -w $w -d $d -c | tee -a results.log; 10 | done; 11 | done; 12 | done 13 | for e in 2 4 8 16 32 64 128 256 512 1024; do 14 | for d in uniform skewed; do 15 | target/release/concurrent-map-bench -r 1 -w 1 -d $d -e $e | tee -a results.log; 16 | done; 17 | done 18 | R -q --no-readline --no-restore --no-save < plot.r 19 | ``` 20 | -------------------------------------------------------------------------------- /benchmark/plot.r: -------------------------------------------------------------------------------- 1 | v = read.table(file("results.log")) 2 | t <- data.frame(readers=v[,1], writers=v[,2], distribution=v[,3], variant=v[,4], opss=v[,5], op=v[,7]) 3 | 4 | library(plyr) 5 | t$writers = as.factor(t$writers) 6 | t$readers = as.numeric(t$readers) 7 | 8 | # split the data into tx/readers and tx/refresh 9 | compare_impl = t[grep("evmap-refresh", t$variant, invert = TRUE),] 10 | compare_rate = t[grep("evmap-refresh", t$variant, invert = FALSE),] 11 | compare_rate = rbind(compare_rate, compare_impl[compare_impl$variant == "evmap",]) 12 | 13 | r = compare_impl[compare_impl$op == "read",] 14 | r <- ddply(r, c("readers", "writers", "distribution", "variant", "op"), summarise, opss = sum(opss)) 15 | w = compare_impl[compare_impl$op == "write",] 16 | w <- ddply(w, c("readers", "writers", "distribution", "variant", "op"), summarise, opss = sum(opss)) 17 | 18 | library(ggplot2) 19 | 20 | 21 | r$opss = r$opss / 1000000.0 22 | p <- ggplot(data=r, aes(x=readers, y=opss, color=variant)) 23 | #p <- p + ylim(c(0, 2500)) 24 | p <- p + xlim(c(0, NA)) 25 | p <- p + facet_grid(distribution ~ writers, labeller = labeller(writers = label_both)) 26 | p <- p + geom_point(size = .4, alpha = .1) 27 | p <- p + geom_line(size = .5) 28 | #p <- p + stat_smooth(size = .5, se = FALSE) 29 | p <- p + xlab("readers") + ylab("M reads/s") + ggtitle("Total reads/s with increasing # of readers") 30 | ggsave('read-throughput.png',plot=p,width=10,height=6) 31 | 32 | 33 | w$opss = w$opss / 1000000.0 34 | p <- ggplot(data=w, aes(x=readers, y=opss, color=variant)) 35 | #p <- p + scale_y_log10(lim=c(1, NA))#5000)) 36 | p <- p + facet_grid(distribution ~ writers, labeller = labeller(writers = label_both)) 37 | p <- p + geom_point(size = 1, alpha = .2) 38 | p <- p + geom_line(size = .5) 39 | #p <- p + stat_smooth(size = .5, se = FALSE) 40 | #p <- p + coord_cartesian(ylim=c(0,250)) 41 | p <- p + xlim(c(0, NA)) 42 | p <- p + xlab("readers") + ylab("M writes/s") + ggtitle("Total writes/s with increasing # of readers") 43 | ggsave('write-throughput.png',plot=p,width=10,height=6) 44 | 45 | 46 | library(scales) 47 | 48 | w = compare_rate 49 | w <- w[w$writers == 1,] 50 | w <- w[w$readers == 1,] 51 | w <- w[w$op == "write",] 52 | w$variant = gsub("^evmap$", "evmap-refresh1", w$variant, perl = TRUE) 53 | w$period = as.numeric(gsub("evmap-refresh([\\d]+)", "\\1", w$variant, perl = TRUE)) 54 | w$variant = gsub("evmap-refresh[\\d]+", "evmap", w$variant, perl = TRUE) 55 | w$opss = w$opss / 1000000.0 56 | p <- ggplot(data=w, aes(x=period, y=opss, color=distribution)) 57 | p <- p + geom_point(size = 1, alpha = .2) 58 | p <- p + geom_line(size = .5) 59 | p <- p + scale_x_continuous(trans="log2", 60 | breaks = trans_breaks("log2", function(x) 2^x), 61 | labels = trans_format("log2", math_format(2^.x))) 62 | p <- p + xlab("refresh every N writes") + ylab("M writes/s") + ggtitle("Total writes/s with decreasing refresh frequency") 63 | ggsave('write-with-refresh.png',plot=p,width=10,height=6) 64 | -------------------------------------------------------------------------------- /benchmark/read-throughput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonhoo/evmap/ca57886a718a9209a1d336ca7dd3909362aa8e9e/benchmark/read-throughput.png -------------------------------------------------------------------------------- /benchmark/src/main.rs: -------------------------------------------------------------------------------- 1 | use chashmap::CHashMap; 2 | use clap::{crate_version, value_t}; 3 | use clap::{App, Arg}; 4 | use rand::prelude::*; 5 | use std::collections::HashMap; 6 | use std::sync; 7 | use std::thread; 8 | use std::time; 9 | 10 | fn main() { 11 | let matches = App::new("Concurrent HashMap Benchmarker") 12 | .version(crate_version!()) 13 | .author("Jon Gjengset ") 14 | .about( 15 | "Benchmark multiple implementations of concurrent HashMaps with varying read/write load", 16 | ) 17 | .arg( 18 | Arg::with_name("readers") 19 | .short("r") 20 | .long("readers") 21 | .help("Set the number of readers") 22 | .required(true) 23 | .takes_value(true), 24 | ) 25 | .arg( 26 | Arg::with_name("compare") 27 | .short("c") 28 | .help("Also benchmark Arc> and CHashMap"), 29 | ) 30 | .arg( 31 | Arg::with_name("eventual") 32 | .short("e") 33 | .takes_value(true) 34 | .default_value("1") 35 | .value_name("N") 36 | .help("Refresh evmap every N writes"), 37 | ) 38 | .arg( 39 | Arg::with_name("writers") 40 | .short("w") 41 | .long("writers") 42 | .required(true) 43 | .help("Set the number of writers") 44 | .takes_value(true), 45 | ) 46 | .arg( 47 | Arg::with_name("distribution") 48 | .short("d") 49 | .long("dist") 50 | .possible_values(&["uniform", "skewed"]) 51 | .default_value("uniform") 52 | .help("Set the distribution for reads and writes") 53 | .takes_value(true), 54 | ) 55 | .get_matches(); 56 | 57 | let refresh = value_t!(matches, "eventual", usize).unwrap_or_else(|e| e.exit()); 58 | let readers = value_t!(matches, "readers", usize).unwrap_or_else(|e| e.exit()); 59 | let writers = value_t!(matches, "writers", usize).unwrap_or_else(|e| e.exit()); 60 | let dist = matches.value_of("distribution").unwrap_or("uniform"); 61 | let dur = time::Duration::from_secs(5); 62 | let dur_in_ns = dur.as_secs() * 1_000_000_000_u64 + dur.subsec_nanos() as u64; 63 | let dur_in_s = dur_in_ns as f64 / 1_000_000_000_f64; 64 | let span = 10000; 65 | 66 | let stat = |var: &str, op, results: Vec<(_, usize)>| { 67 | for (i, res) in results.into_iter().enumerate() { 68 | println!( 69 | "{:2} {:2} {:10} {:10} {:8.0} ops/s {} {}", 70 | readers, 71 | writers, 72 | dist, 73 | var, 74 | res.1 as f64 / dur_in_s as f64, 75 | op, 76 | i 77 | ) 78 | } 79 | }; 80 | 81 | let mut join = Vec::with_capacity(readers + writers); 82 | // first, benchmark Arc> 83 | if matches.is_present("compare") { 84 | let map: HashMap = HashMap::with_capacity(5_000_000); 85 | let map = sync::Arc::new(parking_lot::RwLock::new(map)); 86 | let start = time::Instant::now(); 87 | let end = start + dur; 88 | join.extend((0..readers).into_iter().map(|_| { 89 | let map = map.clone(); 90 | let dist = dist.to_owned(); 91 | thread::spawn(move || drive(map, end, dist, false, span)) 92 | })); 93 | join.extend((0..writers).into_iter().map(|_| { 94 | let map = map.clone(); 95 | let dist = dist.to_owned(); 96 | thread::spawn(move || drive(map, end, dist, true, span)) 97 | })); 98 | let (wres, rres): (Vec<_>, _) = join 99 | .drain(..) 100 | .map(|jh| jh.join().unwrap()) 101 | .partition(|&(write, _)| write); 102 | stat("std", "write", wres); 103 | stat("std", "read", rres); 104 | } 105 | 106 | // then, benchmark Arc 107 | if matches.is_present("compare") { 108 | let map: CHashMap = CHashMap::with_capacity(5_000_000); 109 | let map = sync::Arc::new(map); 110 | let start = time::Instant::now(); 111 | let end = start + dur; 112 | join.extend((0..readers).into_iter().map(|_| { 113 | let map = map.clone(); 114 | let dist = dist.to_owned(); 115 | thread::spawn(move || drive(map, end, dist, false, span)) 116 | })); 117 | join.extend((0..writers).into_iter().map(|_| { 118 | let map = map.clone(); 119 | let dist = dist.to_owned(); 120 | thread::spawn(move || drive(map, end, dist, true, span)) 121 | })); 122 | let (wres, rres): (Vec<_>, _) = join 123 | .drain(..) 124 | .map(|jh| jh.join().unwrap()) 125 | .partition(|&(write, _)| write); 126 | stat("chashmap", "write", wres); 127 | stat("chashmap", "read", rres); 128 | } 129 | 130 | // finally, benchmark evmap 131 | { 132 | let (w, r) = evmap::Options::default() 133 | .with_capacity(5_000_000) 134 | .construct(); 135 | let w = sync::Arc::new(parking_lot::Mutex::new((w, 0, refresh))); 136 | let start = time::Instant::now(); 137 | let end = start + dur; 138 | join.extend((0..readers).into_iter().map(|_| { 139 | let map = EvHandle::Read(r.clone()); 140 | let dist = dist.to_owned(); 141 | thread::spawn(move || drive(map, end, dist, false, span)) 142 | })); 143 | join.extend((0..writers).into_iter().map(|_| { 144 | let map = EvHandle::Write(w.clone()); 145 | let dist = dist.to_owned(); 146 | thread::spawn(move || drive(map, end, dist, true, span)) 147 | })); 148 | let (wres, rres): (Vec<_>, _) = join 149 | .drain(..) 150 | .map(|jh| jh.join().unwrap()) 151 | .partition(|&(write, _)| write); 152 | 153 | let n = if refresh == 1 { 154 | "evmap".to_owned() 155 | } else { 156 | format!("evmap-refresh{}", refresh) 157 | }; 158 | stat(&n, "write", wres); 159 | stat(&n, "read", rres); 160 | } 161 | } 162 | 163 | trait Backend { 164 | fn b_get(&self, key: usize) -> usize; 165 | fn b_put(&mut self, key: usize, value: usize); 166 | } 167 | 168 | fn drive( 169 | mut backend: B, 170 | end: time::Instant, 171 | dist: String, 172 | write: bool, 173 | span: usize, 174 | ) -> (bool, usize) { 175 | let mut ops = 0; 176 | let skewed = dist == "skewed"; 177 | let mut t_rng = rand::thread_rng(); 178 | let zipf = zipf::ZipfDistribution::new(span, 1.03).unwrap(); 179 | while time::Instant::now() < end { 180 | // generate both so that overhead is always the same 181 | let id_uniform: usize = t_rng.gen_range(0, span as usize); 182 | let id_skewed: usize = zipf.sample(&mut t_rng); 183 | let id = if skewed { id_skewed } else { id_uniform }; 184 | if write { 185 | backend.b_put(id, t_rng.gen()); 186 | } else { 187 | backend.b_get(id); 188 | } 189 | ops += 1; 190 | } 191 | 192 | (write, ops) 193 | } 194 | 195 | impl Backend for sync::Arc> { 196 | fn b_get(&self, key: usize) -> usize { 197 | self.get(&key).map(|v| *v).unwrap_or(0) 198 | } 199 | 200 | fn b_put(&mut self, key: usize, value: usize) { 201 | self.insert(key, value); 202 | } 203 | } 204 | 205 | impl Backend for sync::Arc>> { 206 | fn b_get(&self, key: usize) -> usize { 207 | self.read().get(&key).map(|&v| v).unwrap_or(0) 208 | } 209 | 210 | fn b_put(&mut self, key: usize, value: usize) { 211 | self.write().insert(key, value); 212 | } 213 | } 214 | 215 | enum EvHandle { 216 | Read(evmap::handles::ReadHandle), 217 | Write(sync::Arc, usize, usize)>>), 218 | } 219 | 220 | impl Backend for EvHandle { 221 | fn b_get(&self, key: usize) -> usize { 222 | if let EvHandle::Read(ref r) = *self { 223 | r.get(&key) 224 | .map(|v| v.iter().next().cloned().unwrap()) 225 | .unwrap_or(0) 226 | } else { 227 | unreachable!(); 228 | } 229 | } 230 | 231 | fn b_put(&mut self, key: usize, value: usize) { 232 | if let EvHandle::Write(ref w) = *self { 233 | let mut w = w.lock(); 234 | w.0.update(key, value); 235 | w.1 += 1; 236 | if w.1 == w.2 { 237 | w.1 = 0; 238 | w.0.publish(); 239 | } 240 | } else { 241 | unreachable!(); 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /benchmark/write-throughput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonhoo/evmap/ca57886a718a9209a1d336ca7dd3909362aa8e9e/benchmark/write-throughput.png -------------------------------------------------------------------------------- /benchmark/write-with-refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonhoo/evmap/ca57886a718a9209a1d336ca7dd3909362aa8e9e/benchmark/write-with-refresh.png -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # Hold ourselves to a high bar 2 | coverage: 3 | range: 80..100 4 | round: down 5 | precision: 2 6 | status: 7 | project: 8 | default: 9 | threshold: 1% 10 | 11 | # Tests aren't important for coverage 12 | ignore: 13 | - "tests" 14 | 15 | # Make less noisy comments 16 | comment: 17 | layout: "files" 18 | require_changes: yes 19 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | -------------------------------------------------------------------------------- /src/aliasing.rs: -------------------------------------------------------------------------------- 1 | //! The types in this module _cannot_ be public, and you must _never_ implement a trait for one but 2 | //! not the other. 3 | //! 4 | //! See the module-level documentation on [`left_right::aliasing`] for details. 5 | 6 | use left_right::aliasing::DropBehavior; 7 | 8 | /// If you can see this type outside of `evmap`, that is a severe unsoundness bug. 9 | /// Please report it at https://github.com/jonhoo/rust-evmap/issues. 10 | #[allow(missing_debug_implementations)] 11 | // NOTE: This type is pub so that we can expose it in a couple of trait bounds of the form: 12 | // 13 | // Aliased: Trait 14 | // 15 | // However, it remains private because this module is private. 16 | pub struct NoDrop; 17 | 18 | pub(crate) struct DoDrop; 19 | 20 | impl DropBehavior for NoDrop { 21 | const DO_DROP: bool = false; 22 | } 23 | impl DropBehavior for DoDrop { 24 | const DO_DROP: bool = true; 25 | } 26 | -------------------------------------------------------------------------------- /src/inner.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::hash::{BuildHasher, Hash}; 3 | 4 | #[cfg(all(feature = "indexed", not(feature = "amortize")))] 5 | pub(crate) use indexmap::{map::Entry, IndexMap as MapImpl}; 6 | #[cfg(feature = "amortize")] 7 | pub(crate) use indexmap_amortized::{map::Entry, IndexMap as MapImpl}; 8 | #[cfg(not(any(feature = "indexed", feature = "amortize")))] 9 | pub(crate) use std::collections::{hash_map::Entry, HashMap as MapImpl}; 10 | 11 | use crate::values::ValuesInner; 12 | use left_right::aliasing::DropBehavior; 13 | 14 | pub(crate) struct Inner 15 | where 16 | K: Eq + Hash, 17 | S: BuildHasher, 18 | D: DropBehavior, 19 | { 20 | pub(crate) data: MapImpl, S>, 21 | pub(crate) meta: M, 22 | pub(crate) ready: bool, 23 | } 24 | 25 | impl fmt::Debug for Inner 26 | where 27 | K: Eq + Hash + fmt::Debug, 28 | S: BuildHasher, 29 | V: fmt::Debug, 30 | M: fmt::Debug, 31 | { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | f.debug_struct("Inner") 34 | .field("data", &self.data) 35 | .field("meta", &self.meta) 36 | .field("ready", &self.ready) 37 | .finish() 38 | } 39 | } 40 | 41 | impl Clone for Inner 42 | where 43 | K: Eq + Hash + Clone, 44 | S: BuildHasher + Clone, 45 | M: Clone, 46 | { 47 | fn clone(&self) -> Self { 48 | assert!(self.data.is_empty()); 49 | Inner { 50 | data: MapImpl::with_capacity_and_hasher( 51 | self.data.capacity(), 52 | self.data.hasher().clone(), 53 | ), 54 | meta: self.meta.clone(), 55 | ready: self.ready, 56 | } 57 | } 58 | } 59 | 60 | impl Inner 61 | where 62 | K: Eq + Hash, 63 | S: BuildHasher, 64 | { 65 | pub(crate) fn with_hasher(m: M, hash_builder: S) -> Self { 66 | Inner { 67 | data: MapImpl::with_hasher(hash_builder), 68 | meta: m, 69 | ready: false, 70 | } 71 | } 72 | 73 | pub(crate) fn with_capacity_and_hasher(m: M, capacity: usize, hash_builder: S) -> Self { 74 | Inner { 75 | data: MapImpl::with_capacity_and_hasher(capacity, hash_builder), 76 | meta: m, 77 | ready: false, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A lock-free, eventually consistent, concurrent multi-value map. 2 | //! 3 | //! This map implementation allows reads and writes to execute entirely in parallel, with no 4 | //! implicit synchronization overhead. Reads never take locks on their critical path, and neither 5 | //! do writes assuming there is a single writer (multi-writer is possible using a `Mutex`), which 6 | //! significantly improves performance under contention. See the [`left-right` crate](left_right) 7 | //! for details on the underlying concurrency primitive. 8 | //! 9 | //! The trade-off exposed by this type is one of eventual consistency: writes are not visible to 10 | //! readers except following explicit synchronization. Specifically, readers only see the 11 | //! operations that preceeded the last call to `WriteHandle::refresh` by a writer. This lets 12 | //! writers decide how stale they are willing to let reads get. They can refresh the map after 13 | //! every write to emulate a regular concurrent `HashMap`, or they can refresh only occasionally to 14 | //! reduce the synchronization overhead at the cost of stale reads. 15 | //! 16 | //! For read-heavy workloads, the scheme used by this module is particularly useful. Writers can 17 | //! afford to refresh after every write, which provides up-to-date reads, and readers remain fast 18 | //! as they do not need to ever take locks. 19 | //! 20 | //! The map is multi-value, meaning that every key maps to a *collection* of values. This 21 | //! introduces some memory cost by adding a layer of indirection through a `Vec` for each value, 22 | //! but enables more advanced use. This choice was made as it would not be possible to emulate such 23 | //! functionality on top of the semantics of this map (think about it -- what would the operational 24 | //! log contain?). 25 | //! 26 | //! To faciliate more advanced use-cases, each of the two maps also carry some customizeable 27 | //! meta-information. The writers may update this at will, and when a refresh happens, the current 28 | //! meta will also be made visible to readers. This could be useful, for example, to indicate what 29 | //! time the refresh happened. 30 | //! 31 | //! # Features 32 | //! 33 | //! - `eviction`: Gives you access to [`WriteHandle::empty_random`] to empty out randomly chosen 34 | //! keys from the map. 35 | //! - `amortize`: Amortizes the cost of resizes in the underlying data structures. See 36 | //! [`griddle`](https://github.com/jonhoo/griddle/) and 37 | //! [`atone`](https://github.com/jonhoo/atone/) for details. This requires a nightly compiler 38 | //! [for the time being](https://docs.rs/indexmap-amortized/1.0/indexmap_amortized/#rust-version). 39 | //! 40 | //! 41 | //! # Examples 42 | //! 43 | //! Single-reader, single-writer 44 | //! 45 | //! ``` 46 | //! // new will use the default HashMap hasher, and a meta of () 47 | //! // note that we get separate read and write handles 48 | //! // the read handle can be cloned to have more readers 49 | //! let (mut book_reviews_w, book_reviews_r) = evmap::new(); 50 | //! 51 | //! // review some books. 52 | //! book_reviews_w.insert("Adventures of Huckleberry Finn", "My favorite book."); 53 | //! book_reviews_w.insert("Grimms' Fairy Tales", "Masterpiece."); 54 | //! book_reviews_w.insert("Pride and Prejudice", "Very enjoyable."); 55 | //! book_reviews_w.insert("The Adventures of Sherlock Holmes", "Eye lyked it alot."); 56 | //! 57 | //! // at this point, reads from book_reviews_r will not see any of the reviews! 58 | //! assert_eq!(book_reviews_r.len(), 0); 59 | //! // we need to refresh first to make the writes visible 60 | //! book_reviews_w.publish(); 61 | //! assert_eq!(book_reviews_r.len(), 4); 62 | //! // reads will now return Some() because the map has been initialized 63 | //! assert_eq!(book_reviews_r.get("Grimms' Fairy Tales").map(|rs| rs.len()), Some(1)); 64 | //! 65 | //! // remember, this is a multi-value map, so we can have many reviews 66 | //! book_reviews_w.insert("Grimms' Fairy Tales", "Eh, the title seemed weird."); 67 | //! book_reviews_w.insert("Pride and Prejudice", "Too many words."); 68 | //! 69 | //! // but again, new writes are not yet visible 70 | //! assert_eq!(book_reviews_r.get("Grimms' Fairy Tales").map(|rs| rs.len()), Some(1)); 71 | //! 72 | //! // we need to refresh first 73 | //! book_reviews_w.publish(); 74 | //! assert_eq!(book_reviews_r.get("Grimms' Fairy Tales").map(|rs| rs.len()), Some(2)); 75 | //! 76 | //! // oops, this review has a lot of spelling mistakes, let's delete it. 77 | //! // remove_entry deletes *all* reviews (though in this case, just one) 78 | //! book_reviews_w.remove_entry("The Adventures of Sherlock Holmes"); 79 | //! // but again, it's not visible to readers until we refresh 80 | //! assert_eq!(book_reviews_r.get("The Adventures of Sherlock Holmes").map(|rs| rs.len()), Some(1)); 81 | //! book_reviews_w.publish(); 82 | //! assert_eq!(book_reviews_r.get("The Adventures of Sherlock Holmes").map(|rs| rs.len()), None); 83 | //! 84 | //! // look up the values associated with some keys. 85 | //! let to_find = ["Pride and Prejudice", "Alice's Adventure in Wonderland"]; 86 | //! for book in &to_find { 87 | //! if let Some(reviews) = book_reviews_r.get(book) { 88 | //! for review in &*reviews { 89 | //! println!("{}: {}", book, review); 90 | //! } 91 | //! } else { 92 | //! println!("{} is unreviewed.", book); 93 | //! } 94 | //! } 95 | //! 96 | //! // iterate over everything. 97 | //! for (book, reviews) in &book_reviews_r.enter().unwrap() { 98 | //! for review in reviews { 99 | //! println!("{}: \"{}\"", book, review); 100 | //! } 101 | //! } 102 | //! ``` 103 | //! 104 | //! Reads from multiple threads are possible by cloning the `ReadHandle`. 105 | //! 106 | //! ``` 107 | //! use std::thread; 108 | //! let (mut book_reviews_w, book_reviews_r) = evmap::new(); 109 | //! 110 | //! // start some readers 111 | //! let readers: Vec<_> = (0..4).map(|_| { 112 | //! let r = book_reviews_r.clone(); 113 | //! thread::spawn(move || { 114 | //! loop { 115 | //! let l = r.len(); 116 | //! if l == 0 { 117 | //! thread::yield_now(); 118 | //! } else { 119 | //! // the reader will either see all the reviews, 120 | //! // or none of them, since refresh() is atomic. 121 | //! assert_eq!(l, 4); 122 | //! break; 123 | //! } 124 | //! } 125 | //! }) 126 | //! }).collect(); 127 | //! 128 | //! // do some writes 129 | //! book_reviews_w.insert("Adventures of Huckleberry Finn", "My favorite book."); 130 | //! book_reviews_w.insert("Grimms' Fairy Tales", "Masterpiece."); 131 | //! book_reviews_w.insert("Pride and Prejudice", "Very enjoyable."); 132 | //! book_reviews_w.insert("The Adventures of Sherlock Holmes", "Eye lyked it alot."); 133 | //! // expose the writes 134 | //! book_reviews_w.publish(); 135 | //! 136 | //! // you can read through the write handle 137 | //! assert_eq!(book_reviews_w.len(), 4); 138 | //! 139 | //! // the original read handle still works too 140 | //! assert_eq!(book_reviews_r.len(), 4); 141 | //! 142 | //! // all the threads should eventually see .len() == 4 143 | //! for r in readers.into_iter() { 144 | //! assert!(r.join().is_ok()); 145 | //! } 146 | //! ``` 147 | //! 148 | //! If multiple writers are needed, the `WriteHandle` must be protected by a `Mutex`. 149 | //! 150 | //! ``` 151 | //! use std::thread; 152 | //! use std::sync::{Arc, Mutex}; 153 | //! let (mut book_reviews_w, book_reviews_r) = evmap::new(); 154 | //! 155 | //! // start some writers. 156 | //! // since evmap does not support concurrent writes, we need 157 | //! // to protect the write handle by a mutex. 158 | //! let w = Arc::new(Mutex::new(book_reviews_w)); 159 | //! let writers: Vec<_> = (0..4).map(|i| { 160 | //! let w = w.clone(); 161 | //! thread::spawn(move || { 162 | //! let mut w = w.lock().unwrap(); 163 | //! w.insert(i, true); 164 | //! w.publish(); 165 | //! }) 166 | //! }).collect(); 167 | //! 168 | //! // eventually we should see all the writes 169 | //! while book_reviews_r.len() < 4 { thread::yield_now(); }; 170 | //! 171 | //! // all the threads should eventually finish writing 172 | //! for w in writers.into_iter() { 173 | //! assert!(w.join().is_ok()); 174 | //! } 175 | //! ``` 176 | //! 177 | //! [`ReadHandle`] is not `Sync` as sharing a single instance amongst threads would introduce a 178 | //! significant performance bottleneck. A fresh `ReadHandle` needs to be created for each thread 179 | //! either by cloning a [`ReadHandle`] or from a [`handles::ReadHandleFactory`]. For further 180 | //! information, see [`left_right::ReadHandle`]. 181 | //! 182 | //! # Implementation 183 | //! 184 | //! Under the hood, the map is implemented using two regular `HashMap`s and some magic. Take a look 185 | //! at [`left-right`](left_right) for a much more in-depth discussion. Since the implementation 186 | //! uses regular `HashMap`s under the hood, table resizing is fully supported. It does, however, 187 | //! also mean that the memory usage of this implementation is approximately twice of that of a 188 | //! regular `HashMap`, and more if writers rarely refresh after writing. 189 | //! 190 | //! # Value storage 191 | //! 192 | //! The values for each key in the map are stored in [`refs::Values`]. Conceptually, each `Values` 193 | //! is a _bag_ or _multiset_; it can store multiple copies of the same value. `evmap` applies some 194 | //! cleverness in an attempt to reduce unnecessary allocations and keep the cost of operations on 195 | //! even large value-bags small. For small bags, `Values` uses the `smallvec` crate. This avoids 196 | //! allocation entirely for single-element bags, and uses a `Vec` if the bag is relatively small. 197 | //! For large bags, `Values` uses the `hashbag` crate, which enables `evmap` to efficiently look up 198 | //! and remove specific elements in the value bag. For bags larger than one element, but smaller 199 | //! than the threshold for moving to `hashbag`, we use `smallvec` to avoid unnecessary hashing. 200 | //! Operations such as `Fit` and `Replace` will automatically switch back to the inline storage if 201 | //! possible. This is ideal for maps that mostly use one element per key, as it can improvate 202 | //! memory locality with less indirection. 203 | #![warn( 204 | missing_docs, 205 | rust_2018_idioms, 206 | missing_debug_implementations, 207 | broken_intra_doc_links 208 | )] 209 | #![allow(clippy::type_complexity)] 210 | // This _should_ detect if we ever accidentally leak aliasing::NoDrop. 211 | // But, currently, it does not.. 212 | #![deny(unreachable_pub)] 213 | #![cfg_attr(docsrs, feature(doc_cfg))] 214 | 215 | use crate::inner::Inner; 216 | use crate::read::ReadHandle; 217 | use crate::write::WriteHandle; 218 | use left_right::aliasing::Aliased; 219 | use std::collections::hash_map::RandomState; 220 | use std::fmt; 221 | use std::hash::{BuildHasher, Hash}; 222 | 223 | mod inner; 224 | mod read; 225 | mod stable_hash_eq; 226 | mod values; 227 | mod write; 228 | 229 | pub use stable_hash_eq::StableHashEq; 230 | 231 | /// Handles to the read and write halves of an `evmap`. 232 | pub mod handles { 233 | pub use crate::write::WriteHandle; 234 | 235 | // These cannot use ::{..} syntax because of 236 | // https://github.com/rust-lang/rust/issues/57411 237 | pub use crate::read::ReadHandle; 238 | pub use crate::read::ReadHandleFactory; 239 | } 240 | 241 | /// Helper types that give access to values inside the read half of an `evmap`. 242 | pub mod refs { 243 | // Same here, ::{..} won't work. 244 | pub use super::values::Values; 245 | pub use crate::read::MapReadRef; 246 | pub use crate::read::ReadGuardIter; 247 | 248 | // Expose `ReadGuard` since it has useful methods the user will likely care about. 249 | #[doc(inline)] 250 | pub use left_right::ReadGuard; 251 | } 252 | 253 | // NOTE: It is _critical_ that this module is not public. 254 | mod aliasing; 255 | 256 | /// Options for how to initialize the map. 257 | /// 258 | /// In particular, the options dictate the hashing function, meta type, and initial capacity of the 259 | /// map. 260 | pub struct Options 261 | where 262 | S: BuildHasher, 263 | { 264 | meta: M, 265 | hasher: S, 266 | capacity: Option, 267 | } 268 | 269 | impl fmt::Debug for Options 270 | where 271 | S: BuildHasher, 272 | M: fmt::Debug, 273 | { 274 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 275 | f.debug_struct("Options") 276 | .field("meta", &self.meta) 277 | .field("capacity", &self.capacity) 278 | .finish() 279 | } 280 | } 281 | 282 | impl Default for Options<(), RandomState> { 283 | fn default() -> Self { 284 | Options { 285 | meta: (), 286 | hasher: RandomState::default(), 287 | capacity: None, 288 | } 289 | } 290 | } 291 | 292 | impl Options 293 | where 294 | S: BuildHasher, 295 | { 296 | /// Set the initial meta value for the map. 297 | pub fn with_meta(self, meta: M2) -> Options { 298 | Options { 299 | meta, 300 | hasher: self.hasher, 301 | capacity: self.capacity, 302 | } 303 | } 304 | 305 | /// Set the hasher used for the map. 306 | /// 307 | /// # Safety 308 | /// 309 | /// This method is safe to call as long as the given hasher is deterministic. That is, it must 310 | /// yield the same hash if given the same sequence of inputs. 311 | pub unsafe fn with_hasher(self, hash_builder: S2) -> Options 312 | where 313 | S2: BuildHasher + Clone, 314 | { 315 | Options { 316 | meta: self.meta, 317 | hasher: hash_builder, 318 | capacity: self.capacity, 319 | } 320 | } 321 | 322 | /// Set the initial capacity for the map. 323 | pub fn with_capacity(self, capacity: usize) -> Options { 324 | Options { 325 | meta: self.meta, 326 | hasher: self.hasher, 327 | capacity: Some(capacity), 328 | } 329 | } 330 | 331 | /// Create the map, and construct the read and write handles used to access it. 332 | /// 333 | /// If you want to use arbitrary types for the keys and values, use [`assert_stable`][Options::assert_stable]. 334 | #[allow(clippy::type_complexity)] 335 | pub fn construct(self) -> (WriteHandle, ReadHandle) 336 | where 337 | K: StableHashEq + Clone, 338 | S: BuildHasher + Clone, 339 | V: StableHashEq, 340 | M: 'static + Clone, 341 | { 342 | unsafe { self.assert_stable() } 343 | } 344 | 345 | /// Create the map, and construct the read and write handles used to access it. 346 | /// 347 | /// # Safety 348 | /// 349 | /// This method is safe to call as long as the implementation of `Hash` and `Eq` for both `K` 350 | /// and `V` are deterministic. That is, they must always yield the same result if given the 351 | /// same inputs. For keys of type `K`, the result must also be consistent between different clones 352 | /// of the same key. 353 | #[allow(clippy::type_complexity)] 354 | pub unsafe fn assert_stable(self) -> (WriteHandle, ReadHandle) 355 | where 356 | K: Eq + Hash + Clone, 357 | S: BuildHasher + Clone, 358 | V: Eq + Hash, 359 | M: 'static + Clone, 360 | { 361 | let inner = if let Some(cap) = self.capacity { 362 | Inner::with_capacity_and_hasher(self.meta, cap, self.hasher) 363 | } else { 364 | Inner::with_hasher(self.meta, self.hasher) 365 | }; 366 | 367 | let (mut w, r) = left_right::new_from_empty(inner); 368 | w.append(write::Operation::MarkReady); 369 | 370 | (WriteHandle::new(w), ReadHandle::new(r)) 371 | } 372 | } 373 | 374 | /// Create an empty eventually consistent map. 375 | /// 376 | /// Use the [`Options`](./struct.Options.html) builder for more control over initialization. 377 | /// 378 | /// If you want to use arbitrary types for the keys and values, use [`new_assert_stable`]. 379 | #[allow(clippy::type_complexity)] 380 | pub fn new() -> ( 381 | WriteHandle, 382 | ReadHandle, 383 | ) 384 | where 385 | K: StableHashEq + Clone, 386 | V: StableHashEq, 387 | { 388 | Options::default().construct() 389 | } 390 | 391 | /// Create an empty eventually consistent map. 392 | /// 393 | /// Use the [`Options`](./struct.Options.html) builder for more control over initialization. 394 | /// 395 | /// # Safety 396 | /// 397 | /// This method is safe to call as long as the implementation of `Hash` and `Eq` for both `K` and 398 | /// `V` are deterministic. That is, they must always yield the same result if given the same 399 | /// inputs. For keys of type `K`, the result must also be consistent between different clones 400 | /// of the same key. 401 | #[allow(clippy::type_complexity)] 402 | pub unsafe fn new_assert_stable() -> ( 403 | WriteHandle, 404 | ReadHandle, 405 | ) 406 | where 407 | K: Eq + Hash + Clone, 408 | V: Eq + Hash, 409 | { 410 | Options::default().assert_stable() 411 | } 412 | 413 | /// Create an empty eventually consistent map with meta information and custom hasher. 414 | /// 415 | /// Use the [`Options`](./struct.Options.html) builder for more control over initialization. 416 | /// 417 | /// # Safety 418 | /// 419 | /// This method is safe to call as long as the implementation of `Hash` and `Eq` for both `K` and 420 | /// `V`, and the implementation of `BuildHasher` for `S` and [`Hasher`][std::hash::Hasher] 421 | /// for S::[Hasher][BuildHasher::Hasher] are deterministic. That is, they must always 422 | /// yield the same result if given the same inputs. For keys of type `K` and hashers of type `S`, 423 | /// their behavior must also be consistent between different clones of the same value. 424 | #[allow(clippy::type_complexity)] 425 | pub unsafe fn with_hasher( 426 | meta: M, 427 | hasher: S, 428 | ) -> (WriteHandle, ReadHandle) 429 | where 430 | K: Eq + Hash + Clone, 431 | V: Eq + Hash, 432 | M: 'static + Clone, 433 | S: BuildHasher + Clone, 434 | { 435 | Options::default() 436 | .with_hasher(hasher) 437 | .with_meta(meta) 438 | .assert_stable() 439 | } 440 | -------------------------------------------------------------------------------- /src/read.rs: -------------------------------------------------------------------------------- 1 | use crate::{inner::Inner, values::Values, Aliased}; 2 | use left_right::ReadGuard; 3 | 4 | use std::borrow::Borrow; 5 | use std::collections::hash_map::RandomState; 6 | use std::fmt; 7 | use std::hash::{BuildHasher, Hash}; 8 | use std::iter::FromIterator; 9 | 10 | mod read_ref; 11 | pub use read_ref::{MapReadRef, ReadGuardIter}; 12 | 13 | mod factory; 14 | pub use factory::ReadHandleFactory; 15 | 16 | /// A handle that may be used to read from the eventually consistent map. 17 | /// 18 | /// Note that any changes made to the map will not be made visible until the writer calls 19 | /// [`publish`](crate::WriteHandle::publish). In other words, all operations performed on a 20 | /// `ReadHandle` will *only* see writes to the map that preceeded the last call to `publish`. 21 | pub struct ReadHandle 22 | where 23 | K: Eq + Hash, 24 | S: BuildHasher, 25 | { 26 | pub(crate) handle: left_right::ReadHandle>, 27 | } 28 | 29 | impl fmt::Debug for ReadHandle 30 | where 31 | K: Eq + Hash + fmt::Debug, 32 | S: BuildHasher, 33 | M: fmt::Debug, 34 | { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | f.debug_struct("ReadHandle") 37 | .field("handle", &self.handle) 38 | .finish() 39 | } 40 | } 41 | 42 | impl Clone for ReadHandle 43 | where 44 | K: Eq + Hash, 45 | S: BuildHasher, 46 | { 47 | fn clone(&self) -> Self { 48 | Self { 49 | handle: self.handle.clone(), 50 | } 51 | } 52 | } 53 | 54 | impl ReadHandle 55 | where 56 | K: Eq + Hash, 57 | S: BuildHasher, 58 | { 59 | pub(crate) fn new(handle: left_right::ReadHandle>) -> Self { 60 | Self { handle } 61 | } 62 | } 63 | 64 | impl ReadHandle 65 | where 66 | K: Eq + Hash, 67 | V: Eq + Hash, 68 | S: BuildHasher, 69 | M: Clone, 70 | { 71 | /// Take out a guarded live reference to the read side of the map. 72 | /// 73 | /// This lets you perform more complex read operations on the map. 74 | /// 75 | /// While the reference lives, changes to the map cannot be published. 76 | /// 77 | /// If no publish has happened, or the map has been destroyed, this function returns `None`. 78 | /// 79 | /// See [`MapReadRef`]. 80 | pub fn enter(&self) -> Option> { 81 | let guard = self.handle.enter()?; 82 | if !guard.ready { 83 | return None; 84 | } 85 | Some(MapReadRef { guard }) 86 | } 87 | 88 | /// Returns the number of non-empty keys present in the map. 89 | pub fn len(&self) -> usize { 90 | self.enter().map_or(0, |x| x.len()) 91 | } 92 | 93 | /// Returns true if the map contains no elements. 94 | pub fn is_empty(&self) -> bool { 95 | self.enter().map_or(true, |x| x.is_empty()) 96 | } 97 | 98 | /// Get the current meta value. 99 | pub fn meta(&self) -> Option> { 100 | Some(ReadGuard::map(self.handle.enter()?, |inner| &inner.meta)) 101 | } 102 | 103 | /// Internal version of `get_and` 104 | fn get_raw(&self, key: &Q) -> Option>> 105 | where 106 | K: Borrow, 107 | Q: Hash + Eq, 108 | { 109 | let inner = self.handle.enter()?; 110 | if !inner.ready { 111 | return None; 112 | } 113 | 114 | ReadGuard::try_map(inner, |inner| inner.data.get(key).map(AsRef::as_ref)) 115 | } 116 | 117 | /// Returns a guarded reference to the values corresponding to the key. 118 | /// 119 | /// While the guard lives, changes to the map cannot be published. 120 | /// 121 | /// The key may be any borrowed form of the map's key type, but `Hash` and `Eq` on the borrowed 122 | /// form must match those for the key type. 123 | /// 124 | /// Note that not all writes will be included with this read -- only those that have been 125 | /// published by the writer. If no publish has happened, or the map has been destroyed, this 126 | /// function returns `None`. 127 | #[inline] 128 | pub fn get<'rh, Q: ?Sized>(&'rh self, key: &'_ Q) -> Option>> 129 | where 130 | K: Borrow, 131 | Q: Hash + Eq, 132 | { 133 | // call `borrow` here to monomorphize `get_raw` fewer times 134 | self.get_raw(key.borrow()) 135 | } 136 | 137 | /// Returns a guarded reference to _one_ value corresponding to the key. 138 | /// 139 | /// This is mostly intended for use when you are working with no more than one value per key. 140 | /// If there are multiple values stored for this key, there are no guarantees to which element 141 | /// is returned. 142 | /// 143 | /// While the guard lives, changes to the map cannot be published. 144 | /// 145 | /// The key may be any borrowed form of the map's key type, but `Hash` and `Eq` on the borrowed 146 | /// form must match those for the key type. 147 | /// 148 | /// Note that not all writes will be included with this read -- only those that have been 149 | /// refreshed by the writer. If no refresh has happened, or the map has been destroyed, this 150 | /// function returns `None`. 151 | #[inline] 152 | pub fn get_one<'rh, Q: ?Sized>(&'rh self, key: &'_ Q) -> Option> 153 | where 154 | K: Borrow, 155 | Q: Hash + Eq, 156 | { 157 | ReadGuard::try_map(self.get_raw(key.borrow())?, |x| x.get_one()) 158 | } 159 | 160 | /// Returns a guarded reference to the values corresponding to the key along with the map 161 | /// meta. 162 | /// 163 | /// While the guard lives, changes to the map cannot be published. 164 | /// 165 | /// The key may be any borrowed form of the map's key type, but `Hash` and `Eq` on the borrowed 166 | /// form *must* match those for the key type. 167 | /// 168 | /// Note that not all writes will be included with this read -- only those that have been 169 | /// refreshed by the writer. If no refresh has happened, or the map has been destroyed, this 170 | /// function returns `None`. 171 | /// 172 | /// If no values exist for the given key, `Some(None, _)` is returned. 173 | pub fn meta_get(&self, key: &Q) -> Option<(Option>>, M)> 174 | where 175 | K: Borrow, 176 | Q: Hash + Eq, 177 | { 178 | let inner = self.handle.enter()?; 179 | if !inner.ready { 180 | return None; 181 | } 182 | let meta = inner.meta.clone(); 183 | let res = ReadGuard::try_map(inner, |inner| inner.data.get(key).map(AsRef::as_ref)); 184 | Some((res, meta)) 185 | } 186 | 187 | /// Returns true if the [`WriteHandle`](crate::WriteHandle) has been dropped. 188 | pub fn was_dropped(&self) -> bool { 189 | self.handle.was_dropped() 190 | } 191 | 192 | /// Returns true if the map contains any values for the specified key. 193 | /// 194 | /// The key may be any borrowed form of the map's key type, but `Hash` and `Eq` on the borrowed 195 | /// form *must* match those for the key type. 196 | pub fn contains_key(&self, key: &Q) -> bool 197 | where 198 | K: Borrow, 199 | Q: Hash + Eq, 200 | { 201 | self.enter().map_or(false, |x| x.contains_key(key)) 202 | } 203 | 204 | /// Returns true if the map contains the specified value for the specified key. 205 | /// 206 | /// The key and value may be any borrowed form of the map's respective types, but `Hash` and 207 | /// `Eq` on the borrowed form *must* match. 208 | pub fn contains_value(&self, key: &Q, value: &W) -> bool 209 | where 210 | K: Borrow, 211 | Aliased: Borrow, 212 | Q: Hash + Eq, 213 | W: Hash + Eq, 214 | V: Hash + Eq, 215 | { 216 | self.get_raw(key.borrow()) 217 | .map(|x| x.contains(value)) 218 | .unwrap_or(false) 219 | } 220 | 221 | /// Read all values in the map, and transform them into a new collection. 222 | pub fn map_into(&self, mut f: Map) -> Collector 223 | where 224 | Map: FnMut(&K, &Values) -> Target, 225 | Collector: FromIterator, 226 | { 227 | self.enter() 228 | .iter() 229 | .flatten() 230 | .map(|(k, v)| f(k, v)) 231 | .collect() 232 | } 233 | } 234 | 235 | #[cfg(test)] 236 | mod test { 237 | use crate::new; 238 | 239 | // the idea of this test is to allocate 64 elements, and only use 17. The vector will 240 | // probably try to fit either exactly the length, to the next highest power of 2 from 241 | // the length, or something else entirely, E.g. 17, 32, etc., 242 | // but it should always end up being smaller than the original 64 elements reserved. 243 | #[test] 244 | fn reserve_and_fit() { 245 | const MIN: usize = (1 << 4) + 1; 246 | const MAX: usize = 1 << 6; 247 | 248 | let (mut w, r) = new(); 249 | 250 | w.reserve(0, MAX).publish(); 251 | 252 | assert!(r.get_raw(&0).unwrap().capacity() >= MAX); 253 | 254 | for i in 0..MIN { 255 | w.insert(0, i); 256 | } 257 | 258 | w.fit_all().publish(); 259 | 260 | assert!(r.get_raw(&0).unwrap().capacity() < MAX); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/read/factory.rs: -------------------------------------------------------------------------------- 1 | use super::ReadHandle; 2 | use crate::inner::Inner; 3 | use std::collections::hash_map::RandomState; 4 | use std::fmt; 5 | use std::hash::{BuildHasher, Hash}; 6 | 7 | /// A type that is both `Sync` and `Send` and lets you produce new [`ReadHandle`] instances. 8 | /// 9 | /// This serves as a handy way to distribute read handles across many threads without requiring 10 | /// additional external locking to synchronize access to the non-`Sync` [`ReadHandle`] type. Note 11 | /// that this _internally_ takes a lock whenever you call [`ReadHandleFactory::handle`], so 12 | /// you should not expect producing new handles rapidly to scale well. 13 | pub struct ReadHandleFactory 14 | where 15 | K: Eq + Hash, 16 | S: BuildHasher, 17 | { 18 | pub(super) factory: left_right::ReadHandleFactory>, 19 | } 20 | 21 | impl fmt::Debug for ReadHandleFactory 22 | where 23 | K: Eq + Hash, 24 | S: BuildHasher, 25 | { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | f.debug_struct("ReadHandleFactory") 28 | .field("factory", &self.factory) 29 | .finish() 30 | } 31 | } 32 | 33 | impl Clone for ReadHandleFactory 34 | where 35 | K: Eq + Hash, 36 | S: BuildHasher, 37 | { 38 | fn clone(&self) -> Self { 39 | Self { 40 | factory: self.factory.clone(), 41 | } 42 | } 43 | } 44 | 45 | impl ReadHandleFactory 46 | where 47 | K: Eq + Hash, 48 | S: BuildHasher, 49 | { 50 | /// Produce a new [`ReadHandle`] to the same left-right data structure as this factory was 51 | /// originally produced from. 52 | pub fn handle(&self) -> ReadHandle { 53 | ReadHandle { 54 | handle: self.factory.handle(), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/read/read_ref.rs: -------------------------------------------------------------------------------- 1 | use crate::values::ValuesInner; 2 | use crate::{inner::Inner, values::Values, Aliased}; 3 | use left_right::ReadGuard; 4 | use std::borrow::Borrow; 5 | use std::collections::hash_map::RandomState; 6 | use std::fmt; 7 | use std::hash::{BuildHasher, Hash}; 8 | 9 | // To make [`WriteHandle`] and friends work. 10 | #[cfg(doc)] 11 | use crate::WriteHandle; 12 | 13 | /// A live reference into the read half of an evmap. 14 | /// 15 | /// As long as this lives, changes to the map being read cannot be published. If a writer attempts 16 | /// to call [`WriteHandle::publish`], that call will block until this is dropped. 17 | /// 18 | /// Since the map remains immutable while this lives, the methods on this type all give you 19 | /// unguarded references to types contained in the map. 20 | pub struct MapReadRef<'rh, K, V, M = (), S = RandomState> 21 | where 22 | K: Hash + Eq, 23 | V: Eq + Hash, 24 | S: BuildHasher, 25 | { 26 | pub(super) guard: ReadGuard<'rh, Inner>, 27 | } 28 | 29 | impl<'rh, K, V, M, S> fmt::Debug for MapReadRef<'rh, K, V, M, S> 30 | where 31 | K: Hash + Eq, 32 | V: Eq + Hash, 33 | S: BuildHasher, 34 | K: fmt::Debug, 35 | M: fmt::Debug, 36 | V: fmt::Debug, 37 | { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | f.debug_struct("MapReadRef") 40 | .field("guard", &self.guard) 41 | .finish() 42 | } 43 | } 44 | 45 | impl<'rh, K, V, M, S> MapReadRef<'rh, K, V, M, S> 46 | where 47 | K: Hash + Eq, 48 | V: Eq + Hash, 49 | S: BuildHasher, 50 | { 51 | /// Iterate over all key + valuesets in the map. 52 | /// 53 | /// Be careful with this function! While the iteration is ongoing, any writer that tries to 54 | /// publish changes will block waiting on this reader to finish. 55 | pub fn iter(&self) -> ReadGuardIter<'_, K, V, S> { 56 | ReadGuardIter { 57 | iter: self.guard.data.iter(), 58 | } 59 | } 60 | 61 | /// Iterate over all keys in the map. 62 | /// 63 | /// Be careful with this function! While the iteration is ongoing, any writer that tries to 64 | /// publish changes will block waiting on this reader to finish. 65 | pub fn keys(&self) -> KeysIter<'_, K, V, S> { 66 | KeysIter { 67 | iter: self.guard.data.iter(), 68 | } 69 | } 70 | 71 | /// Iterate over all value sets in the map. 72 | /// 73 | /// Be careful with this function! While the iteration is ongoing, any writer that tries to 74 | /// publish changes will block waiting on this reader to finish. 75 | pub fn values(&self) -> ValuesIter<'_, K, V, S> { 76 | ValuesIter { 77 | iter: self.guard.data.iter(), 78 | } 79 | } 80 | 81 | /// Returns the number of non-empty keys present in the map. 82 | pub fn len(&self) -> usize { 83 | self.guard.data.len() 84 | } 85 | 86 | /// Returns true if the map contains no elements. 87 | pub fn is_empty(&self) -> bool { 88 | self.guard.data.is_empty() 89 | } 90 | 91 | /// Get the current meta value. 92 | pub fn meta(&self) -> &M { 93 | &self.guard.meta 94 | } 95 | 96 | /// Returns a reference to the values corresponding to the key. 97 | /// 98 | /// The key may be any borrowed form of the map's key type, but `Hash` and `Eq` on the borrowed 99 | /// form *must* match those for the key type. 100 | /// 101 | /// Note that not all writes will be included with this read -- only those that have been 102 | /// published by the writer. If no publish has happened, or the map has been destroyed, this 103 | /// function returns `None`. 104 | pub fn get<'a, Q: ?Sized>(&'a self, key: &'_ Q) -> Option<&'a Values> 105 | where 106 | K: Borrow, 107 | Q: Hash + Eq, 108 | { 109 | self.guard.data.get(key).map(AsRef::as_ref) 110 | } 111 | 112 | /// Returns a guarded reference to _one_ value corresponding to the key. 113 | /// 114 | /// This is mostly intended for use when you are working with no more than one value per key. 115 | /// If there are multiple values stored for this key, there are no guarantees to which element 116 | /// is returned. 117 | /// 118 | /// The key may be any borrowed form of the map's key type, but `Hash` and `Eq` on the borrowed 119 | /// form *must* match those for the key type. 120 | /// 121 | /// Note that not all writes will be included with this read -- only those that have been 122 | /// published by the writer. If no publish has happened, or the map has been destroyed, this 123 | /// function returns `None`. 124 | pub fn get_one<'a, Q: ?Sized>(&'a self, key: &'_ Q) -> Option<&'a V> 125 | where 126 | K: Borrow, 127 | Q: Hash + Eq, 128 | { 129 | self.guard 130 | .data 131 | .get(key) 132 | .and_then(|values| values.as_ref().get_one()) 133 | } 134 | 135 | /// Returns true if the map contains any values for the specified key. 136 | /// 137 | /// The key may be any borrowed form of the map's key type, but `Hash` and `Eq` on the borrowed 138 | /// form *must* match those for the key type. 139 | pub fn contains_key(&self, key: &Q) -> bool 140 | where 141 | K: Borrow, 142 | Q: Hash + Eq, 143 | { 144 | self.guard.data.contains_key(key) 145 | } 146 | 147 | /// Returns true if the map contains the specified value for the specified key. 148 | /// 149 | /// The key and value may be any borrowed form of the map's respective types, but `Hash` and 150 | /// `Eq` on the borrowed form *must* match. 151 | pub fn contains_value(&self, key: &Q, value: &W) -> bool 152 | where 153 | K: Borrow, 154 | Aliased: Borrow, 155 | Q: Hash + Eq, 156 | W: Hash + Eq, 157 | { 158 | self.guard 159 | .data 160 | .get(key) 161 | .map_or(false, |values| values.as_ref().contains(value)) 162 | } 163 | } 164 | 165 | impl<'rh, K, Q, V, M, S> std::ops::Index<&'_ Q> for MapReadRef<'rh, K, V, M, S> 166 | where 167 | K: Eq + Hash + Borrow, 168 | V: Eq + Hash, 169 | Q: Eq + Hash + ?Sized, 170 | S: BuildHasher, 171 | { 172 | type Output = Values; 173 | fn index(&self, key: &Q) -> &Self::Output { 174 | self.get(key).unwrap() 175 | } 176 | } 177 | 178 | impl<'rg, 'rh, K, V, M, S> IntoIterator for &'rg MapReadRef<'rh, K, V, M, S> 179 | where 180 | K: Eq + Hash, 181 | V: Eq + Hash, 182 | S: BuildHasher, 183 | { 184 | type Item = (&'rg K, &'rg Values); 185 | type IntoIter = ReadGuardIter<'rg, K, V, S>; 186 | fn into_iter(self) -> Self::IntoIter { 187 | self.iter() 188 | } 189 | } 190 | 191 | /// An [`Iterator`] over keys and values in the evmap. 192 | pub struct ReadGuardIter<'rg, K, V, S> 193 | where 194 | K: Eq + Hash, 195 | V: Eq + Hash, 196 | S: BuildHasher, 197 | { 198 | iter: <&'rg crate::inner::MapImpl, S> as IntoIterator>::IntoIter, 199 | } 200 | 201 | impl<'rg, K, V, S> fmt::Debug for ReadGuardIter<'rg, K, V, S> 202 | where 203 | K: Eq + Hash + fmt::Debug, 204 | V: Eq + Hash, 205 | S: BuildHasher, 206 | V: fmt::Debug, 207 | { 208 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 209 | f.debug_tuple("ReadGuardIter").field(&self.iter).finish() 210 | } 211 | } 212 | 213 | impl<'rg, K, V, S> Iterator for ReadGuardIter<'rg, K, V, S> 214 | where 215 | K: Eq + Hash, 216 | V: Eq + Hash, 217 | S: BuildHasher, 218 | { 219 | type Item = (&'rg K, &'rg Values); 220 | fn next(&mut self) -> Option { 221 | self.iter.next().map(|(k, v)| (k, v.as_ref())) 222 | } 223 | } 224 | 225 | /// An [`Iterator`] over keys. 226 | pub struct KeysIter<'rg, K, V, S> 227 | where 228 | K: Eq + Hash, 229 | V: Eq + Hash, 230 | S: BuildHasher, 231 | { 232 | iter: <&'rg crate::inner::MapImpl, S> as IntoIterator>::IntoIter, 233 | } 234 | 235 | impl<'rg, K, V, S> fmt::Debug for KeysIter<'rg, K, V, S> 236 | where 237 | K: Eq + Hash + fmt::Debug, 238 | V: Eq + Hash, 239 | S: BuildHasher, 240 | V: fmt::Debug, 241 | { 242 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 243 | f.debug_tuple("KeysIter").field(&self.iter).finish() 244 | } 245 | } 246 | 247 | impl<'rg, K, V, S> Iterator for KeysIter<'rg, K, V, S> 248 | where 249 | K: Eq + Hash, 250 | V: Eq + Hash, 251 | S: BuildHasher, 252 | { 253 | type Item = &'rg K; 254 | fn next(&mut self) -> Option { 255 | self.iter.next().map(|(k, _)| k) 256 | } 257 | } 258 | 259 | /// An [`Iterator`] over value sets. 260 | pub struct ValuesIter<'rg, K, V, S> 261 | where 262 | K: Eq + Hash, 263 | V: Eq + Hash, 264 | S: BuildHasher, 265 | { 266 | iter: <&'rg crate::inner::MapImpl, S> as IntoIterator>::IntoIter, 267 | } 268 | 269 | impl<'rg, K, V, S> fmt::Debug for ValuesIter<'rg, K, V, S> 270 | where 271 | K: Eq + Hash + fmt::Debug, 272 | V: Eq + Hash, 273 | S: BuildHasher, 274 | V: fmt::Debug, 275 | { 276 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 277 | f.debug_tuple("ValuesIter").field(&self.iter).finish() 278 | } 279 | } 280 | 281 | impl<'rg, K, V, S> Iterator for ValuesIter<'rg, K, V, S> 282 | where 283 | K: Eq + Hash, 284 | V: Eq + Hash, 285 | S: BuildHasher, 286 | { 287 | type Item = &'rg Values; 288 | fn next(&mut self) -> Option { 289 | self.iter.next().map(|(_, v)| v.as_ref()) 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/stable_hash_eq.rs: -------------------------------------------------------------------------------- 1 | /// [Sealed] trait for types in [`std`] that are known to implement 2 | /// `Hash` and `Eq` deterministically. 3 | /// 4 | /// Furthermore, if `T: Clone + StableHashEq`, then `T::clone` is 5 | /// deterministic too, in the sense that the behavior with regards 6 | /// to `Hash` and `Eq` methods stays consistent between both clones. 7 | /// 8 | /// _This trait is sealed and cannot be implemented outside of the `evmap` crate._ 9 | /// 10 | /// [Sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed 11 | pub trait StableHashEq: Hash + Eq + sealed_hash_eq::Sealed {} 12 | 13 | mod sealed_hash_eq { 14 | pub trait Sealed {} 15 | } 16 | 17 | macro_rules! stable_hash_eq { 18 | ($( 19 | $({$($a:lifetime),*$(,)?$($T:ident$(:?$Sized:ident)?),*$(,)?} 20 | $({$($manual_bounds:tt)*})?)? $Type:ty, 21 | )*) => { 22 | stable_hash_eq!{# 23 | $( 24 | $({$($a)*$($T$(:?$Sized$Sized)?)*})? $($({where $($manual_bounds)*})? 25 | { 26 | where $( 27 | $T: StableHashEq, 28 | )* 29 | })? 30 | $Type, 31 | )* 32 | } 33 | }; 34 | (#$( 35 | $({$($a:lifetime)*$($T:ident$(:?Sized$Sized:ident)?)*} 36 | {$($where_bounds:tt)*}$({$($_t:tt)*})?)? $Type:ty, 37 | )*) => { 38 | $( 39 | impl$(<$($a,)*$($T$(:?$Sized)?,)*>)? StableHashEq for $Type 40 | $($($where_bounds)*)? {} 41 | impl$(<$($a,)*$($T$(:?$Sized)?,)*>)? sealed_hash_eq::Sealed for $Type 42 | $($($where_bounds)*)? {} 43 | )* 44 | }; 45 | } 46 | 47 | use std::{ 48 | any::TypeId, 49 | borrow::Cow, 50 | cmp::{self, Reverse}, 51 | collections::{BTreeMap, BTreeSet, LinkedList, VecDeque}, 52 | convert::Infallible, 53 | ffi::{CStr, CString, OsStr, OsString}, 54 | fmt, 55 | fs::FileType, 56 | hash::Hash, 57 | io::ErrorKind, 58 | marker::{PhantomData, PhantomPinned}, 59 | mem::{Discriminant, ManuallyDrop}, 60 | net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, 61 | num::{ 62 | NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, 63 | NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping, 64 | }, 65 | ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, 66 | path::{Component, Path, PathBuf, Prefix, PrefixComponent}, 67 | ptr::NonNull, 68 | rc::Rc, 69 | sync::{atomic, Arc}, 70 | task::Poll, 71 | thread::ThreadId, 72 | time::{Duration, Instant, SystemTime}, 73 | }; 74 | 75 | stable_hash_eq! { 76 | cmp::Ordering, 77 | Infallible, 78 | ErrorKind, 79 | IpAddr, 80 | SocketAddr, 81 | atomic::Ordering, 82 | bool, char, 83 | i8, i16, i32, i64, i128, 84 | isize, 85 | str, 86 | u8, u16, u32, u64, u128, 87 | (), 88 | usize, 89 | TypeId, 90 | CStr, 91 | CString, 92 | OsStr, 93 | OsString, 94 | fmt::Error, 95 | FileType, 96 | PhantomPinned, 97 | Ipv4Addr, 98 | Ipv6Addr, 99 | SocketAddrV4, 100 | SocketAddrV6, 101 | NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, 102 | NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, 103 | RangeFull, 104 | Path, 105 | PathBuf, 106 | String, 107 | ThreadId, 108 | Duration, 109 | Instant, 110 | SystemTime, 111 | {'a} PrefixComponent<'a>, 112 | {'a} Cow<'a, str>, 113 | {'a} Cow<'a, CStr>, 114 | {'a} Cow<'a, OsStr>, 115 | {'a} Cow<'a, Path>, 116 | {'a, T}{T: Clone + StableHashEq} Cow<'a, [T]>, 117 | {'a, T}{T: Clone + StableHashEq} Cow<'a, T>, 118 | {'a, T: ?Sized} &'a T, 119 | {'a, T: ?Sized} &'a mut T, 120 | {'a} Component<'a>, 121 | {'a} Prefix<'a>, 122 | {A: ?Sized} (A,), 123 | {T} VecDeque, 124 | {A, B: ?Sized} (A, B), 125 | {A, B, C: ?Sized} (A, B, C), 126 | {A, B, C, D: ?Sized} (A, B, C, D), 127 | {A, B, C, D, E: ?Sized} (A, B, C, D, E), 128 | {A, B, C, D, E, F: ?Sized} (A, B, C, D, E, F), 129 | {A, B, C, D, E, F, G: ?Sized} (A, B, C, D, E, F, G), 130 | {A, B, C, D, E, F, G, H: ?Sized} (A, B, C, D, E, F, G, H), 131 | {A, B, C, D, E, F, G, H, I: ?Sized} (A, B, C, D, E, F, G, H, I), 132 | {A, B, C, D, E, F, G, H, I, J: ?Sized} (A, B, C, D, E, F, G, H, I, J), 133 | {A, B, C, D, E, F, G, H, I, J, K: ?Sized} (A, B, C, D, E, F, G, H, I, J, K), 134 | {A, B, C, D, E, F, G, H, I, J, K, L: ?Sized} (A, B, C, D, E, F, G, H, I, J, K, L), 135 | {Idx} Range, 136 | {Idx} RangeFrom, 137 | {Idx} RangeInclusive, 138 | {Idx} RangeTo, 139 | {Idx} RangeToInclusive, 140 | {K, V} BTreeMap, 141 | } 142 | macro_rules! stable_hash_eq_fn { 143 | ($({$($($A:ident),+)?})*) => { 144 | stable_hash_eq!{ 145 | $( 146 | {Ret$(, $($A),+)?}{} fn($($($A),+)?) -> Ret, 147 | {Ret$(, $($A),+)?}{} extern "C" fn($($($A),+)?) -> Ret, 148 | $({Ret, $($A),+}{} extern "C" fn($($A),+, ...) -> Ret,)? 149 | {Ret$(, $($A),+)?}{} unsafe fn($($($A),+)?) -> Ret, 150 | {Ret$(, $($A),+)?}{} unsafe extern "C" fn($($($A),+)?) -> Ret, 151 | $({Ret, $($A),+}{} unsafe extern "C" fn($($A),+, ...) -> Ret,)? 152 | )* 153 | } 154 | }; 155 | } 156 | stable_hash_eq_fn! { 157 | {} 158 | {A} 159 | {A, B} 160 | {A, B, C} 161 | {A, B, C, D} 162 | {A, B, C, D, E} 163 | {A, B, C, D, E, F} 164 | {A, B, C, D, E, F, G} 165 | {A, B, C, D, E, F, G, H} 166 | {A, B, C, D, E, F, G, H, I} 167 | {A, B, C, D, E, F, G, H, I, J} 168 | {A, B, C, D, E, F, G, H, I, J, K} 169 | {A, B, C, D, E, F, G, H, I, J, K, L} 170 | } 171 | stable_hash_eq! { 172 | {T} Bound, 173 | {T} Option, 174 | {T} Poll, 175 | {T: ?Sized}{} *const T, 176 | {T: ?Sized}{} *mut T, 177 | {T} [T], 178 | {T: ?Sized} Box, 179 | {T} Reverse, 180 | {T} BTreeSet, 181 | {T} LinkedList, 182 | {T: ?Sized}{} PhantomData, 183 | {T}{} Discriminant, 184 | {T} ManuallyDrop, 185 | {T} Wrapping, 186 | {T: ?Sized}{} NonNull, 187 | {T: ?Sized} Rc, 188 | {T: ?Sized} Arc, 189 | {T} Vec, 190 | {T, E} Result, 191 | {T} [T; 0], {T} [T; 1], {T} [T; 2], {T} [T; 3], {T} [T; 4], 192 | {T} [T; 5], {T} [T; 6], {T} [T; 7], {T} [T; 8], {T} [T; 9], 193 | {T} [T; 10], {T} [T; 11], {T} [T; 12], {T} [T; 13], {T} [T; 14], 194 | {T} [T; 15], {T} [T; 16], {T} [T; 17], {T} [T; 18], {T} [T; 19], 195 | {T} [T; 20], {T} [T; 21], {T} [T; 22], {T} [T; 23], {T} [T; 24], 196 | {T} [T; 25], {T} [T; 26], {T} [T; 27], {T} [T; 28], {T} [T; 29], 197 | {T} [T; 30], {T} [T; 31], {T} [T; 32], 198 | } 199 | -------------------------------------------------------------------------------- /src/values.rs: -------------------------------------------------------------------------------- 1 | use left_right::aliasing::{Aliased, DropBehavior}; 2 | use std::borrow::Borrow; 3 | use std::fmt; 4 | use std::hash::{BuildHasher, Hash}; 5 | 6 | /// This value determines when a value-set is promoted from a list to a HashBag. 7 | const BAG_THRESHOLD: usize = 32; 8 | 9 | /// A bag of values for a given key in the evmap. 10 | #[repr(transparent)] 11 | pub struct Values( 12 | pub(crate) ValuesInner, 13 | ); 14 | 15 | impl Default for Values { 16 | fn default() -> Self { 17 | Values(ValuesInner::Short(Default::default())) 18 | } 19 | } 20 | 21 | impl fmt::Debug for Values 22 | where 23 | T: fmt::Debug, 24 | S: BuildHasher, 25 | { 26 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | self.0.fmt(fmt) 28 | } 29 | } 30 | 31 | pub(crate) enum ValuesInner 32 | where 33 | D: DropBehavior, 34 | { 35 | Short(smallvec::SmallVec<[Aliased; 1]>), 36 | Long(hashbag::HashBag, S>), 37 | } 38 | 39 | impl From> for Values { 40 | fn from(v: ValuesInner) -> Self { 41 | Values(v) 42 | } 43 | } 44 | 45 | impl fmt::Debug for ValuesInner 46 | where 47 | D: DropBehavior, 48 | T: fmt::Debug, 49 | S: BuildHasher, 50 | { 51 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 52 | match self { 53 | ValuesInner::Short(ref v) => fmt.debug_set().entries(v.iter()).finish(), 54 | ValuesInner::Long(ref v) => fmt.debug_set().entries(v.iter()).finish(), 55 | } 56 | } 57 | } 58 | 59 | impl Values { 60 | /// Returns the number of values. 61 | pub fn len(&self) -> usize { 62 | match self.0 { 63 | ValuesInner::Short(ref v) => v.len(), 64 | ValuesInner::Long(ref v) => v.len(), 65 | } 66 | } 67 | 68 | /// Returns true if the bag holds no values. 69 | pub fn is_empty(&self) -> bool { 70 | match self.0 { 71 | ValuesInner::Short(ref v) => v.is_empty(), 72 | ValuesInner::Long(ref v) => v.is_empty(), 73 | } 74 | } 75 | 76 | /// Returns the number of values that can be held without reallocating. 77 | pub fn capacity(&self) -> usize { 78 | match self.0 { 79 | ValuesInner::Short(ref v) => v.capacity(), 80 | ValuesInner::Long(ref v) => v.capacity(), 81 | } 82 | } 83 | 84 | /// An iterator visiting all elements in arbitrary order. 85 | /// 86 | /// The iterator element type is &'a T. 87 | pub fn iter(&self) -> ValuesIter<'_, T, S> { 88 | match self.0 { 89 | ValuesInner::Short(ref v) => ValuesIter(ValuesIterInner::Short(v.iter())), 90 | ValuesInner::Long(ref v) => ValuesIter(ValuesIterInner::Long(v.iter())), 91 | } 92 | } 93 | 94 | /// Returns a guarded reference to _one_ value corresponding to the key. 95 | /// 96 | /// This is mostly intended for use when you are working with no more than one value per key. 97 | /// If there are multiple values stored for this key, there are no guarantees to which element 98 | /// is returned. 99 | pub fn get_one(&self) -> Option<&T> { 100 | match self.0 { 101 | ValuesInner::Short(ref v) => v.get(0).map(|v| &**v), 102 | ValuesInner::Long(ref v) => v.iter().next().map(|v| &**v), 103 | } 104 | } 105 | 106 | /// Returns true if a value matching `value` is among the stored values. 107 | /// 108 | /// The value may be any borrowed form of `T`, but [`Hash`] and [`Eq`] on the borrowed form 109 | /// *must* match those for the value type. 110 | pub fn contains(&self, value: &Q) -> bool 111 | where 112 | Aliased: Borrow, 113 | Q: Eq + Hash, 114 | T: Eq + Hash, 115 | S: BuildHasher, 116 | { 117 | match self.0 { 118 | ValuesInner::Short(ref v) => v.iter().any(|v| v.borrow() == value), 119 | ValuesInner::Long(ref v) => v.contains(value) != 0, 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | fn is_short(&self) -> bool { 125 | matches!(self.0, ValuesInner::Short(_)) 126 | } 127 | } 128 | 129 | impl<'a, T, S> IntoIterator for &'a Values { 130 | type IntoIter = ValuesIter<'a, T, S>; 131 | type Item = &'a T; 132 | fn into_iter(self) -> Self::IntoIter { 133 | self.iter() 134 | } 135 | } 136 | 137 | pub struct ValuesIter<'a, T, S>(ValuesIterInner<'a, T, S>); 138 | 139 | #[non_exhaustive] 140 | enum ValuesIterInner<'a, T, S> { 141 | Short(<&'a smallvec::SmallVec<[Aliased; 1]> as IntoIterator>::IntoIter), 142 | Long(<&'a hashbag::HashBag, S> as IntoIterator>::IntoIter), 143 | } 144 | 145 | impl<'a, T, S> fmt::Debug for ValuesIter<'a, T, S> 146 | where 147 | T: fmt::Debug, 148 | { 149 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 150 | match self.0 { 151 | ValuesIterInner::Short(ref it) => f.debug_tuple("Short").field(it).finish(), 152 | ValuesIterInner::Long(ref it) => f.debug_tuple("Long").field(it).finish(), 153 | } 154 | } 155 | } 156 | 157 | impl<'a, T, S> Iterator for ValuesIter<'a, T, S> { 158 | type Item = &'a T; 159 | fn next(&mut self) -> Option { 160 | match self.0 { 161 | ValuesIterInner::Short(ref mut it) => it.next().map(|v| &**v), 162 | ValuesIterInner::Long(ref mut it) => it.next().map(|v| &**v), 163 | } 164 | } 165 | 166 | fn size_hint(&self) -> (usize, Option) { 167 | match self.0 { 168 | ValuesIterInner::Short(ref it) => it.size_hint(), 169 | ValuesIterInner::Long(ref it) => it.size_hint(), 170 | } 171 | } 172 | } 173 | 174 | impl<'a, T, S> ExactSizeIterator for ValuesIter<'a, T, S> 175 | where 176 | <&'a smallvec::SmallVec<[T; 1]> as IntoIterator>::IntoIter: ExactSizeIterator, 177 | <&'a hashbag::HashBag as IntoIterator>::IntoIter: ExactSizeIterator, 178 | { 179 | } 180 | 181 | impl<'a, T, S> std::iter::FusedIterator for ValuesIter<'a, T, S> 182 | where 183 | <&'a smallvec::SmallVec<[T; 1]> as IntoIterator>::IntoIter: std::iter::FusedIterator, 184 | <&'a hashbag::HashBag as IntoIterator>::IntoIter: std::iter::FusedIterator, 185 | { 186 | } 187 | 188 | impl ValuesInner 189 | where 190 | D: DropBehavior, 191 | T: Eq + Hash, 192 | S: BuildHasher + Clone, 193 | { 194 | pub(crate) fn new() -> Self { 195 | ValuesInner::Short(smallvec::SmallVec::new()) 196 | } 197 | 198 | pub(crate) fn with_capacity_and_hasher(capacity: usize, hasher: &S) -> Self { 199 | if capacity > BAG_THRESHOLD { 200 | ValuesInner::Long(hashbag::HashBag::with_capacity_and_hasher( 201 | capacity, 202 | hasher.clone(), 203 | )) 204 | } else { 205 | ValuesInner::Short(smallvec::SmallVec::with_capacity(capacity)) 206 | } 207 | } 208 | 209 | pub(crate) fn shrink_to_fit(&mut self) { 210 | match self { 211 | ValuesInner::Short(ref mut v) => v.shrink_to_fit(), 212 | ValuesInner::Long(ref mut v) => { 213 | // here, we actually want to be clever 214 | // we want to potentially "downgrade" from a Long to a Short 215 | // 216 | // NOTE: there may be more than one instance of row in the bag. if there is, we do 217 | // not move to a SmallVec. The reason is simple: if we did, we would need to 218 | // duplicate those rows again. But, how would we do so safely? If we clone into 219 | // both the left and the right map (that is, on both first and second apply), then 220 | // we would only free one of them. If we alias the one we have in the hashbag, then 221 | // once any instance gets remove from both sides, it'll be freed, which will 222 | // invalidate the remaining references. 223 | if v.len() < BAG_THRESHOLD && v.len() == v.set_len() { 224 | let mut short = smallvec::SmallVec::with_capacity(v.len()); 225 | // NOTE: this drain _must_ have a deterministic iteration order. 226 | // that is, the items must be yielded in the same order regardless of whether 227 | // we are iterating on the left or right map. otherwise, this happens; 228 | // 229 | // 1. after shrink_to_fit, left value is AA*B*, right is B*AA*. 230 | // X* elements are aliased copies of each other 231 | // 2. swap_remove B (1st); left is AA*B*, right is now A*A 232 | // 3. swap_remove B (2nd); left drops B* and is now AA*, right is A*A 233 | // 4. swap_remove A (1st); left is now A*, right is A*A 234 | // 5. swap_remove A (2nd); left is A*, right drops A* and is now A 235 | // right dropped A* while A still has it -- no okay! 236 | for (row, n) in v.drain() { 237 | assert_eq!(n, 1); 238 | short.push(row); 239 | } 240 | *self = ValuesInner::Short(short); 241 | } else { 242 | v.shrink_to_fit(); 243 | } 244 | } 245 | } 246 | } 247 | 248 | pub(crate) fn clear(&mut self) { 249 | // NOTE: we do _not_ downgrade to Short here -- shrink is for that 250 | match self { 251 | ValuesInner::Short(ref mut v) => v.clear(), 252 | ValuesInner::Long(ref mut v) => v.clear(), 253 | } 254 | } 255 | 256 | pub(crate) fn swap_remove(&mut self, value: &T) { 257 | match self { 258 | ValuesInner::Short(ref mut v) => { 259 | if let Some(i) = v.iter().position(|v| &**v == value) { 260 | v.swap_remove(i); 261 | } 262 | } 263 | ValuesInner::Long(ref mut v) => { 264 | v.remove(value); 265 | } 266 | } 267 | } 268 | 269 | fn baggify(&mut self, capacity: usize, hasher: &S) { 270 | if let ValuesInner::Short(ref mut v) = self { 271 | let mut long = hashbag::HashBag::with_capacity_and_hasher(capacity, hasher.clone()); 272 | 273 | // NOTE: this _may_ drop some values since the bag does not keep duplicates. 274 | // that should be fine -- if we drop for the first time, we're dropping 275 | // ManuallyDrop, which won't actually drop the aliased values. when we drop for 276 | // the second time, we do the actual dropping. since second application has the 277 | // exact same original state, this change from short/long should occur exactly 278 | // the same. 279 | long.extend(v.drain(..)); 280 | *self = ValuesInner::Long(long); 281 | } 282 | } 283 | 284 | pub(crate) fn reserve(&mut self, additional: usize, hasher: &S) { 285 | match self { 286 | ValuesInner::Short(ref mut v) => { 287 | let n = v.len() + additional; 288 | if n >= BAG_THRESHOLD { 289 | self.baggify(n, hasher); 290 | } else { 291 | v.reserve(additional) 292 | } 293 | } 294 | ValuesInner::Long(ref mut v) => v.reserve(additional), 295 | } 296 | } 297 | 298 | pub(crate) fn push(&mut self, value: Aliased, hasher: &S) { 299 | match self { 300 | ValuesInner::Short(ref mut v) => { 301 | // we may want to upgrade to a Long.. 302 | let n = v.len() + 1; 303 | if n >= BAG_THRESHOLD { 304 | self.baggify(n, hasher); 305 | if let ValuesInner::Long(ref mut v) = self { 306 | v.insert(value); 307 | } else { 308 | unreachable!(); 309 | } 310 | } else { 311 | v.push(value); 312 | } 313 | } 314 | ValuesInner::Long(ref mut v) => { 315 | v.insert(value); 316 | } 317 | } 318 | } 319 | 320 | pub(crate) fn retain(&mut self, mut f: F) 321 | where 322 | F: FnMut(&T) -> bool, 323 | { 324 | match self { 325 | ValuesInner::Short(ref mut v) => v.retain(|v| f(&*v)), 326 | ValuesInner::Long(ref mut v) => v.retain(|v, n| if f(v) { n } else { 0 }), 327 | } 328 | } 329 | } 330 | 331 | impl AsRef> for ValuesInner { 332 | fn as_ref(&self) -> &Values { 333 | // safety: Values is #[repr(transparent)] 334 | unsafe { &*(self as *const _ as *const Values) } 335 | } 336 | } 337 | 338 | impl ValuesInner 339 | where 340 | T: Eq + Hash, 341 | S: BuildHasher + Clone, 342 | { 343 | pub(crate) unsafe fn alias( 344 | other: &ValuesInner, 345 | hasher: &S, 346 | ) -> Self { 347 | match &other { 348 | ValuesInner::Short(s) => { 349 | ValuesInner::Short(s.iter().map(|v| v.alias().change_drop()).collect()) 350 | } 351 | ValuesInner::Long(l) => { 352 | let mut long = hashbag::HashBag::with_hasher(hasher.clone()); 353 | long.extend(l.set_iter().map(|(v, n)| (v.alias().change_drop(), n))); 354 | ValuesInner::Long(long) 355 | } 356 | } 357 | } 358 | } 359 | 360 | #[cfg(test)] 361 | mod tests { 362 | use super::*; 363 | use std::collections::hash_map::RandomState; 364 | 365 | macro_rules! assert_empty { 366 | ($x:expr) => { 367 | assert_eq!($x.len(), 0); 368 | assert!($x.is_empty()); 369 | assert_eq!($x.iter().count(), 0); 370 | assert_eq!($x.into_iter().count(), 0); 371 | assert_eq!($x.get_one(), None); 372 | }; 373 | } 374 | 375 | macro_rules! assert_len { 376 | ($x:expr, $n:expr) => { 377 | assert_eq!($x.len(), $n); 378 | assert!(!$x.is_empty()); 379 | assert_eq!($x.iter().count(), $n); 380 | assert_eq!($x.into_iter().count(), $n); 381 | }; 382 | } 383 | 384 | #[test] 385 | fn sensible_default() { 386 | let v: Values = Values::default(); 387 | assert!(v.is_short()); 388 | assert_eq!(v.capacity(), 1); 389 | assert_empty!(v); 390 | } 391 | 392 | #[test] 393 | fn short_values() { 394 | let hasher = RandomState::default(); 395 | let mut v = Values(ValuesInner::new()); 396 | 397 | let values = 0..BAG_THRESHOLD - 1; 398 | let len = values.clone().count(); 399 | for i in values.clone() { 400 | v.0.push(Aliased::from(i), &hasher); 401 | } 402 | 403 | for i in values.clone() { 404 | assert!(v.contains(&i)); 405 | } 406 | assert!(v.is_short()); 407 | assert_len!(v, len); 408 | assert_eq!(v.get_one(), Some(&0)); 409 | 410 | v.0.clear(); 411 | 412 | assert_empty!(v); 413 | 414 | // clear() should not affect capacity or value type! 415 | assert!(v.capacity() > 1); 416 | assert!(v.is_short()); 417 | 418 | v.0.shrink_to_fit(); 419 | 420 | assert_eq!(v.capacity(), 1); 421 | } 422 | 423 | #[test] 424 | fn long_values() { 425 | let hasher = RandomState::default(); 426 | let mut v = Values(ValuesInner::new()); 427 | 428 | let values = 0..BAG_THRESHOLD; 429 | let len = values.clone().count(); 430 | for i in values.clone() { 431 | v.0.push(Aliased::from(i), &hasher); 432 | } 433 | 434 | for i in values.clone() { 435 | assert!(v.contains(&i)); 436 | } 437 | assert!(!v.is_short()); 438 | assert_len!(v, len); 439 | assert!(values.contains(v.get_one().unwrap())); 440 | 441 | v.0.clear(); 442 | 443 | assert_empty!(v); 444 | 445 | // clear() should not affect capacity or value type! 446 | assert!(v.capacity() > 1); 447 | assert!(!v.is_short()); 448 | 449 | v.0.shrink_to_fit(); 450 | 451 | // Now we have short values! 452 | assert!(v.is_short()); 453 | assert_eq!(v.capacity(), 1); 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /src/write.rs: -------------------------------------------------------------------------------- 1 | use crate::inner::{Entry, Inner}; 2 | use crate::read::ReadHandle; 3 | use crate::values::ValuesInner; 4 | use left_right::{aliasing::Aliased, Absorb}; 5 | 6 | use std::collections::hash_map::RandomState; 7 | use std::fmt; 8 | use std::hash::{BuildHasher, Hash}; 9 | 10 | /// A handle that may be used to modify the eventually consistent map. 11 | /// 12 | /// Note that any changes made to the map will not be made visible to readers until 13 | /// [`publish`](Self::publish) is called. 14 | /// 15 | /// When the `WriteHandle` is dropped, the map is immediately (but safely) taken away from all 16 | /// readers, causing all future lookups to return `None`. 17 | /// 18 | /// # Examples 19 | /// ``` 20 | /// let x = ('x', 42); 21 | /// 22 | /// let (mut w, r) = evmap::new(); 23 | /// 24 | /// // the map is uninitialized, so all lookups should return None 25 | /// assert_eq!(r.get(&x.0).map(|rs| rs.len()), None); 26 | /// 27 | /// w.publish(); 28 | /// 29 | /// // after the first publish, it is empty, but ready 30 | /// assert_eq!(r.get(&x.0).map(|rs| rs.len()), None); 31 | /// 32 | /// w.insert(x.0, x); 33 | /// 34 | /// // it is empty even after an add (we haven't publish yet) 35 | /// assert_eq!(r.get(&x.0).map(|rs| rs.len()), None); 36 | /// 37 | /// w.publish(); 38 | /// 39 | /// // but after the swap, the record is there! 40 | /// assert_eq!(r.get(&x.0).map(|rs| rs.len()), Some(1)); 41 | /// assert_eq!(r.get(&x.0).map(|rs| rs.iter().any(|v| v.0 == x.0 && v.1 == x.1)), Some(true)); 42 | /// ``` 43 | pub struct WriteHandle 44 | where 45 | K: Eq + Hash + Clone, 46 | S: BuildHasher + Clone, 47 | V: Eq + Hash, 48 | M: 'static + Clone, 49 | { 50 | handle: left_right::WriteHandle, Operation>, 51 | r_handle: ReadHandle, 52 | } 53 | 54 | impl fmt::Debug for WriteHandle 55 | where 56 | K: Eq + Hash + Clone + fmt::Debug, 57 | S: BuildHasher + Clone, 58 | V: Eq + Hash + fmt::Debug, 59 | M: 'static + Clone + fmt::Debug, 60 | { 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | f.debug_struct("WriteHandle") 63 | .field("handle", &self.handle) 64 | .finish() 65 | } 66 | } 67 | 68 | impl WriteHandle 69 | where 70 | K: Eq + Hash + Clone, 71 | S: BuildHasher + Clone, 72 | V: Eq + Hash, 73 | M: 'static + Clone, 74 | { 75 | pub(crate) fn new( 76 | handle: left_right::WriteHandle, Operation>, 77 | ) -> Self { 78 | let r_handle = ReadHandle::new(left_right::ReadHandle::clone(&*handle)); 79 | Self { handle, r_handle } 80 | } 81 | 82 | /// Publish all changes since the last call to `publish` to make them visible to readers. 83 | /// 84 | /// This can take some time, especially if readers are executing slow operations, or if there 85 | /// are many of them. 86 | pub fn publish(&mut self) -> &mut Self { 87 | self.handle.publish(); 88 | self 89 | } 90 | 91 | /// Returns true if there are changes to the map that have not yet been exposed to readers. 92 | pub fn has_pending(&self) -> bool { 93 | self.handle.has_pending_operations() 94 | } 95 | 96 | /// Set the metadata. 97 | /// 98 | /// Will only be visible to readers after the next call to [`publish`](Self::publish). 99 | pub fn set_meta(&mut self, meta: M) { 100 | self.add_op(Operation::SetMeta(meta)); 101 | } 102 | 103 | fn add_op(&mut self, op: Operation) -> &mut Self { 104 | self.handle.append(op); 105 | self 106 | } 107 | 108 | /// Add the given value to the value-bag of the given key. 109 | /// 110 | /// The updated value-bag will only be visible to readers after the next call to 111 | /// [`publish`](Self::publish). 112 | pub fn insert(&mut self, k: K, v: V) -> &mut Self { 113 | self.add_op(Operation::Add(k, Aliased::from(v))) 114 | } 115 | 116 | /// Replace the value-bag of the given key with the given value. 117 | /// 118 | /// Replacing the value will automatically deallocate any heap storage and place the new value 119 | /// back into the `SmallVec` inline storage. This can improve cache locality for common 120 | /// cases where the value-bag is only ever a single element. 121 | /// 122 | /// See [the doc section on this](./index.html#small-vector-optimization) for more information. 123 | /// 124 | /// The new value will only be visible to readers after the next call to 125 | /// [`publish`](Self::publish). 126 | pub fn update(&mut self, k: K, v: V) -> &mut Self { 127 | self.add_op(Operation::Replace(k, Aliased::from(v))) 128 | } 129 | 130 | /// Clear the value-bag of the given key, without removing it. 131 | /// 132 | /// If a value-bag already exists, this will clear it but leave the 133 | /// allocated memory intact for reuse, or if no associated value-bag exists 134 | /// an empty value-bag will be created for the given key. 135 | /// 136 | /// The new value will only be visible to readers after the next call to 137 | /// [`publish`](Self::publish). 138 | pub fn clear(&mut self, k: K) -> &mut Self { 139 | self.add_op(Operation::Clear(k)) 140 | } 141 | 142 | /// Remove the given value from the value-bag of the given key. 143 | /// 144 | /// The updated value-bag will only be visible to readers after the next call to 145 | /// [`publish`](Self::publish). 146 | #[deprecated(since = "11.0.0", note = "Renamed to remove_value")] 147 | pub fn remove(&mut self, k: K, v: V) -> &mut Self { 148 | self.remove_value(k, v) 149 | } 150 | 151 | /// Remove the given value from the value-bag of the given key. 152 | /// 153 | /// The updated value-bag will only be visible to readers after the next call to 154 | /// [`publish`](Self::publish). 155 | pub fn remove_value(&mut self, k: K, v: V) -> &mut Self { 156 | self.add_op(Operation::RemoveValue(k, v)) 157 | } 158 | 159 | /// Remove the value-bag for the given key. 160 | /// 161 | /// The value-bag will only disappear from readers after the next call to 162 | /// [`publish`](Self::publish). 163 | #[deprecated(since = "11.0.0", note = "Renamed to remove_entry")] 164 | pub fn empty(&mut self, k: K) -> &mut Self { 165 | self.remove_entry(k) 166 | } 167 | 168 | /// Remove the value-bag for the given key. 169 | /// 170 | /// The value-bag will only disappear from readers after the next call to 171 | /// [`publish`](Self::publish). 172 | pub fn remove_entry(&mut self, k: K) -> &mut Self { 173 | self.add_op(Operation::RemoveEntry(k)) 174 | } 175 | 176 | /// Purge all value-bags from the map. 177 | /// 178 | /// The map will only appear empty to readers after the next call to 179 | /// [`publish`](Self::publish). 180 | /// 181 | /// Note that this will iterate once over all the keys internally. 182 | pub fn purge(&mut self) -> &mut Self { 183 | self.add_op(Operation::Purge) 184 | } 185 | 186 | /// Retain elements for the given key using the provided predicate function. 187 | /// 188 | /// The remaining value-bag will only be visible to readers after the next call to 189 | /// [`publish`](Self::publish) 190 | /// 191 | /// # Safety 192 | /// 193 | /// The given closure is called _twice_ for each element, once when called, and once 194 | /// on swap. It _must_ retain the same elements each time, otherwise a value may exist in one 195 | /// map, but not the other, leaving the two maps permanently out-of-sync. This is _really_ bad, 196 | /// as values are aliased between the maps, and are assumed safe to free when they leave the 197 | /// map during a `publish`. Returning `true` when `retain` is first called for a value, and 198 | /// `false` the second time would free the value, but leave an aliased pointer to it in the 199 | /// other side of the map. 200 | /// 201 | /// The arguments to the predicate function are the current value in the value-bag, and `true` 202 | /// if this is the first value in the value-bag on the second map, or `false` otherwise. Use 203 | /// the second argument to know when to reset any closure-local state to ensure deterministic 204 | /// operation. 205 | /// 206 | /// So, stated plainly, the given closure _must_ return the same order of true/false for each 207 | /// of the two iterations over the value-bag. That is, the sequence of returned booleans before 208 | /// the second argument is true must be exactly equal to the sequence of returned booleans 209 | /// at and beyond when the second argument is true. 210 | pub unsafe fn retain(&mut self, k: K, f: F) -> &mut Self 211 | where 212 | F: FnMut(&V, bool) -> bool + 'static + Send, 213 | { 214 | self.add_op(Operation::Retain(k, Predicate(Box::new(f)))) 215 | } 216 | 217 | /// Shrinks a value-bag to it's minimum necessary size, freeing memory 218 | /// and potentially improving cache locality by switching to inline storage. 219 | /// 220 | /// The optimized value-bag will only be visible to readers after the next call to 221 | /// [`publish`](Self::publish) 222 | pub fn fit(&mut self, k: K) -> &mut Self { 223 | self.add_op(Operation::Fit(Some(k))) 224 | } 225 | 226 | /// Like [`WriteHandle::fit`](#method.fit), but shrinks all value-bags in the map. 227 | /// 228 | /// The optimized value-bags will only be visible to readers after the next call to 229 | /// [`publish`](Self::publish) 230 | pub fn fit_all(&mut self) -> &mut Self { 231 | self.add_op(Operation::Fit(None)) 232 | } 233 | 234 | /// Reserves capacity for some number of additional elements in a value-bag, 235 | /// or creates an empty value-bag for this key with the given capacity if 236 | /// it doesn't already exist. 237 | /// 238 | /// Readers are unaffected by this operation, but it can improve performance 239 | /// by pre-allocating space for large value-bags. 240 | pub fn reserve(&mut self, k: K, additional: usize) -> &mut Self { 241 | self.add_op(Operation::Reserve(k, additional)) 242 | } 243 | 244 | #[cfg(feature = "eviction")] 245 | #[cfg_attr(docsrs, doc(cfg(feature = "eviction")))] 246 | /// Remove the value-bag for `n` randomly chosen keys. 247 | /// 248 | /// This method immediately calls [`publish`](Self::publish) to ensure that the keys and values 249 | /// it returns match the elements that will be emptied on the next call to 250 | /// [`publish`](Self::publish). The value-bags will only disappear from readers after the next 251 | /// call to [`publish`](Self::publish). 252 | pub fn empty_random<'a>( 253 | &'a mut self, 254 | rng: &mut impl rand::Rng, 255 | n: usize, 256 | ) -> impl ExactSizeIterator)> { 257 | // force a publish so that our view into self.r_handle matches the indices we choose. 258 | // if we didn't do this, the `i`th element of r_handle may be a completely different 259 | // element than the one that _will_ be evicted when `EmptyAt([.. i ..])` is applied. 260 | // this would be bad since we are telling the caller which elements we are evicting! 261 | // note also that we _must_ use `r_handle`, not `w_handle`, since `w_handle` may have 262 | // pending operations even after a publish! 263 | self.publish(); 264 | 265 | let inner = self 266 | .r_handle 267 | .handle 268 | .raw_handle() 269 | .expect("WriteHandle has not been dropped"); 270 | // safety: the writer cannot publish until 'a ends, so we know that reading from the read 271 | // map is safe for the duration of 'a. 272 | let inner: &'a Inner = 273 | unsafe { std::mem::transmute::<&Inner, _>(inner.as_ref()) }; 274 | let inner = &inner.data; 275 | 276 | // let's pick some (distinct) indices to evict! 277 | let n = n.min(inner.len()); 278 | let indices = rand::seq::index::sample(rng, inner.len(), n); 279 | 280 | // we need to sort the indices so that, later, we can make sure to swap remove from last to 281 | // first (and so not accidentally remove the wrong index). 282 | let mut to_remove = indices.clone().into_vec(); 283 | to_remove.sort_unstable(); 284 | self.add_op(Operation::EmptyAt(to_remove)); 285 | 286 | indices.into_iter().map(move |i| { 287 | let (k, vs) = inner.get_index(i).expect("in-range"); 288 | (k, vs.as_ref()) 289 | }) 290 | } 291 | } 292 | 293 | impl Absorb> for Inner 294 | where 295 | K: Eq + Hash + Clone, 296 | S: BuildHasher + Clone, 297 | V: Eq + Hash, 298 | M: 'static + Clone, 299 | { 300 | /// Apply ops in such a way that no values are dropped, only forgotten 301 | fn absorb_first(&mut self, op: &mut Operation, other: &Self) { 302 | // Safety note for calls to .alias(): 303 | // 304 | // it is safe to alias this value here because if it is ever removed, one alias is always 305 | // first dropped with NoDrop (in absorb_first), and _then_ the other (and only remaining) 306 | // alias is dropped with DoDrop (in absorb_second). we won't drop the aliased value until 307 | // _after_ absorb_second is called on this operation, so leaving an alias in the oplog is 308 | // also safe. 309 | 310 | let hasher = other.data.hasher(); 311 | match *op { 312 | Operation::Replace(ref key, ref mut value) => { 313 | let vs = self 314 | .data 315 | .entry(key.clone()) 316 | .or_insert_with(ValuesInner::new); 317 | 318 | // truncate vector 319 | vs.clear(); 320 | 321 | // implicit shrink_to_fit on replace op 322 | // so it will switch back to inline allocation for the subsequent push. 323 | vs.shrink_to_fit(); 324 | 325 | vs.push(unsafe { value.alias() }, hasher); 326 | } 327 | Operation::Clear(ref key) => { 328 | self.data 329 | .entry(key.clone()) 330 | .or_insert_with(ValuesInner::new) 331 | .clear(); 332 | } 333 | Operation::Add(ref key, ref mut value) => { 334 | self.data 335 | .entry(key.clone()) 336 | .or_insert_with(ValuesInner::new) 337 | .push(unsafe { value.alias() }, hasher); 338 | } 339 | Operation::RemoveEntry(ref key) => { 340 | #[cfg(not(feature = "indexed"))] 341 | self.data.remove(key); 342 | #[cfg(feature = "indexed")] 343 | self.data.swap_remove(key); 344 | } 345 | Operation::Purge => { 346 | self.data.clear(); 347 | } 348 | #[cfg(feature = "eviction")] 349 | Operation::EmptyAt(ref indices) => { 350 | for &index in indices.iter().rev() { 351 | self.data.swap_remove_index(index); 352 | } 353 | } 354 | Operation::RemoveValue(ref key, ref value) => { 355 | if let Some(e) = self.data.get_mut(key) { 356 | e.swap_remove(&value); 357 | } 358 | } 359 | Operation::Retain(ref key, ref mut predicate) => { 360 | if let Some(e) = self.data.get_mut(key) { 361 | let mut first = true; 362 | e.retain(move |v| { 363 | let retain = predicate.eval(v, first); 364 | first = false; 365 | retain 366 | }); 367 | } 368 | } 369 | Operation::Fit(ref key) => match key { 370 | Some(ref key) => { 371 | if let Some(e) = self.data.get_mut(key) { 372 | e.shrink_to_fit(); 373 | } 374 | } 375 | None => { 376 | for value_set in self.data.values_mut() { 377 | value_set.shrink_to_fit(); 378 | } 379 | } 380 | }, 381 | Operation::Reserve(ref key, additional) => match self.data.entry(key.clone()) { 382 | Entry::Occupied(mut entry) => { 383 | entry.get_mut().reserve(additional, hasher); 384 | } 385 | Entry::Vacant(entry) => { 386 | entry.insert(ValuesInner::with_capacity_and_hasher(additional, hasher)); 387 | } 388 | }, 389 | Operation::MarkReady => { 390 | self.ready = true; 391 | } 392 | Operation::SetMeta(ref m) => { 393 | self.meta = m.clone(); 394 | } 395 | } 396 | } 397 | 398 | /// Apply operations while allowing dropping of values 399 | fn absorb_second(&mut self, op: Operation, other: &Self) { 400 | // # Safety (for cast): 401 | // 402 | // See the module-level documentation for left_right::aliasing. 403 | // NoDrop and DoDrop are both private, therefore this cast is (likely) sound. 404 | // 405 | // # Safety (for NoDrop -> DoDrop): 406 | // 407 | // It is safe for us to drop values the second time each operation has been 408 | // performed, since if they are dropped here, they were also dropped in the first 409 | // application of the operation, which removed the only other alias. 410 | // 411 | // FIXME: This is where the non-determinism of Hash and PartialEq hits us (#78). 412 | let inner: &mut Inner = 413 | unsafe { &mut *(self as *mut _ as *mut _) }; 414 | 415 | // Safety note for calls to .change_drop(): 416 | // 417 | // we're turning a NoDrop into DoDrop, so we must be prepared for a drop. 418 | // if absorb_first dropped its alias, then `value` is the only alias 419 | // if absorb_first did not drop its alias, then `value` will not be dropped here either, 420 | // and at the end of scope we revert to `NoDrop`, so all is well. 421 | let hasher = other.data.hasher(); 422 | match op { 423 | Operation::Replace(key, value) => { 424 | let v = inner.data.entry(key).or_insert_with(ValuesInner::new); 425 | v.clear(); 426 | v.shrink_to_fit(); 427 | 428 | v.push(unsafe { value.change_drop() }, hasher); 429 | } 430 | Operation::Clear(key) => { 431 | inner 432 | .data 433 | .entry(key) 434 | .or_insert_with(ValuesInner::new) 435 | .clear(); 436 | } 437 | Operation::Add(key, value) => { 438 | // safety (below): 439 | // we're turning a NoDrop into DoDrop, so we must be prepared for a drop. 440 | // if absorb_first dropped the value, then `value` is the only alias 441 | // if absorb_first did not drop the value, then `value` will not be dropped here 442 | // either, and at the end of scope we revert to `NoDrop`, so all is well. 443 | inner 444 | .data 445 | .entry(key) 446 | .or_insert_with(ValuesInner::new) 447 | .push(unsafe { value.change_drop() }, hasher); 448 | } 449 | Operation::RemoveEntry(key) => { 450 | #[cfg(not(feature = "indexed"))] 451 | inner.data.remove(&key); 452 | #[cfg(feature = "indexed")] 453 | inner.data.swap_remove(&key); 454 | } 455 | Operation::Purge => { 456 | inner.data.clear(); 457 | } 458 | #[cfg(feature = "eviction")] 459 | Operation::EmptyAt(indices) => { 460 | for &index in indices.iter().rev() { 461 | inner.data.swap_remove_index(index); 462 | } 463 | } 464 | Operation::RemoveValue(key, value) => { 465 | if let Some(e) = inner.data.get_mut(&key) { 466 | // find the first entry that matches all fields 467 | e.swap_remove(&value); 468 | } 469 | } 470 | Operation::Retain(key, mut predicate) => { 471 | if let Some(e) = inner.data.get_mut(&key) { 472 | let mut first = true; 473 | e.retain(move |v| { 474 | let retain = predicate.eval(&*v, first); 475 | first = false; 476 | retain 477 | }); 478 | } 479 | } 480 | Operation::Fit(key) => match key { 481 | Some(ref key) => { 482 | if let Some(e) = inner.data.get_mut(key) { 483 | e.shrink_to_fit(); 484 | } 485 | } 486 | None => { 487 | for value_set in inner.data.values_mut() { 488 | value_set.shrink_to_fit(); 489 | } 490 | } 491 | }, 492 | Operation::Reserve(key, additional) => match inner.data.entry(key) { 493 | Entry::Occupied(mut entry) => { 494 | entry.get_mut().reserve(additional, hasher); 495 | } 496 | Entry::Vacant(entry) => { 497 | entry.insert(ValuesInner::with_capacity_and_hasher(additional, hasher)); 498 | } 499 | }, 500 | Operation::MarkReady => { 501 | inner.ready = true; 502 | } 503 | Operation::SetMeta(m) => { 504 | inner.meta = m; 505 | } 506 | } 507 | } 508 | 509 | fn drop_first(self: Box) { 510 | // since the two copies are exactly equal, we need to make sure that we *don't* call the 511 | // destructors of any of the values that are in our map, as they'll all be called when the 512 | // last read handle goes out of scope. that's easy enough since none of them will be 513 | // dropped by default. 514 | } 515 | 516 | fn drop_second(self: Box) { 517 | // when the second copy is dropped is where we want to _actually_ drop all the values in 518 | // the map. we do this by setting the generic type to the one that causes drops to happen. 519 | // 520 | // safety: since we're going second, we know that all the aliases in the first map have 521 | // gone away, so all of our aliases must be the only ones. 522 | let inner: Box> = 523 | unsafe { Box::from_raw(Box::into_raw(self) as *mut _ as *mut _) }; 524 | drop(inner); 525 | } 526 | 527 | fn sync_with(&mut self, first: &Self) { 528 | let inner: &mut Inner = 529 | unsafe { &mut *(self as *mut _ as *mut _) }; 530 | inner.data.extend(first.data.iter().map(|(k, vs)| { 531 | // # Safety (for aliasing): 532 | // 533 | // We are aliasing every value in the read map, and the oplog has no other 534 | // pending operations (by the semantics of JustCloneRHandle). For any of the 535 | // values we alias to be dropped, the operation that drops it must first be 536 | // enqueued to the oplog, at which point it will _first_ go through 537 | // absorb_first, which will remove the alias and leave only one alias left. 538 | // Only after that, when that operation eventually goes through absorb_second, 539 | // will the alias be dropped, and by that time it is the only value. 540 | // 541 | // # Safety (for hashing): 542 | // 543 | // Due to `RandomState` there can be subtle differences between the iteration order 544 | // of two `HashMap` instances. We prevent this by using `left_right::new_with_empty`, 545 | // which `clone`s the first map, making them use the same hasher. 546 | // 547 | // # Safety (for NoDrop -> DoDrop): 548 | // 549 | // The oplog has only this one operation in it for the first call to `publish`, 550 | // so we are about to turn the alias back into NoDrop. 551 | (k.clone(), unsafe { 552 | ValuesInner::alias(vs, first.data.hasher()) 553 | }) 554 | })); 555 | self.ready = true; 556 | } 557 | } 558 | 559 | impl Extend<(K, V)> for WriteHandle 560 | where 561 | K: Eq + Hash + Clone, 562 | S: BuildHasher + Clone, 563 | V: Eq + Hash, 564 | M: 'static + Clone, 565 | { 566 | fn extend>(&mut self, iter: I) { 567 | for (k, v) in iter { 568 | self.insert(k, v); 569 | } 570 | } 571 | } 572 | 573 | // allow using write handle for reads 574 | use std::ops::Deref; 575 | impl Deref for WriteHandle 576 | where 577 | K: Eq + Hash + Clone, 578 | S: BuildHasher + Clone, 579 | V: Eq + Hash, 580 | M: 'static + Clone, 581 | { 582 | type Target = ReadHandle; 583 | fn deref(&self) -> &Self::Target { 584 | &self.r_handle 585 | } 586 | } 587 | 588 | /// A pending map operation. 589 | #[non_exhaustive] 590 | pub(super) enum Operation { 591 | /// Replace the set of entries for this key with this value. 592 | Replace(K, Aliased), 593 | /// Add this value to the set of entries for this key. 594 | Add(K, Aliased), 595 | /// Remove this value from the set of entries for this key. 596 | RemoveValue(K, V), 597 | /// Remove the value set for this key. 598 | RemoveEntry(K), 599 | #[cfg(feature = "eviction")] 600 | /// Drop keys at the given indices. 601 | /// 602 | /// The list of indices must be sorted in ascending order. 603 | EmptyAt(Vec), 604 | /// Remove all values in the value set for this key. 605 | Clear(K), 606 | /// Remove all values for all keys. 607 | /// 608 | /// Note that this will iterate once over all the keys internally. 609 | Purge, 610 | /// Retains all values matching the given predicate. 611 | Retain(K, Predicate), 612 | /// Shrinks [`Values`] to their minimum necessary size, freeing memory 613 | /// and potentially improving cache locality. 614 | /// 615 | /// If no key is given, all `Values` will shrink to fit. 616 | Fit(Option), 617 | /// Reserves capacity for some number of additional elements in [`Values`] 618 | /// for the given key. If the given key does not exist, allocate an empty 619 | /// `Values` with the given capacity. 620 | /// 621 | /// This can improve performance by pre-allocating space for large bags of values. 622 | Reserve(K, usize), 623 | /// Mark the map as ready to be consumed for readers. 624 | MarkReady, 625 | /// Set the value of the map meta. 626 | SetMeta(M), 627 | } 628 | 629 | impl fmt::Debug for Operation 630 | where 631 | K: fmt::Debug, 632 | V: fmt::Debug, 633 | M: fmt::Debug, 634 | { 635 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 636 | match *self { 637 | Operation::Replace(ref a, ref b) => f.debug_tuple("Replace").field(a).field(b).finish(), 638 | Operation::Add(ref a, ref b) => f.debug_tuple("Add").field(a).field(b).finish(), 639 | Operation::RemoveValue(ref a, ref b) => { 640 | f.debug_tuple("RemoveValue").field(a).field(b).finish() 641 | } 642 | Operation::RemoveEntry(ref a) => f.debug_tuple("RemoveEntry").field(a).finish(), 643 | #[cfg(feature = "eviction")] 644 | Operation::EmptyAt(ref a) => f.debug_tuple("EmptyAt").field(a).finish(), 645 | Operation::Clear(ref a) => f.debug_tuple("Clear").field(a).finish(), 646 | Operation::Purge => f.debug_tuple("Purge").finish(), 647 | Operation::Retain(ref a, ref b) => f.debug_tuple("Retain").field(a).field(b).finish(), 648 | Operation::Fit(ref a) => f.debug_tuple("Fit").field(a).finish(), 649 | Operation::Reserve(ref a, ref b) => f.debug_tuple("Reserve").field(a).field(b).finish(), 650 | Operation::MarkReady => f.debug_tuple("MarkReady").finish(), 651 | Operation::SetMeta(ref a) => f.debug_tuple("SetMeta").field(a).finish(), 652 | } 653 | } 654 | } 655 | 656 | /// Unary predicate used to retain elements. 657 | /// 658 | /// The predicate function is called once for each distinct value, and `true` if this is the 659 | /// _first_ call to the predicate on the _second_ application of the operation. 660 | pub(super) struct Predicate(Box bool + Send>); 661 | 662 | impl Predicate { 663 | /// Evaluate the predicate for the given element 664 | #[inline] 665 | fn eval(&mut self, value: &V, reset: bool) -> bool { 666 | (*self.0)(value, reset) 667 | } 668 | } 669 | 670 | impl PartialEq for Predicate { 671 | #[inline] 672 | fn eq(&self, other: &Self) -> bool { 673 | // only compare data, not vtable: https://stackoverflow.com/q/47489449/472927 674 | &*self.0 as *const _ as *const () == &*other.0 as *const _ as *const () 675 | } 676 | } 677 | 678 | impl Eq for Predicate {} 679 | 680 | impl fmt::Debug for Predicate { 681 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 682 | f.debug_tuple("Predicate") 683 | .field(&format_args!("{:p}", &*self.0 as *const _)) 684 | .finish() 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate evmap; 2 | 3 | use std::iter::FromIterator; 4 | 5 | macro_rules! assert_match { 6 | ($x:expr, $p:pat) => { 7 | if let $p = $x { 8 | } else { 9 | panic!(concat!(stringify!($x), " did not match ", stringify!($p))); 10 | } 11 | }; 12 | } 13 | 14 | #[test] 15 | fn it_works() { 16 | let x = ('x', 42); 17 | 18 | let (mut w, r) = evmap::new(); 19 | 20 | // the map is uninitialized, so all lookups should return None 21 | assert_match!(r.get(&x.0), None); 22 | 23 | w.publish(); 24 | 25 | // after the first refresh, it is empty, but ready 26 | assert_match!(r.get(&x.0), None); 27 | // since we're not using `meta`, we get () 28 | assert_match!(r.meta_get(&x.0), Some((None, ()))); 29 | 30 | w.insert(x.0, x); 31 | 32 | // it is empty even after an add (we haven't refresh yet) 33 | assert_match!(r.get(&x.0), None); 34 | assert_match!(r.meta_get(&x.0), Some((None, ()))); 35 | 36 | w.publish(); 37 | 38 | // but after the swap, the record is there! 39 | assert_match!(r.get(&x.0).map(|rs| rs.len()), Some(1)); 40 | assert_match!( 41 | r.meta_get(&x.0).map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 42 | Some((Some(1), ())) 43 | ); 44 | assert_match!( 45 | r.get(&x.0) 46 | .map(|rs| rs.iter().any(|v| v.0 == x.0 && v.1 == x.1)), 47 | Some(true) 48 | ); 49 | 50 | // non-existing records return None 51 | assert_match!(r.get(&'y').map(|rs| rs.len()), None); 52 | assert_match!( 53 | r.meta_get(&'y').map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 54 | Some((None, ())) 55 | ); 56 | 57 | // if we purge, the readers still see the values 58 | w.purge(); 59 | assert_match!( 60 | r.get(&x.0) 61 | .map(|rs| rs.iter().any(|v| v.0 == x.0 && v.1 == x.1)), 62 | Some(true) 63 | ); 64 | 65 | // but once we refresh, things will be empty 66 | w.publish(); 67 | assert_match!(r.get(&x.0).map(|rs| rs.len()), None); 68 | assert_match!( 69 | r.meta_get(&x.0).map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 70 | Some((None, ())) 71 | ); 72 | } 73 | 74 | #[test] 75 | fn mapref() { 76 | let x = ('x', 42); 77 | 78 | let (mut w, r) = evmap::new(); 79 | 80 | // get a read ref to the map 81 | // scope to ensure it gets dropped and doesn't stall refresh 82 | { 83 | assert!(r.enter().is_none()); 84 | } 85 | 86 | w.publish(); 87 | 88 | { 89 | let map = r.enter().unwrap(); 90 | // after the first refresh, it is empty, but ready 91 | assert!(map.is_empty()); 92 | assert!(!map.contains_key(&x.0)); 93 | assert!(map.get(&x.0).is_none()); 94 | // since we're not using `meta`, we get () 95 | assert_eq!(map.meta(), &()); 96 | } 97 | 98 | w.insert(x.0, x); 99 | 100 | { 101 | let map = r.enter().unwrap(); 102 | // it is empty even after an add (we haven't refresh yet) 103 | assert!(map.is_empty()); 104 | assert!(!map.contains_key(&x.0)); 105 | assert!(map.get(&x.0).is_none()); 106 | assert_eq!(map.meta(), &()); 107 | } 108 | 109 | w.publish(); 110 | 111 | { 112 | let map = r.enter().unwrap(); 113 | 114 | // but after the swap, the record is there! 115 | assert!(!map.is_empty()); 116 | assert!(map.contains_key(&x.0)); 117 | assert_eq!(map.get(&x.0).unwrap().len(), 1); 118 | assert_eq!(map[&x.0].len(), 1); 119 | assert_eq!(map.meta(), &()); 120 | assert!(map 121 | .get(&x.0) 122 | .unwrap() 123 | .iter() 124 | .any(|v| v.0 == x.0 && v.1 == x.1)); 125 | 126 | // non-existing records return None 127 | assert!(map.get(&'y').is_none()); 128 | assert_eq!(map.meta(), &()); 129 | 130 | // if we purge, the readers still see the values 131 | w.purge(); 132 | 133 | assert!(map 134 | .get(&x.0) 135 | .unwrap() 136 | .iter() 137 | .any(|v| v.0 == x.0 && v.1 == x.1)); 138 | } 139 | 140 | // but once we refresh, things will be empty 141 | w.publish(); 142 | 143 | { 144 | let map = r.enter().unwrap(); 145 | assert!(map.is_empty()); 146 | assert!(!map.contains_key(&x.0)); 147 | assert!(map.get(&x.0).is_none()); 148 | assert_eq!(map.meta(), &()); 149 | } 150 | 151 | drop(w); 152 | { 153 | let map = r.enter(); 154 | assert!(map.is_none(), "the map should have been destroyed"); 155 | } 156 | } 157 | 158 | #[test] 159 | #[cfg_attr(miri, ignore)] 160 | // https://github.com/rust-lang/miri/issues/658 161 | fn paniced_reader_doesnt_block_writer() { 162 | let (mut w, r) = evmap::new(); 163 | w.insert(1, "a"); 164 | w.publish(); 165 | 166 | // reader panics 167 | let r = std::panic::catch_unwind(move || r.get(&1).map(|_| panic!())); 168 | assert!(r.is_err()); 169 | 170 | // writer should still be able to continue 171 | w.insert(1, "b"); 172 | w.publish(); 173 | w.publish(); 174 | } 175 | 176 | #[test] 177 | fn read_after_drop() { 178 | let x = ('x', 42); 179 | 180 | let (mut w, r) = evmap::new(); 181 | w.insert(x.0, x); 182 | w.publish(); 183 | assert_eq!(r.get(&x.0).map(|rs| rs.len()), Some(1)); 184 | 185 | // once we drop the writer, the readers should see empty maps 186 | drop(w); 187 | assert_eq!(r.get(&x.0).map(|rs| rs.len()), None); 188 | assert_eq!( 189 | r.meta_get(&x.0).map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 190 | None 191 | ); 192 | } 193 | 194 | #[test] 195 | fn clone_types() { 196 | let x = b"xyz"; 197 | 198 | let (mut w, r) = evmap::new(); 199 | w.insert(&*x, x); 200 | w.publish(); 201 | 202 | assert_eq!(r.get(&*x).map(|rs| rs.len()), Some(1)); 203 | assert_eq!( 204 | r.meta_get(&*x).map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 205 | Some((Some(1), ())) 206 | ); 207 | assert_eq!(r.get(&*x).map(|rs| rs.iter().any(|v| *v == x)), Some(true)); 208 | } 209 | 210 | #[test] 211 | #[cfg_attr(miri, ignore)] 212 | fn busybusybusy_fast() { 213 | busybusybusy_inner(false); 214 | } 215 | #[test] 216 | #[cfg_attr(miri, ignore)] 217 | fn busybusybusy_slow() { 218 | busybusybusy_inner(true); 219 | } 220 | 221 | fn busybusybusy_inner(slow: bool) { 222 | use std::thread; 223 | use std::time; 224 | 225 | let threads = 4; 226 | let mut n = 1000; 227 | if !slow { 228 | n *= 100; 229 | } 230 | let (mut w, r) = evmap::new(); 231 | w.publish(); 232 | 233 | let rs: Vec<_> = (0..threads) 234 | .map(|_| { 235 | let r = r.clone(); 236 | thread::spawn(move || { 237 | // rustfmt 238 | for i in 0..n { 239 | let i = i.into(); 240 | loop { 241 | let map = r.enter().unwrap(); 242 | let rs = map.get(&i); 243 | if rs.is_some() && slow { 244 | thread::sleep(time::Duration::from_millis(2)); 245 | } 246 | match rs { 247 | Some(rs) => { 248 | assert_eq!(rs.len(), 1); 249 | assert!(rs.capacity() >= rs.len()); 250 | assert_eq!(rs.iter().next().unwrap(), &i); 251 | break; 252 | } 253 | None => { 254 | thread::yield_now(); 255 | } 256 | } 257 | } 258 | } 259 | }) 260 | }) 261 | .collect(); 262 | 263 | for i in 0..n { 264 | w.insert(i, i); 265 | w.publish(); 266 | } 267 | 268 | for r in rs { 269 | r.join().unwrap(); 270 | } 271 | } 272 | 273 | #[test] 274 | #[cfg_attr(miri, ignore)] 275 | fn busybusybusy_heap() { 276 | use std::thread; 277 | 278 | let threads = 2; 279 | let n = 1000; 280 | let (mut w, r) = evmap::new::<_, Vec<_>>(); 281 | w.publish(); 282 | 283 | let rs: Vec<_> = (0..threads) 284 | .map(|_| { 285 | let r = r.clone(); 286 | thread::spawn(move || { 287 | for i in 0..n { 288 | let i = i.into(); 289 | loop { 290 | let map = r.enter().unwrap(); 291 | let rs = map.get(&i); 292 | match rs { 293 | Some(rs) => { 294 | assert_eq!(rs.len(), 1); 295 | assert_eq!(rs.iter().next().unwrap().len(), i); 296 | break; 297 | } 298 | None => { 299 | thread::yield_now(); 300 | } 301 | } 302 | } 303 | } 304 | }) 305 | }) 306 | .collect(); 307 | 308 | for i in 0..n { 309 | w.insert(i, (0..i).collect()); 310 | w.publish(); 311 | } 312 | 313 | for r in rs { 314 | r.join().unwrap(); 315 | } 316 | } 317 | 318 | #[test] 319 | fn minimal_query() { 320 | let (mut w, r) = evmap::new(); 321 | w.insert(1, "a"); 322 | w.publish(); 323 | w.insert(1, "b"); 324 | 325 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(1)); 326 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"a")).unwrap()); 327 | } 328 | 329 | #[test] 330 | fn clear_vs_empty() { 331 | let (mut w, r) = evmap::new::<_, ()>(); 332 | w.publish(); 333 | assert_eq!(r.get(&1).map(|rs| rs.len()), None); 334 | w.clear(1); 335 | w.publish(); 336 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(0)); 337 | w.remove_entry(1); 338 | w.publish(); 339 | assert_eq!(r.get(&1).map(|rs| rs.len()), None); 340 | // and again to test both apply_first and apply_second 341 | w.clear(1); 342 | w.publish(); 343 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(0)); 344 | w.remove_entry(1); 345 | w.publish(); 346 | assert_eq!(r.get(&1).map(|rs| rs.len()), None); 347 | } 348 | 349 | #[test] 350 | fn non_copy_values() { 351 | let (mut w, r) = evmap::new(); 352 | w.insert(1, "a".to_string()); 353 | assert_eq!(r.get(&1).map(|rs| rs.len()), None); 354 | 355 | w.publish(); 356 | 357 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(1)); 358 | assert!(r.get(&1).map(|rs| { rs.iter().any(|r| r == "a") }).unwrap()); 359 | 360 | w.insert(1, "b".to_string()); 361 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(1)); 362 | assert!(r.get(&1).map(|rs| { rs.iter().any(|r| r == "a") }).unwrap()); 363 | } 364 | 365 | #[test] 366 | fn non_minimal_query() { 367 | let (mut w, r) = evmap::new(); 368 | w.insert(1, "a"); 369 | w.insert(1, "b"); 370 | w.publish(); 371 | w.insert(1, "c"); 372 | 373 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(2)); 374 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"a")).unwrap()); 375 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"b")).unwrap()); 376 | } 377 | 378 | #[test] 379 | fn absorb_negative_immediate() { 380 | let (mut w, r) = evmap::new(); 381 | w.insert(1, "a"); 382 | w.insert(1, "b"); 383 | w.remove_value(1, "a"); 384 | w.publish(); 385 | 386 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(1)); 387 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"b")).unwrap()); 388 | } 389 | 390 | #[test] 391 | fn absorb_negative_later() { 392 | let (mut w, r) = evmap::new(); 393 | w.insert(1, "a"); 394 | w.insert(1, "b"); 395 | w.publish(); 396 | w.remove_value(1, "a"); 397 | w.publish(); 398 | 399 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(1)); 400 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"b")).unwrap()); 401 | } 402 | 403 | #[test] 404 | fn absorb_multi() { 405 | let (mut w, r) = evmap::new(); 406 | w.extend(vec![(1, "a"), (1, "b")]); 407 | w.publish(); 408 | 409 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(2)); 410 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"a")).unwrap()); 411 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"b")).unwrap()); 412 | 413 | w.remove_value(1, "a"); 414 | w.insert(1, "c"); 415 | w.remove_value(1, "c"); 416 | w.publish(); 417 | 418 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(1)); 419 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"b")).unwrap()); 420 | } 421 | 422 | #[test] 423 | fn empty() { 424 | let (mut w, r) = evmap::new(); 425 | w.insert(1, "a"); 426 | w.insert(1, "b"); 427 | w.insert(2, "c"); 428 | w.remove_entry(1); 429 | w.publish(); 430 | 431 | assert_eq!(r.get(&1).map(|rs| rs.len()), None); 432 | assert_eq!(r.get(&2).map(|rs| rs.len()), Some(1)); 433 | assert!(r.get(&2).map(|rs| rs.iter().any(|r| r == &"c")).unwrap()); 434 | } 435 | 436 | #[test] 437 | #[cfg(feature = "eviction")] 438 | fn empty_random() { 439 | let (mut w, r) = evmap::new(); 440 | w.insert(1, "a"); 441 | w.insert(1, "b"); 442 | w.insert(2, "c"); 443 | 444 | let mut rng = rand::thread_rng(); 445 | let removed: Vec<_> = w.empty_random(&mut rng, 1).map(|(&k, _)| k).collect(); 446 | w.publish(); 447 | 448 | // should only have one value set left 449 | assert_eq!(removed.len(), 1); 450 | let kept = 3 - removed[0]; 451 | // one of them must have gone away 452 | assert_eq!(r.len(), 1); 453 | assert!(!r.contains_key(&removed[0])); 454 | assert!(r.contains_key(&kept)); 455 | 456 | // remove the other one 457 | let removed: Vec<_> = w.empty_random(&mut rng, 100).map(|(&k, _)| k).collect(); 458 | w.publish(); 459 | 460 | assert_eq!(removed.len(), 1); 461 | assert_eq!(removed[0], kept); 462 | assert!(r.is_empty()); 463 | } 464 | 465 | #[test] 466 | #[cfg(feature = "eviction")] 467 | fn empty_random_multiple() { 468 | let (mut w, r) = evmap::new(); 469 | w.insert(1, "a"); 470 | w.insert(1, "b"); 471 | w.insert(2, "c"); 472 | 473 | let mut rng = rand::thread_rng(); 474 | let removed1: Vec<_> = w.empty_random(&mut rng, 1).map(|(&k, _)| k).collect(); 475 | let removed2: Vec<_> = w.empty_random(&mut rng, 1).map(|(&k, _)| k).collect(); 476 | w.publish(); 477 | 478 | assert_eq!(removed1.len(), 1); 479 | assert_eq!(removed2.len(), 1); 480 | assert_ne!(removed1[0], removed2[0]); 481 | assert!(r.is_empty()); 482 | } 483 | 484 | #[test] 485 | fn empty_post_refresh() { 486 | let (mut w, r) = evmap::new(); 487 | w.insert(1, "a"); 488 | w.insert(1, "b"); 489 | w.insert(2, "c"); 490 | w.publish(); 491 | w.remove_entry(1); 492 | w.publish(); 493 | 494 | assert_eq!(r.get(&1).map(|rs| rs.len()), None); 495 | assert_eq!(r.get(&2).map(|rs| rs.len()), Some(1)); 496 | assert!(r.get(&2).map(|rs| rs.iter().any(|r| r == &"c")).unwrap()); 497 | } 498 | 499 | #[test] 500 | fn clear() { 501 | let (mut w, r) = evmap::new(); 502 | w.insert(1, "a"); 503 | w.insert(1, "b"); 504 | w.insert(2, "c"); 505 | w.clear(1); 506 | w.publish(); 507 | 508 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(0)); 509 | assert_eq!(r.get(&2).map(|rs| rs.len()), Some(1)); 510 | 511 | w.clear(2); 512 | w.publish(); 513 | 514 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(0)); 515 | assert_eq!(r.get(&2).map(|rs| rs.len()), Some(0)); 516 | 517 | w.remove_entry(1); 518 | w.publish(); 519 | 520 | assert_eq!(r.get(&1).map(|rs| rs.len()), None); 521 | assert_eq!(r.get(&2).map(|rs| rs.len()), Some(0)); 522 | } 523 | 524 | #[test] 525 | fn replace() { 526 | let (mut w, r) = evmap::new(); 527 | w.insert(1, "a"); 528 | w.insert(1, "b"); 529 | w.insert(2, "c"); 530 | w.update(1, "x"); 531 | w.publish(); 532 | 533 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(1)); 534 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"x")).unwrap()); 535 | assert_eq!(r.get(&2).map(|rs| rs.len()), Some(1)); 536 | assert!(r.get(&2).map(|rs| rs.iter().any(|r| r == &"c")).unwrap()); 537 | } 538 | 539 | #[test] 540 | fn replace_post_refresh() { 541 | let (mut w, r) = evmap::new(); 542 | w.insert(1, "a"); 543 | w.insert(1, "b"); 544 | w.insert(2, "c"); 545 | w.publish(); 546 | w.update(1, "x"); 547 | w.publish(); 548 | 549 | assert_eq!(r.get(&1).map(|rs| rs.len()), Some(1)); 550 | assert!(r.get(&1).map(|rs| rs.iter().any(|r| r == &"x")).unwrap()); 551 | assert_eq!(r.get(&2).map(|rs| rs.len()), Some(1)); 552 | assert!(r.get(&2).map(|rs| rs.iter().any(|r| r == &"c")).unwrap()); 553 | } 554 | 555 | #[test] 556 | fn with_meta() { 557 | let (mut w, r) = evmap::Options::default() 558 | .with_meta(42) 559 | .construct::(); 560 | assert_eq!( 561 | r.meta_get(&1).map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 562 | None 563 | ); 564 | w.publish(); 565 | assert_eq!( 566 | r.meta_get(&1).map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 567 | Some((None, 42)) 568 | ); 569 | w.set_meta(43); 570 | assert_eq!( 571 | r.meta_get(&1).map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 572 | Some((None, 42)) 573 | ); 574 | w.publish(); 575 | assert_eq!( 576 | r.meta_get(&1).map(|(rs, m)| (rs.map(|rs| rs.len()), m)), 577 | Some((None, 43)) 578 | ); 579 | } 580 | 581 | #[test] 582 | fn map_into() { 583 | let (mut w, r) = evmap::new(); 584 | w.insert(1, "a"); 585 | w.insert(1, "b"); 586 | w.insert(2, "c"); 587 | w.publish(); 588 | w.insert(1, "x"); 589 | 590 | use std::collections::HashMap; 591 | let copy: HashMap<_, Vec<_>> = r.map_into(|&k, vs| (k, Vec::from_iter(vs.iter().cloned()))); 592 | 593 | assert_eq!(copy.len(), 2); 594 | assert!(copy.contains_key(&1)); 595 | assert!(copy.contains_key(&2)); 596 | assert_eq!(copy[&1].len(), 2); 597 | assert_eq!(copy[&2].len(), 1); 598 | assert!(copy[&1].contains(&"a")); 599 | assert!(copy[&1].contains(&"b")); 600 | assert!(copy[&2].contains(&"c")); 601 | } 602 | 603 | #[test] 604 | fn keys() { 605 | let (mut w, r) = evmap::new(); 606 | w.insert(1, "a"); 607 | w.insert(1, "b"); 608 | w.insert(2, "c"); 609 | w.publish(); 610 | w.insert(1, "x"); 611 | 612 | let mut keys = r.enter().unwrap().keys().copied().collect::>(); 613 | keys.sort(); 614 | 615 | assert_eq!(keys, vec![1, 2]); 616 | } 617 | 618 | #[test] 619 | fn values() { 620 | let (mut w, r) = evmap::new(); 621 | w.insert(1, "a"); 622 | w.insert(1, "b"); 623 | w.insert(2, "c"); 624 | w.publish(); 625 | w.insert(1, "x"); 626 | 627 | let mut values = r 628 | .enter() 629 | .unwrap() 630 | .values() 631 | .map(|value_bag| { 632 | let mut inner_items = value_bag.iter().copied().collect::>(); 633 | inner_items.sort(); 634 | inner_items 635 | }) 636 | .collect::>>(); 637 | values.sort_by_key(|value_vec| value_vec.len()); 638 | 639 | assert_eq!(values, vec![vec!["c"], vec!["a", "b"]]); 640 | } 641 | 642 | #[test] 643 | #[cfg_attr(miri, ignore)] 644 | fn clone_churn() { 645 | use std::thread; 646 | let (mut w, r) = evmap::new(); 647 | 648 | thread::spawn(move || loop { 649 | let r = r.clone(); 650 | if r.get(&1).is_some() { 651 | thread::yield_now(); 652 | } 653 | }); 654 | 655 | for i in 0..1000 { 656 | w.insert(1, i); 657 | w.publish(); 658 | } 659 | } 660 | 661 | #[test] 662 | #[cfg_attr(miri, ignore)] 663 | fn bigbag() { 664 | use std::thread; 665 | let (mut w, r) = evmap::new(); 666 | w.publish(); 667 | 668 | let ndistinct = 32; 669 | 670 | let jh = thread::spawn(move || loop { 671 | let map = if let Some(map) = r.enter() { 672 | map 673 | } else { 674 | break; 675 | }; 676 | if let Some(rs) = map.get(&1) { 677 | assert!(rs.len() <= ndistinct * (ndistinct - 1)); 678 | let mut found = true; 679 | for i in 0..ndistinct { 680 | if found { 681 | if !rs.contains(&[i][..]) { 682 | found = false; 683 | } 684 | } else { 685 | assert!(!found); 686 | } 687 | } 688 | assert_eq!(rs.into_iter().count(), rs.len()); 689 | drop(map); 690 | thread::yield_now(); 691 | } 692 | }); 693 | 694 | for _ in 0..64 { 695 | for i in 1..ndistinct { 696 | // add some duplicates too 697 | // total: 698 | for _ in 0..i { 699 | w.insert(1, vec![i]); 700 | w.publish(); 701 | } 702 | } 703 | for i in (1..ndistinct).rev() { 704 | for _ in 0..i { 705 | w.remove_value(1, vec![i]); 706 | w.fit(1); 707 | w.publish(); 708 | } 709 | } 710 | w.remove_entry(1); 711 | } 712 | 713 | drop(w); 714 | jh.join().unwrap(); 715 | } 716 | 717 | #[test] 718 | fn foreach() { 719 | let (mut w, r) = evmap::new(); 720 | w.insert(1, "a"); 721 | w.insert(1, "b"); 722 | w.insert(2, "c"); 723 | w.publish(); 724 | w.insert(1, "x"); 725 | 726 | let r = r.enter().unwrap(); 727 | for (k, vs) in &r { 728 | match k { 729 | 1 => { 730 | assert_eq!(vs.len(), 2); 731 | assert!(vs.contains(&"a")); 732 | assert!(vs.contains(&"b")); 733 | } 734 | 2 => { 735 | assert_eq!(vs.len(), 1); 736 | assert!(vs.contains(&"c")); 737 | } 738 | _ => unreachable!(), 739 | } 740 | } 741 | } 742 | 743 | #[test] 744 | fn retain() { 745 | // do same operations on a plain vector 746 | // to verify retain implementation 747 | let mut v = Vec::new(); 748 | let (mut w, r) = evmap::new(); 749 | 750 | for i in 0..50 { 751 | w.insert(0, i); 752 | v.push(i); 753 | } 754 | 755 | fn is_even(num: &i32, _: bool) -> bool { 756 | num % 2 == 0 757 | } 758 | 759 | unsafe { w.retain(0, is_even) }.publish(); 760 | v.retain(|i| is_even(i, false)); 761 | 762 | let mut vs = r 763 | .get(&0) 764 | .map(|nums| Vec::from_iter(nums.iter().cloned())) 765 | .unwrap(); 766 | vs.sort(); 767 | assert_eq!(v, &*vs); 768 | } 769 | 770 | #[test] 771 | fn get_one() { 772 | let x = ('x', 42); 773 | 774 | let (mut w, r) = evmap::new(); 775 | 776 | w.insert(x.0, x); 777 | w.insert(x.0, x); 778 | 779 | assert_match!(r.get_one(&x.0), None); 780 | 781 | w.publish(); 782 | 783 | assert_match!(r.get_one(&x.0).as_deref(), Some(('x', 42))); 784 | } 785 | 786 | #[test] 787 | fn insert_remove_value() { 788 | let x = 'x'; 789 | 790 | let (mut w, r) = evmap::new(); 791 | 792 | w.insert(x, x); 793 | 794 | w.remove_value(x, x); 795 | w.publish(); 796 | 797 | // There are no more values associated with this key 798 | assert!(r.get(&x).is_some()); 799 | assert_match!(r.get(&x).as_deref().unwrap().len(), 0); 800 | 801 | // But the map is NOT empty! It still has an empty bag for the key! 802 | assert!(!r.is_empty()); 803 | assert_eq!(r.len(), 1); 804 | } 805 | 806 | #[test] 807 | fn insert_remove_entry() { 808 | let x = 'x'; 809 | 810 | let (mut w, r) = evmap::new(); 811 | 812 | w.insert(x, x); 813 | 814 | w.remove_entry(x); 815 | w.publish(); 816 | 817 | assert!(r.is_empty()); 818 | assert!(r.get(&x).is_none()); 819 | } 820 | -------------------------------------------------------------------------------- /tests/quick.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(miri))] 2 | 3 | extern crate evmap; 4 | 5 | use evmap::handles::{ReadHandle, WriteHandle}; 6 | 7 | extern crate quickcheck; 8 | #[macro_use(quickcheck)] 9 | extern crate quickcheck_macros; 10 | 11 | use quickcheck::Arbitrary; 12 | use quickcheck::Gen; 13 | 14 | use rand::Rng; 15 | use std::cmp::{min, Ord}; 16 | use std::collections::{HashMap, HashSet}; 17 | use std::fmt::Debug; 18 | use std::hash::{BuildHasher, Hash}; 19 | use std::ops::Deref; 20 | 21 | fn set<'a, T: 'a, I>(iter: I) -> HashSet 22 | where 23 | I: IntoIterator, 24 | T: Copy + Hash + Eq, 25 | { 26 | iter.into_iter().cloned().collect() 27 | } 28 | 29 | #[quickcheck] 30 | fn contains(insert: Vec) -> bool { 31 | let (mut w, r) = evmap::new(); 32 | for &key in &insert { 33 | w.insert(key, ()); 34 | } 35 | w.publish(); 36 | 37 | insert.iter().all(|&key| r.get(&key).is_some()) 38 | } 39 | 40 | #[quickcheck] 41 | fn contains_not(insert: Vec, not: Vec) -> bool { 42 | let (mut w, r) = evmap::new(); 43 | for &key in &insert { 44 | w.insert(key, ()); 45 | } 46 | w.publish(); 47 | 48 | let nots = &set(¬) - &set(&insert); 49 | nots.iter().all(|&key| r.get(&key).is_none()) 50 | } 51 | 52 | #[quickcheck] 53 | fn insert_empty(insert: Vec, remove: Vec) -> bool { 54 | let (mut w, r) = evmap::new(); 55 | for &key in &insert { 56 | w.insert(key, ()); 57 | } 58 | w.publish(); 59 | for &key in &remove { 60 | w.remove_entry(key); 61 | } 62 | w.publish(); 63 | let elements = &set(&insert) - &set(&remove); 64 | r.len() == elements.len() 65 | && r.enter().iter().map(|r| r.keys()).flatten().count() == elements.len() 66 | && elements.iter().all(|k| r.get(k).is_some()) 67 | } 68 | 69 | use Op::*; 70 | #[derive(Copy, Clone, Debug)] 71 | enum Op { 72 | Add(K, V), 73 | Remove(K), 74 | RemoveValue(K, V), 75 | Refresh, 76 | } 77 | 78 | impl Arbitrary for Op 79 | where 80 | K: Arbitrary, 81 | V: Arbitrary, 82 | { 83 | fn arbitrary(g: &mut G) -> Self { 84 | match g.gen::() % 4 { 85 | 0 => Add(K::arbitrary(g), V::arbitrary(g)), 86 | 1 => Remove(K::arbitrary(g)), 87 | 2 => RemoveValue(K::arbitrary(g), V::arbitrary(g)), 88 | _ => Refresh, 89 | } 90 | } 91 | } 92 | 93 | fn do_ops( 94 | ops: &[Op], 95 | evmap: &mut WriteHandle, 96 | write_ref: &mut HashMap>, 97 | read_ref: &mut HashMap>, 98 | ) where 99 | K: Hash + Eq + Clone, 100 | V: Clone + Eq + Hash, 101 | S: BuildHasher + Clone, 102 | { 103 | for op in ops { 104 | match *op { 105 | Add(ref k, ref v) => { 106 | evmap.insert(k.clone(), v.clone()); 107 | write_ref 108 | .entry(k.clone()) 109 | .or_insert_with(Vec::new) 110 | .push(v.clone()); 111 | } 112 | Remove(ref k) => { 113 | evmap.remove_entry(k.clone()); 114 | write_ref.remove(k); 115 | } 116 | RemoveValue(ref k, ref v) => { 117 | evmap.remove_value(k.clone(), v.clone()); 118 | write_ref.get_mut(k).and_then(|values| { 119 | values 120 | .iter_mut() 121 | .position(|value| value == v) 122 | .and_then(|pos| Some(values.swap_remove(pos))) 123 | }); 124 | } 125 | Refresh => { 126 | evmap.publish(); 127 | *read_ref = write_ref.clone(); 128 | } 129 | } 130 | } 131 | } 132 | 133 | fn assert_maps_equivalent(a: &ReadHandle, b: &HashMap>) -> bool 134 | where 135 | K: Clone + Hash + Eq + Debug, 136 | V: Hash + Eq + Debug + Ord + Copy, 137 | S: BuildHasher, 138 | { 139 | assert_eq!(a.len(), b.len()); 140 | for key in a.enter().iter().map(|r| r.keys()).flatten() { 141 | assert!(b.contains_key(key), "b does not contain {:?}", key); 142 | } 143 | for key in b.keys() { 144 | assert!(a.get(key).is_some(), "a does not contain {:?}", key); 145 | } 146 | let guard = if let Some(guard) = a.enter() { 147 | guard 148 | } else { 149 | // Reference was empty, ReadHandle was destroyed, so all is well. Maybe. 150 | return true; 151 | }; 152 | for key in guard.keys() { 153 | let mut ev_map_values: Vec = guard.get(key).unwrap().iter().copied().collect(); 154 | ev_map_values.sort(); 155 | let mut map_values = b[key].clone(); 156 | map_values.sort(); 157 | assert_eq!(ev_map_values, map_values); 158 | } 159 | true 160 | } 161 | 162 | /// quickcheck Arbitrary adaptor -- make a larger vec 163 | #[derive(Clone, Debug)] 164 | struct Large(T); 165 | 166 | impl Deref for Large { 167 | type Target = T; 168 | fn deref(&self) -> &T { 169 | &self.0 170 | } 171 | } 172 | 173 | impl Arbitrary for Large> 174 | where 175 | T: Arbitrary, 176 | { 177 | fn arbitrary(g: &mut G) -> Self { 178 | let len = g.next_u32() % (g.size() * 10) as u32; 179 | Large((0..len).map(|_| T::arbitrary(g)).collect()) 180 | } 181 | 182 | fn shrink(&self) -> Box> { 183 | Box::new((**self).shrink().map(Large)) 184 | } 185 | } 186 | 187 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 188 | struct Alphabet(String); 189 | 190 | impl Deref for Alphabet { 191 | type Target = String; 192 | fn deref(&self) -> &String { 193 | &self.0 194 | } 195 | } 196 | 197 | const ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; 198 | 199 | impl Arbitrary for Alphabet { 200 | fn arbitrary(g: &mut G) -> Self { 201 | let len = g.next_u32() % g.size() as u32; 202 | let len = min(len, 16); 203 | Alphabet( 204 | (0..len) 205 | .map(|_| ALPHABET[g.next_u32() as usize % ALPHABET.len()] as char) 206 | .collect(), 207 | ) 208 | } 209 | 210 | fn shrink(&self) -> Box> { 211 | Box::new((**self).shrink().map(Alphabet)) 212 | } 213 | } 214 | 215 | #[quickcheck] 216 | fn operations_i8(ops: Large>>) -> bool { 217 | let (mut w, r) = evmap::new(); 218 | let mut write_ref = HashMap::new(); 219 | let mut read_ref = HashMap::new(); 220 | do_ops(&ops, &mut w, &mut write_ref, &mut read_ref); 221 | assert_maps_equivalent(&r, &read_ref); 222 | 223 | w.publish(); 224 | assert_maps_equivalent(&r, &write_ref) 225 | } 226 | 227 | #[quickcheck] 228 | fn operations_string(ops: Vec>) -> bool { 229 | // safety: Alphabet is just String, and has stable Hash + Eq. 230 | let (mut w, r) = unsafe { evmap::new_assert_stable() }; 231 | let mut write_ref = HashMap::new(); 232 | let mut read_ref = HashMap::new(); 233 | do_ops(&ops, &mut w, &mut write_ref, &mut read_ref); 234 | assert_maps_equivalent(&r, &read_ref); 235 | 236 | w.publish(); 237 | assert_maps_equivalent(&r, &write_ref) 238 | } 239 | 240 | #[quickcheck] 241 | fn keys_values(ops: Large>>) -> bool { 242 | let (mut w, r) = evmap::new(); 243 | let mut write_ref = HashMap::new(); 244 | let mut read_ref = HashMap::new(); 245 | do_ops(&ops, &mut w, &mut write_ref, &mut read_ref); 246 | 247 | if let Some(read_guard) = r.enter() { 248 | let (mut w_visit, r_visit) = evmap::new(); 249 | for (k, v_set) in read_guard.keys().zip(read_guard.values()) { 250 | assert!(read_guard[k].iter().all(|v| v_set.contains(v))); 251 | assert!(v_set.iter().all(|v| read_guard[k].contains(v))); 252 | 253 | assert!(!r_visit.contains_key(k)); 254 | 255 | // If the value set is empty, we still need to add the key. 256 | // But we need to add the key with an empty value bag. 257 | // Just add something and then remove the value, but leave the bag. 258 | if v_set.is_empty() { 259 | w_visit.insert(*k, 0); 260 | w_visit.remove_value(*k, 0); 261 | } else { 262 | for value in v_set { 263 | w_visit.insert(*k, *value); 264 | } 265 | } 266 | w_visit.publish(); 267 | } 268 | assert_eq!(r_visit.len(), read_ref.len()); 269 | } 270 | true 271 | } 272 | --------------------------------------------------------------------------------