├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── gnuradio │ ├── CMakeLists.txt │ └── copy_flowgraph.cpp ├── sdr.rs └── tags.rs ├── src ├── asynchronous.rs ├── double_mapped_buffer │ ├── double_mapped_buffer.rs │ ├── mod.rs │ ├── unix.rs │ └── windows.rs ├── generic.rs ├── lib.rs ├── nonblocking.rs └── sync.rs └── tests ├── async.rs ├── nonblocking.rs ├── sync.rs └── tags.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | test-linux: 7 | name: Unit Tests Linux 8 | runs-on: ubuntu-latest 9 | env: 10 | RUST_BACKTRACE: full 11 | steps: 12 | - name: Checkout sources 13 | uses: actions/checkout@v2 14 | 15 | - name: Install stable toolchain 16 | uses: actions-rs/toolchain@v1 17 | with: 18 | profile: minimal 19 | toolchain: stable 20 | override: true 21 | components: rustfmt, clippy 22 | 23 | - name: Run cargo fmt 24 | uses: actions-rs/cargo@v1 25 | with: 26 | command: fmt 27 | args: --all -- --check 28 | 29 | - name: Run cargo clippy 30 | uses: actions-rs/cargo@v1 31 | with: 32 | command: clippy 33 | args: --all-targets -- -D warnings 34 | 35 | - name: Run cargo test 36 | uses: actions-rs/cargo@v1 37 | with: 38 | command: test 39 | args: --all-targets -- --nocapture 40 | 41 | test-macos: 42 | name: Unit Tests macOS 43 | runs-on: macos-latest 44 | env: 45 | RUST_BACKTRACE: full 46 | steps: 47 | - name: Checkout sources 48 | uses: actions/checkout@v2 49 | 50 | - name: Install stable toolchain 51 | uses: actions-rs/toolchain@v1 52 | with: 53 | profile: minimal 54 | toolchain: stable 55 | override: true 56 | components: clippy 57 | 58 | - name: Run cargo clippy 59 | uses: actions-rs/cargo@v1 60 | with: 61 | command: clippy 62 | args: --all-targets -- -D warnings 63 | 64 | - name: Run cargo test 65 | uses: actions-rs/cargo@v1 66 | with: 67 | command: test 68 | args: --all-targets -- --nocapture 69 | 70 | test-windows: 71 | name: Unit Test Windows 72 | runs-on: windows-latest 73 | env: 74 | RUST_BACKTRACE: full 75 | steps: 76 | - name: Checkout sources 77 | uses: actions/checkout@v2 78 | 79 | - name: Install stable toolchain 80 | uses: actions-rs/toolchain@v1 81 | with: 82 | profile: minimal 83 | toolchain: stable 84 | override: true 85 | components: clippy 86 | 87 | - name: Run cargo clippy 88 | uses: actions-rs/cargo@v1 89 | with: 90 | command: clippy 91 | args: --all-targets -- -D warnings 92 | 93 | - name: Run cargo test 94 | uses: actions-rs/cargo@v1 95 | with: 96 | command: test 97 | args: --all-targets -- --nocapture 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vmcircbuffer" 3 | version = "0.0.10" 4 | authors = ["Bastian Bloessl "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | homepage = "https://www.futuresdr.org" 8 | repository = "https://github.com/futuresdr/vmcircbuffer/" 9 | readme = "README.md" 10 | description = "Double Mapped Circular Buffer" 11 | keywords = ["sdr", "dsp", "real-time", "async"] 12 | categories = ["asynchronous", "concurrency", "hardware-support", "science"] 13 | 14 | [features] 15 | default = ["async", "sync", "nonblocking", "generic"] 16 | async = ["futures", "generic"] 17 | sync = ["generic"] 18 | nonblocking = ["generic"] 19 | generic = [] 20 | 21 | [[example]] 22 | name = "sdr" 23 | required-features = ["sync"] 24 | 25 | [[test]] 26 | name = "async" 27 | required-features = ["async"] 28 | 29 | [[test]] 30 | name = "sync" 31 | required-features = ["sync"] 32 | 33 | [[test]] 34 | name = "nonblocking" 35 | required-features = ["nonblocking"] 36 | 37 | [dependencies] 38 | futures = { version = "0.3.21", optional = true } 39 | once_cell = "1.12" 40 | slab = "0.4.6" 41 | thiserror = "1.0" 42 | 43 | [target.'cfg(unix)'.dependencies] 44 | libc = "0.2.126" 45 | 46 | [target.'cfg(windows)'.dependencies] 47 | winapi = { version = "0.3", features = ["sysinfoapi", "winbase", "handleapi", "memoryapi"] } 48 | 49 | [dev-dependencies] 50 | rand = "0.8.5" 51 | smol = "1.2.5" 52 | 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Double Mapped Circular Buffer 2 | 3 | - Thread-safe. 4 | - Supports multiple readers. 5 | - Generic over the item type. 6 | - Provides access to all items (not n-1). 7 | - Supports Linux, macOS, Windows, and Android. 8 | - Sync, async, and non-blocking implementations. 9 | - Generic variant that allows specifying custom `Notifiers` to ease integration. 10 | - Underlying data structure (i.e., `DoubleMappedBuffer`) is exported to allow custom implementations. 11 | 12 | [![Crates.io][crates-badge]][crates-url] 13 | [![Apache 2.0 licensed][apache-badge]][apache-url] 14 | [![Build Status][actions-badge]][actions-url] 15 | 16 | [crates-badge]: https://img.shields.io/crates/v/vmcircbuffer.svg 17 | [crates-url]: https://crates.io/crates/vmcircbuffer 18 | [apache-badge]: https://img.shields.io/badge/license-Apache%202-blue 19 | [apache-url]: https://github.com/futuresdr/vmcircbuffer/blob/main/LICENSE 20 | [actions-badge]: https://github.com/futuresdr/vmcircbuffer/workflows/CI/badge.svg 21 | [actions-url]: https://github.com/futuresdr/vmcircbuffer/actions?query=workflow%3ACI+branch%3Amain 22 | 23 | ## Overview 24 | 25 | This circular buffer implementation maps the underlying buffer twice, 26 | back-to-back into the virtual address space of the process. This arrangement 27 | allows the circular buffer to present the available data sequentially, (i.e., as 28 | a slice) without having to worry about wrapping. 29 | 30 | ## Example 31 | 32 | ```rust 33 | use vmcircbuffer::sync; 34 | 35 | let mut w = sync::Circular::new::()?; 36 | let mut r = w.add_reader(); 37 | 38 | // delay producing by 1 sec 39 | let now = std::time::Instant::now(); 40 | let delay = std::time::Duration::from_millis(1000); 41 | 42 | // producer thread 43 | std::thread::spawn(move || { 44 | std::thread::sleep(delay); 45 | let w_buff = w.slice(); 46 | for v in w_buff.iter_mut() { 47 | *v = 23; 48 | } 49 | let l = w_buff.len(); 50 | w.produce(l); 51 | }); 52 | 53 | // blocks until data becomes available 54 | let r_buff = r.slice().unwrap(); 55 | assert!(now.elapsed() > delay); 56 | for v in r_buff { 57 | assert_eq!(*v, 23); 58 | } 59 | let l = r_buff.len(); 60 | r.consume(l); 61 | ``` 62 | 63 | ## Contributions 64 | 65 | Unless you explicitly state otherwise, any contribution intentionally submitted 66 | for inclusion in the project, shall be licensed as Apache 2.0, without any 67 | additional terms or conditions. 68 | -------------------------------------------------------------------------------- /examples/gnuradio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(NullRandFlowgraph) 2 | 3 | cmake_minimum_required(VERSION 3.8) 4 | 5 | find_package(Gnuradio "3.10" REQUIRED COMPONENTS blocks) 6 | 7 | add_executable(copy_flowgraph copy_flowgraph.cpp) 8 | 9 | target_link_libraries(copy_flowgraph 10 | gnuradio::gnuradio-runtime 11 | gnuradio::gnuradio-pmt 12 | gnuradio::gnuradio-blocks 13 | ) 14 | -------------------------------------------------------------------------------- /examples/gnuradio/copy_flowgraph.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace gr; 15 | 16 | float get_random() { 17 | static std::default_random_engine e; 18 | static std::uniform_real_distribution<> dis(0, 1); // rage 0 - 1 19 | return dis(e); 20 | } 21 | 22 | 23 | int main(int argc, char **argv) { 24 | int n_copy = 200; 25 | uint64_t n_samples = 20000000; 26 | 27 | std::vector vec; 28 | for (int i = 0; i != n_samples; i++) { 29 | vec.emplace_back(get_random()); 30 | } 31 | 32 | auto tb = gr::make_top_block("copy"); 33 | 34 | auto src = blocks::vector_source_f::make(vec); 35 | auto prev = blocks::copy::make(sizeof(float)); 36 | tb->connect(src, 0, prev, 0); 37 | 38 | for (int stage = 1; stage < n_copy; stage++) { 39 | auto block = blocks::copy::make(sizeof(float)); 40 | tb->connect(prev, 0, block, 0); 41 | prev = block; 42 | } 43 | 44 | auto sink = blocks::vector_sink_f::make(1, n_samples); 45 | tb->connect(prev, 0, sink, 0); 46 | 47 | auto start = std::chrono::high_resolution_clock::now(); 48 | tb->run(); 49 | auto finish = std::chrono::high_resolution_clock::now(); 50 | auto time = 51 | std::chrono::duration_cast(finish - start) 52 | .count() / 53 | 1e9; 54 | 55 | std::cout << boost::format("%1$20.15f") % time << std::endl; 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /examples/sdr.rs: -------------------------------------------------------------------------------- 1 | use std::iter::repeat_with; 2 | use std::marker::PhantomData; 3 | use std::sync::{Arc, Barrier}; 4 | use std::thread; 5 | use std::thread::JoinHandle; 6 | use std::time; 7 | 8 | use vmcircbuffer::sync::Circular; 9 | use vmcircbuffer::sync::Reader; 10 | 11 | const MIN_ITEMS: usize = 16384; 12 | 13 | struct VectorSource; 14 | impl VectorSource { 15 | #[allow(clippy::new_ret_no_self)] 16 | pub fn new( 17 | input: Vec, 18 | ) -> Source Option + Send + Sync + 'static, A> 19 | where 20 | A: Send + Sync + Clone + 'static, 21 | { 22 | let mut i = 0; 23 | let n_samples = input.len(); 24 | Source::new(move |s: &mut [A]| -> Option { 25 | if i < n_samples { 26 | let len = std::cmp::min(s.len(), n_samples - i); 27 | s[0..len].clone_from_slice(&input[i..i + len]); 28 | i += len; 29 | Some(len) 30 | } else { 31 | None 32 | } 33 | }) 34 | } 35 | } 36 | 37 | #[allow(clippy::type_complexity)] 38 | struct Source Option + Send + Sync + 'static, A: Send + Sync + 'static> 39 | { 40 | f: Option, 41 | _p: PhantomData, 42 | } 43 | 44 | impl Option + Send + Sync + 'static, A: Send + Sync> Source { 45 | pub fn new(f: F) -> Source { 46 | Source { 47 | f: Some(f), 48 | _p: PhantomData, 49 | } 50 | } 51 | 52 | pub fn run(&mut self, barrier: Arc) -> (Reader, JoinHandle<()>) { 53 | let mut w = Circular::with_capacity::(MIN_ITEMS).unwrap(); 54 | let r = w.add_reader(); 55 | let mut f = self.f.take().unwrap(); 56 | 57 | let handle = thread::spawn(move || { 58 | barrier.wait(); 59 | 60 | loop { 61 | let s = w.slice(); 62 | if let Some(n) = f(s) { 63 | w.produce(n); 64 | } else { 65 | break; 66 | } 67 | } 68 | }); 69 | 70 | (r, handle) 71 | } 72 | } 73 | 74 | struct CopyBlock; 75 | impl CopyBlock { 76 | #[allow(clippy::new_ret_no_self)] 77 | pub fn new() -> Middle 78 | where 79 | A: Send + Sync + Clone + 'static, 80 | { 81 | Middle::new(|input: &[A], output: &mut [A]| output.clone_from_slice(input)) 82 | } 83 | } 84 | 85 | #[allow(clippy::type_complexity)] 86 | struct Middle 87 | where 88 | F: FnMut(&[A], &mut [B]) + Send + Sync + 'static, 89 | A: Send + Sync + 'static, 90 | B: Send + Sync + 'static, 91 | { 92 | f: Option, 93 | _p1: PhantomData, 94 | _p2: PhantomData, 95 | } 96 | 97 | impl Middle 98 | where 99 | F: FnMut(&[A], &mut [B]) + Send + Sync + 'static, 100 | A: Send + Sync + 'static, 101 | B: Send + Sync + 'static, 102 | { 103 | pub fn new(f: F) -> Middle { 104 | Middle { 105 | f: Some(f), 106 | _p1: PhantomData, 107 | _p2: PhantomData, 108 | } 109 | } 110 | 111 | pub fn run( 112 | &mut self, 113 | mut reader: Reader, 114 | barrier: Arc, 115 | ) -> (Reader, JoinHandle<()>) { 116 | let mut w = Circular::with_capacity::(MIN_ITEMS).unwrap(); 117 | let r = w.add_reader(); 118 | let mut f = self.f.take().unwrap(); 119 | 120 | let handle = thread::spawn(move || { 121 | barrier.wait(); 122 | 123 | while let Some(input) = reader.slice() { 124 | let output = w.slice(); 125 | let n = std::cmp::min(input.len(), output.len()); 126 | f(&input[0..n], &mut output[0..n]); 127 | reader.consume(n); 128 | w.produce(n); 129 | } 130 | }); 131 | 132 | (r, handle) 133 | } 134 | } 135 | 136 | struct Sink { 137 | items: Option>, 138 | } 139 | 140 | impl Sink { 141 | pub fn new(capacity: usize) -> Sink { 142 | Sink { 143 | items: Some(Vec::with_capacity(capacity)), 144 | } 145 | } 146 | 147 | pub fn run(&mut self, mut r: Reader, barrier: Arc) -> JoinHandle> { 148 | let mut items = self.items.take().unwrap(); 149 | 150 | thread::spawn(move || { 151 | barrier.wait(); 152 | 153 | while let Some(s) = r.slice() { 154 | items.extend_from_slice(s); 155 | let l = s.len(); 156 | r.consume(l); 157 | } 158 | 159 | items 160 | }) 161 | } 162 | } 163 | 164 | fn main() { 165 | let n_samples = 20_000_000; 166 | let input: Vec = repeat_with(rand::random::).take(n_samples).collect(); 167 | 168 | let n_copy = 200; 169 | let barrier = Arc::new(Barrier::new(n_copy + 3)); 170 | 171 | let mut src = VectorSource::new(input.clone()); 172 | let (mut reader, _) = src.run(Arc::clone(&barrier)); 173 | 174 | for _ in 0..n_copy { 175 | let mut cpy = CopyBlock::new::(); 176 | let (a, _) = cpy.run(reader, Arc::clone(&barrier)); 177 | reader = a; 178 | } 179 | 180 | let mut snk = Sink::new(n_samples); 181 | let handle = snk.run(reader, Arc::clone(&barrier)); 182 | 183 | let now = time::Instant::now(); 184 | barrier.wait(); 185 | let output = handle.join().unwrap(); 186 | let elapsed = now.elapsed(); 187 | assert_eq!(input, output); 188 | println!("data matches"); 189 | println!("runtime (in s): {}", elapsed.as_secs_f64()); 190 | } 191 | -------------------------------------------------------------------------------- /examples/tags.rs: -------------------------------------------------------------------------------- 1 | use vmcircbuffer::generic::Circular; 2 | use vmcircbuffer::generic::Metadata; 3 | use vmcircbuffer::generic::Notifier; 4 | 5 | struct MyNotifier; 6 | 7 | impl Notifier for MyNotifier { 8 | fn arm(&mut self) {} 9 | fn notify(&mut self) {} 10 | } 11 | 12 | #[derive(Clone)] 13 | struct Tag { 14 | item: usize, 15 | data: String, 16 | } 17 | 18 | struct MyMetadata { 19 | tags: Vec, 20 | } 21 | 22 | impl Metadata for MyMetadata { 23 | type Item = Tag; 24 | 25 | fn new() -> Self { 26 | MyMetadata { tags: Vec::new() } 27 | } 28 | fn add(&mut self, offset: usize, mut tags: Vec) { 29 | for t in tags.iter_mut() { 30 | t.item += offset; 31 | } 32 | self.tags.append(&mut tags); 33 | } 34 | fn get(&self) -> Vec { 35 | self.tags.clone() 36 | } 37 | fn consume(&mut self, items: usize) { 38 | self.tags.retain(|x| x.item >= items); 39 | for t in self.tags.iter_mut() { 40 | t.item -= items; 41 | } 42 | } 43 | } 44 | 45 | fn main() { 46 | let mut w = Circular::with_capacity::(1).unwrap(); 47 | 48 | let mut r = w.add_reader(MyNotifier, MyNotifier); 49 | 50 | let out = w.slice(false); 51 | for v in out.iter_mut() { 52 | *v = 123; 53 | } 54 | let len = out.len(); 55 | 56 | w.produce( 57 | len, 58 | vec![ 59 | Tag { 60 | item: 0, 61 | data: String::from("first"), 62 | }, 63 | Tag { 64 | item: 10, 65 | data: String::from("tenth"), 66 | }, 67 | ], 68 | ); 69 | 70 | let (i, tags) = r.slice(false).unwrap(); 71 | 72 | assert_eq!(i[0], 123); 73 | assert_eq!(tags.len(), 2); 74 | assert_eq!(tags[0].data, String::from("first")); 75 | assert_eq!(tags[0].item, 0); 76 | assert_eq!(tags[1].data, String::from("tenth")); 77 | assert_eq!(tags[1].item, 10); 78 | 79 | r.consume(5); 80 | let (i, tags) = r.slice(false).unwrap(); 81 | 82 | assert_eq!(i[0], 123); 83 | assert_eq!(tags.len(), 1); 84 | assert_eq!(tags[0].data, String::from("tenth")); 85 | assert_eq!(tags[0].item, 5); 86 | } 87 | -------------------------------------------------------------------------------- /src/asynchronous.rs: -------------------------------------------------------------------------------- 1 | //! Async Circular Buffer that can `await` until buffer space becomes available. 2 | //! 3 | //! The [Writer](crate::asynchronous::Writer) and 4 | //! [Reader](crate::asynchronous::Reader) have async `slice()` functions to 5 | //! await until buffer space or data becomes available, respectively. 6 | 7 | use futures::channel::mpsc::{channel, Receiver, Sender}; 8 | use futures::StreamExt; 9 | use std::slice; 10 | 11 | use crate::generic; 12 | use crate::generic::CircularError; 13 | use crate::generic::NoMetadata; 14 | use crate::generic::Notifier; 15 | 16 | struct AsyncNotifier { 17 | chan: Sender<()>, 18 | armed: bool, 19 | } 20 | 21 | impl Notifier for AsyncNotifier { 22 | fn arm(&mut self) { 23 | self.armed = true; 24 | } 25 | fn notify(&mut self) { 26 | if self.armed { 27 | let _ = self.chan.try_send(()); 28 | self.armed = false; 29 | } 30 | } 31 | } 32 | 33 | /// Builder for the *async* circular buffer implementation. 34 | pub struct Circular; 35 | 36 | impl Circular { 37 | /// Create a buffer for items of type `T` with minimal capacity (usually a page size). 38 | /// 39 | /// The actual size is the least common multiple of the page size and the size of `T`. 40 | #[allow(clippy::new_ret_no_self)] 41 | pub fn new() -> Result, CircularError> { 42 | Self::with_capacity(0) 43 | } 44 | 45 | /// Create a buffer that can hold at least `min_items` items of type `T`. 46 | /// 47 | /// The size is the least common multiple of the page size and the size of `T`. 48 | pub fn with_capacity(min_items: usize) -> Result, CircularError> { 49 | let writer = generic::Circular::with_capacity(min_items)?; 50 | 51 | let (tx, rx) = channel(1); 52 | Ok(Writer { 53 | writer, 54 | writer_sender: tx, 55 | chan: rx, 56 | }) 57 | } 58 | } 59 | 60 | /// Writer for a blocking circular buffer with items of type `T`. 61 | pub struct Writer { 62 | writer_sender: Sender<()>, 63 | chan: Receiver<()>, 64 | writer: generic::Writer, 65 | } 66 | 67 | impl Writer { 68 | /// Add a reader to the buffer. 69 | /// 70 | /// All readers can block the buffer, i.e., the writer will only overwrite 71 | /// data, if data was [consume](crate::asynchronous::Reader::consume)ed by 72 | /// all readers. 73 | pub fn add_reader(&self) -> Reader { 74 | let w_notifier = AsyncNotifier { 75 | chan: self.writer_sender.clone(), 76 | armed: false, 77 | }; 78 | 79 | let (tx, rx) = channel(1); 80 | let r_notififer = AsyncNotifier { 81 | chan: tx, 82 | armed: false, 83 | }; 84 | 85 | let reader = self.writer.add_reader(r_notififer, w_notifier); 86 | Reader { reader, chan: rx } 87 | } 88 | 89 | /// Get a slice to the available output space. 90 | /// 91 | /// The future resolves once output space is available. 92 | /// The returned slice will never be empty. 93 | pub async fn slice(&mut self) -> &mut [T] { 94 | // ugly workaround for borrow-checker problem 95 | // https://github.com/rust-lang/rust/issues/21906 96 | let (p, s) = loop { 97 | match self.writer.slice(true) { 98 | [] => { 99 | let _ = self.chan.next().await; 100 | } 101 | s => break (s.as_mut_ptr(), s.len()), 102 | } 103 | }; 104 | unsafe { slice::from_raw_parts_mut(p, s) } 105 | } 106 | 107 | /// Get a slice to the free slots, available for writing. 108 | /// 109 | /// This function return immediately. The slice might be [empty](slice::is_empty). 110 | pub fn try_slice(&mut self) -> &mut [T] { 111 | self.writer.slice(false) 112 | } 113 | 114 | /// Indicates that `n` items were written to the output buffer. 115 | /// 116 | /// It is ok if `n` is zero. 117 | /// 118 | /// # Panics 119 | /// 120 | /// If produced more than space was available in the last provided slice. 121 | pub fn produce(&mut self, n: usize) { 122 | self.writer.produce(n, Vec::new()); 123 | } 124 | } 125 | 126 | /// Reader for an async circular buffer with items of type `T`. 127 | pub struct Reader { 128 | chan: Receiver<()>, 129 | reader: generic::Reader, 130 | } 131 | 132 | impl Reader { 133 | /// Blocks until there is data to read or until the writer is dropped. 134 | /// 135 | /// If all data is read and the writer is dropped, all following calls will 136 | /// return `None`. If `Some` is returned, the contained slice is never empty. 137 | pub async fn slice(&mut self) -> Option<&[T]> { 138 | // ugly workaround for borrow-checker problem 139 | // https://github.com/rust-lang/rust/issues/21906 140 | let r = loop { 141 | match self.reader.slice(true) { 142 | Some(([], _)) => { 143 | let _ = self.chan.next().await; 144 | } 145 | Some((s, _)) => break Some((s.as_ptr(), s.len())), 146 | None => break None, 147 | } 148 | }; 149 | 150 | if let Some((p, s)) = r { 151 | unsafe { Some(slice::from_raw_parts(p, s)) } 152 | } else { 153 | None 154 | } 155 | } 156 | 157 | /// Checks if there is data to read. 158 | /// 159 | /// If all data is read and the writer is dropped, all following calls will 160 | /// return `None`. If there is no data to read, `Some` is returned with an 161 | /// empty slice. 162 | pub fn try_slice(&mut self) -> Option<&[T]> { 163 | self.reader.slice(false).map(|x| x.0) 164 | } 165 | 166 | /// Indicates that `n` items were read. 167 | /// 168 | /// # Panics 169 | /// 170 | /// If consumed more than space was available in the last provided slice. 171 | pub fn consume(&mut self, n: usize) { 172 | self.reader.consume(n); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/double_mapped_buffer/double_mapped_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::mem; 3 | use std::slice; 4 | 5 | use super::DoubleMappedBufferError; 6 | use super::DoubleMappedBufferImpl; 7 | 8 | /// A buffer that is mapped twice, back-to-back in the virtual address space of the process. 9 | /// 10 | /// This struct is supposed to be used as a base for buffer implementations that 11 | /// want to exploit the consequtive mappings to present available buffer space 12 | /// sequentially, without having to worry about wrapping. 13 | pub struct DoubleMappedBuffer { 14 | buffer: DoubleMappedBufferImpl, 15 | _p: PhantomData, 16 | } 17 | 18 | impl DoubleMappedBuffer { 19 | /// Create a buffer that can hold at least `min_items` items. 20 | /// 21 | /// The acutal capacity of the buffer will be the smallest multiple of the 22 | /// system page size and the item size that can hold at least `min_items` 23 | /// items. 24 | pub fn new(min_items: usize) -> Result { 25 | match DoubleMappedBufferImpl::new(min_items, mem::size_of::(), mem::align_of::()) { 26 | Ok(buffer) => Ok(DoubleMappedBuffer { 27 | buffer, 28 | _p: PhantomData, 29 | }), 30 | Err(e) => Err(e), 31 | } 32 | } 33 | 34 | /// Returns the slice corresponding to the first mapping of the buffer. 35 | /// 36 | /// # Safety 37 | /// 38 | /// Provides raw access to the slice. 39 | pub unsafe fn slice(&self) -> &[T] { 40 | let addr = self.buffer.addr(); 41 | debug_assert_eq!(addr % mem::align_of::(), 0); 42 | slice::from_raw_parts(addr as *const T, self.buffer.capacity()) 43 | } 44 | 45 | /// Returns the mutable slice corresponding to the first mapping of the buffer. 46 | /// 47 | /// # Safety 48 | /// 49 | /// Provides raw access to the slice. 50 | #[allow(clippy::mut_from_ref)] 51 | pub unsafe fn slice_mut(&self) -> &mut [T] { 52 | let addr = self.buffer.addr(); 53 | debug_assert_eq!(addr % mem::align_of::(), 0); 54 | slice::from_raw_parts_mut(addr as *mut T, self.buffer.capacity()) 55 | } 56 | 57 | /// View of the full buffer, shifted by an offset. 58 | /// 59 | /// # Safety 60 | /// 61 | /// Provides raw access to the slice. The offset has to be <= the 62 | /// [capacity](DoubleMappedBuffer::capacity) of the buffer. 63 | pub unsafe fn slice_with_offset(&self, offset: usize) -> &[T] { 64 | let addr = self.buffer.addr(); 65 | debug_assert_eq!(addr % mem::align_of::(), 0); 66 | debug_assert!(offset <= self.buffer.capacity()); 67 | slice::from_raw_parts((addr as *const T).add(offset), self.buffer.capacity()) 68 | } 69 | 70 | /// Mutable view of the full buffer, shifted by an offset. 71 | /// 72 | /// # Safety 73 | /// 74 | /// Provides raw access to the slice. The offset has to be <= the 75 | /// [capacity](DoubleMappedBuffer::capacity) of the buffer. 76 | #[allow(clippy::mut_from_ref)] 77 | pub unsafe fn slice_with_offset_mut(&self, offset: usize) -> &mut [T] { 78 | let addr = self.buffer.addr(); 79 | debug_assert_eq!(addr % mem::align_of::(), 0); 80 | debug_assert!(offset <= self.buffer.capacity()); 81 | slice::from_raw_parts_mut((addr as *mut T).add(offset), self.buffer.capacity()) 82 | } 83 | 84 | /// The capacity of the buffer, i.e., how many items it can hold. 85 | pub fn capacity(&self) -> usize { 86 | self.buffer.capacity() 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod test { 92 | use super::*; 93 | use crate::double_mapped_buffer::pagesize; 94 | use std::mem; 95 | use std::sync::atomic::compiler_fence; 96 | use std::sync::atomic::Ordering; 97 | 98 | #[test] 99 | fn byte_buffer() { 100 | let b = DoubleMappedBuffer::::new(123).expect("failed to create buffer"); 101 | let ps = pagesize(); 102 | 103 | assert_eq!(b.capacity() * mem::size_of::() % ps, 0); 104 | assert_eq!(b.buffer.addr() % mem::align_of::(), 0); 105 | 106 | unsafe { 107 | let s = b.slice_mut(); 108 | assert_eq!(s.len(), b.capacity()); 109 | assert_eq!(s.as_mut_ptr() as usize, b.buffer.addr()); 110 | 111 | for (i, v) in s.iter_mut().enumerate() { 112 | *v = (i % 128) as u8; 113 | } 114 | 115 | compiler_fence(Ordering::SeqCst); 116 | 117 | let s = b.slice_with_offset(b.capacity()); 118 | assert_eq!( 119 | s.as_ptr() as usize, 120 | b.buffer.addr() + b.capacity() * mem::size_of::() 121 | ); 122 | for (i, v) in s.iter().enumerate() { 123 | assert_eq!(*v, (i % 128) as u8); 124 | } 125 | 126 | compiler_fence(Ordering::SeqCst); 127 | b.slice_mut()[0] = 123; 128 | compiler_fence(Ordering::SeqCst); 129 | assert_eq!(b.slice_with_offset(b.capacity())[0], 123); 130 | } 131 | } 132 | 133 | #[test] 134 | fn u32_buffer() { 135 | let b = DoubleMappedBuffer::::new(12311).expect("failed to create buffer"); 136 | let ps = pagesize(); 137 | 138 | assert_eq!(b.capacity() * mem::size_of::() % ps, 0); 139 | assert_eq!(b.buffer.addr() % mem::align_of::(), 0); 140 | 141 | unsafe { 142 | let s = b.slice_mut(); 143 | assert_eq!(s.len(), b.capacity()); 144 | assert_eq!(s.as_mut_ptr() as usize, b.buffer.addr()); 145 | 146 | for (i, v) in s.iter_mut().enumerate() { 147 | *v = (i % 128) as u32; 148 | } 149 | 150 | compiler_fence(Ordering::SeqCst); 151 | 152 | let s = b.slice_with_offset(b.capacity()); 153 | assert_eq!( 154 | s.as_ptr() as usize, 155 | b.buffer.addr() + b.capacity() * mem::size_of::() 156 | ); 157 | for (i, v) in s.iter().enumerate() { 158 | assert_eq!(*v, (i % 128) as u32); 159 | } 160 | 161 | compiler_fence(Ordering::SeqCst); 162 | b.slice_mut()[0] = 123; 163 | compiler_fence(Ordering::SeqCst); 164 | assert_eq!(b.slice_with_offset(b.capacity())[0], 123); 165 | } 166 | } 167 | 168 | #[test] 169 | fn many_buffers() { 170 | let _b0 = DoubleMappedBuffer::::new(123).expect("failed to create buffer"); 171 | let _b1 = DoubleMappedBuffer::::new(456).expect("failed to create buffer"); 172 | 173 | let mut v = Vec::new(); 174 | 175 | for _ in 0..100 { 176 | v.push(DoubleMappedBuffer::::new(123).expect("failed to create buffer")); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/double_mapped_buffer/mod.rs: -------------------------------------------------------------------------------- 1 | //! Underlying data structure that maps a buffer twice into virtual memory. 2 | 3 | #[allow(clippy::module_inception)] 4 | mod double_mapped_buffer; 5 | pub use double_mapped_buffer::DoubleMappedBuffer; 6 | 7 | #[cfg(windows)] 8 | mod windows; 9 | #[cfg(windows)] 10 | use windows::DoubleMappedBufferImpl; 11 | 12 | #[cfg(unix)] 13 | mod unix; 14 | #[cfg(unix)] 15 | use unix::DoubleMappedBufferImpl; 16 | 17 | use thiserror::Error; 18 | /// Errors that can occur when setting up the double mapping. 19 | #[derive(Error, Debug)] 20 | pub enum DoubleMappedBufferError { 21 | /// Failed to close temp file. 22 | #[error("Failed to close temp file.")] 23 | Close, 24 | /// Failed to unmap second half. 25 | #[error("Failed to unmap second half.")] 26 | UnmapSecond, 27 | /// Failed to mmap second half. 28 | #[error("Failed to mmap second half.")] 29 | MapSecond, 30 | /// Failed to mmap first half. 31 | #[error("Failed to mmap first half.")] 32 | MapFirst, 33 | /// Failed to mmap placeholder. 34 | #[error("Failed to mmap placeholder.")] 35 | Placeholder, 36 | /// Failed to truncate temp file. 37 | #[error("Failed to truncate temp file.")] 38 | Truncate, 39 | /// Failed to unlink temp file. 40 | #[error("Failed to unlinkt temp file.")] 41 | Unlink, 42 | /// Failed to create temp file. 43 | #[error("Failed to create temp file.")] 44 | Create, 45 | /// Wrong alignment for data type. 46 | #[error("Wrong buffer alignment for data type.")] 47 | Alignment, 48 | } 49 | 50 | // =================== PAGESIZE ====================== 51 | use once_cell::sync::OnceCell; 52 | static PAGE_SIZE: OnceCell = OnceCell::new(); 53 | 54 | /// Size of virtual memory pages. 55 | /// 56 | /// Determines the granularity of the double buffer, which has to be a multiple 57 | /// of the page size. 58 | #[cfg(unix)] 59 | pub fn pagesize() -> usize { 60 | *PAGE_SIZE.get_or_init(|| unsafe { 61 | let ps = libc::sysconf(libc::_SC_PAGESIZE); 62 | if ps < 0 { 63 | panic!("could not determince page size"); 64 | } 65 | ps as usize 66 | }) 67 | } 68 | 69 | #[cfg(windows)] 70 | use winapi::um::sysinfoapi::GetSystemInfo; 71 | #[cfg(windows)] 72 | use winapi::um::sysinfoapi::SYSTEM_INFO; 73 | #[cfg(windows)] 74 | pub fn pagesize() -> usize { 75 | *PAGE_SIZE.get_or_init(|| unsafe { 76 | let mut info: SYSTEM_INFO = std::mem::zeroed(); 77 | GetSystemInfo(&mut info); 78 | info.dwAllocationGranularity as usize 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /src/double_mapped_buffer/unix.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::os::unix::ffi::OsStrExt; 3 | use std::path::PathBuf; 4 | 5 | use super::pagesize; 6 | use super::DoubleMappedBufferError; 7 | 8 | #[derive(Debug)] 9 | pub struct DoubleMappedBufferImpl { 10 | addr: usize, 11 | size_bytes: usize, 12 | item_size: usize, 13 | } 14 | 15 | impl DoubleMappedBufferImpl { 16 | pub fn new( 17 | min_items: usize, 18 | item_size: usize, 19 | alignment: usize, 20 | ) -> Result { 21 | for _ in 0..5 { 22 | let ret = Self::new_try(min_items, item_size, alignment); 23 | if ret.is_ok() { 24 | return ret; 25 | } 26 | } 27 | Self::new_try(min_items, item_size, alignment) 28 | } 29 | 30 | fn new_try( 31 | min_items: usize, 32 | item_size: usize, 33 | alignment: usize, 34 | ) -> Result { 35 | let ps = pagesize(); 36 | let mut size = ps; 37 | while size < min_items * item_size || size % item_size != 0 { 38 | size += ps; 39 | } 40 | 41 | let tmp = std::env::temp_dir(); 42 | let mut path = PathBuf::new(); 43 | path.push(tmp); 44 | path.push("buffer-XXXXXX"); 45 | let cstring = CString::new(path.into_os_string().as_bytes()).unwrap(); 46 | let path = cstring.as_bytes_with_nul().as_ptr(); 47 | 48 | let fd; 49 | let buff; 50 | unsafe { 51 | fd = libc::mkstemp(path as *mut libc::c_char); 52 | if fd < 0 { 53 | return Err(DoubleMappedBufferError::Create); 54 | } 55 | 56 | let ret = libc::unlink(path.cast::()); 57 | if ret < 0 { 58 | libc::close(fd); 59 | return Err(DoubleMappedBufferError::Unlink); 60 | } 61 | 62 | let ret = libc::ftruncate(fd, 2 * size as libc::off_t); 63 | if ret < 0 { 64 | libc::close(fd); 65 | return Err(DoubleMappedBufferError::Truncate); 66 | } 67 | 68 | buff = libc::mmap( 69 | std::ptr::null_mut::(), 70 | 2 * size, 71 | libc::PROT_READ | libc::PROT_WRITE, 72 | libc::MAP_SHARED, 73 | fd, 74 | 0, 75 | ); 76 | if buff == libc::MAP_FAILED { 77 | libc::close(fd); 78 | return Err(DoubleMappedBufferError::Placeholder); 79 | } 80 | if buff as usize % alignment != 0 { 81 | libc::close(fd); 82 | return Err(DoubleMappedBufferError::Alignment); 83 | } 84 | 85 | let ret = libc::munmap(buff.add(size), size); 86 | if ret < 0 { 87 | libc::munmap(buff, size); 88 | libc::close(fd); 89 | return Err(DoubleMappedBufferError::UnmapSecond); 90 | } 91 | 92 | #[cfg(target_os = "freebsd")] 93 | let buff2 = libc::mmap( 94 | buff.add(size), 95 | size, 96 | libc::PROT_READ | libc::PROT_WRITE, 97 | libc::MAP_SHARED | libc::MAP_FIXED, 98 | fd, 99 | 0, 100 | ); 101 | #[cfg(not(target_os = "freebsd"))] 102 | let buff2 = libc::mmap( 103 | buff.add(size), 104 | size, 105 | libc::PROT_READ | libc::PROT_WRITE, 106 | libc::MAP_SHARED, 107 | fd, 108 | 0, 109 | ); 110 | if buff2 != buff.add(size) { 111 | libc::munmap(buff, size); 112 | libc::close(fd); 113 | return Err(DoubleMappedBufferError::MapSecond); 114 | } 115 | 116 | let ret = libc::ftruncate(fd, size as libc::off_t); 117 | if ret < 0 { 118 | libc::munmap(buff, size); 119 | libc::munmap(buff2, size); 120 | libc::close(fd); 121 | return Err(DoubleMappedBufferError::Truncate); 122 | } 123 | 124 | let ret = libc::close(fd); 125 | if ret < 0 { 126 | return Err(DoubleMappedBufferError::Close); 127 | } 128 | } 129 | 130 | Ok(DoubleMappedBufferImpl { 131 | addr: buff as usize, 132 | size_bytes: size, 133 | item_size, 134 | }) 135 | } 136 | 137 | pub fn addr(&self) -> usize { 138 | self.addr 139 | } 140 | 141 | pub fn capacity(&self) -> usize { 142 | self.size_bytes / self.item_size 143 | } 144 | } 145 | 146 | impl Drop for DoubleMappedBufferImpl { 147 | fn drop(&mut self) { 148 | unsafe { 149 | libc::munmap(self.addr as *mut libc::c_void, self.size_bytes * 2); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/double_mapped_buffer/windows.rs: -------------------------------------------------------------------------------- 1 | use winapi::shared::minwindef::DWORD; 2 | use winapi::shared::minwindef::LPCVOID; 3 | use winapi::shared::minwindef::LPVOID; 4 | use winapi::um::handleapi::CloseHandle; 5 | use winapi::um::handleapi::INVALID_HANDLE_VALUE; 6 | use winapi::um::memoryapi::MapViewOfFileEx; 7 | use winapi::um::memoryapi::VirtualAlloc; 8 | use winapi::um::memoryapi::VirtualFree; 9 | use winapi::um::winnt::HANDLE; 10 | use winapi::um::winnt::MEM_RELEASE; 11 | use winapi::um::winnt::MEM_RESERVE; 12 | use winapi::um::winnt::PAGE_NOACCESS; 13 | use winapi::um::winnt::PAGE_READWRITE; 14 | use winapi::um::{ 15 | memoryapi::{UnmapViewOfFile, FILE_MAP_WRITE}, 16 | winbase::CreateFileMappingA, 17 | }; 18 | 19 | use super::pagesize; 20 | use super::DoubleMappedBufferError; 21 | 22 | #[derive(Debug)] 23 | pub struct DoubleMappedBufferImpl { 24 | addr: usize, 25 | handle: usize, 26 | size_bytes: usize, 27 | item_size: usize, 28 | } 29 | 30 | impl DoubleMappedBufferImpl { 31 | pub fn new( 32 | min_items: usize, 33 | item_size: usize, 34 | alignment: usize, 35 | ) -> Result { 36 | for _ in 0..5 { 37 | let ret = Self::new_try(min_items, item_size, alignment); 38 | if ret.is_ok() { 39 | return ret; 40 | } 41 | } 42 | Self::new_try(min_items, item_size, alignment) 43 | } 44 | 45 | fn new_try( 46 | min_items: usize, 47 | item_size: usize, 48 | alignment: usize, 49 | ) -> Result { 50 | let ps = pagesize(); 51 | let mut size = ps; 52 | while size < min_items * item_size || size % item_size != 0 { 53 | size += ps; 54 | } 55 | 56 | unsafe { 57 | let handle = CreateFileMappingA( 58 | INVALID_HANDLE_VALUE, 59 | std::mem::zeroed(), 60 | PAGE_READWRITE, 61 | 0, 62 | size as DWORD, 63 | std::ptr::null(), 64 | ); 65 | 66 | if handle == INVALID_HANDLE_VALUE || handle == 0 as LPVOID { 67 | return Err(DoubleMappedBufferError::Placeholder); 68 | } 69 | 70 | let first_tmp = 71 | VirtualAlloc(std::ptr::null_mut(), 2 * size, MEM_RESERVE, PAGE_NOACCESS); 72 | if first_tmp.is_null() { 73 | CloseHandle(handle); 74 | return Err(DoubleMappedBufferError::MapFirst); 75 | } 76 | 77 | let res = VirtualFree(first_tmp, 0, MEM_RELEASE); 78 | if res == 0 { 79 | CloseHandle(handle); 80 | return Err(DoubleMappedBufferError::MapSecond); 81 | } 82 | 83 | let first_cpy = MapViewOfFileEx(handle, FILE_MAP_WRITE, 0, 0, size, first_tmp); 84 | if first_tmp != first_cpy { 85 | CloseHandle(handle); 86 | return Err(DoubleMappedBufferError::MapFirst); 87 | } 88 | 89 | if first_tmp as usize % alignment != 0 { 90 | CloseHandle(handle); 91 | return Err(DoubleMappedBufferError::Alignment); 92 | } 93 | 94 | let first_ptr = (first_tmp as *mut u8).add(size) as LPVOID; 95 | let second_cpy = MapViewOfFileEx(handle, FILE_MAP_WRITE, 0, 0, size, first_ptr); 96 | if second_cpy != first_ptr { 97 | UnmapViewOfFile(first_cpy); 98 | CloseHandle(handle); 99 | return Err(DoubleMappedBufferError::MapSecond); 100 | } 101 | 102 | Ok(DoubleMappedBufferImpl { 103 | addr: first_tmp as usize, 104 | handle: handle as usize, 105 | size_bytes: size, 106 | item_size, 107 | }) 108 | } 109 | } 110 | 111 | pub fn addr(&self) -> usize { 112 | self.addr 113 | } 114 | 115 | pub fn capacity(&self) -> usize { 116 | self.size_bytes / self.item_size 117 | } 118 | } 119 | 120 | impl Drop for DoubleMappedBufferImpl { 121 | fn drop(&mut self) { 122 | unsafe { 123 | UnmapViewOfFile(self.addr as LPCVOID); 124 | UnmapViewOfFile((self.addr + self.size_bytes) as LPCVOID); 125 | CloseHandle(self.handle as HANDLE); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/generic.rs: -------------------------------------------------------------------------------- 1 | //! Circular Buffer with generic [Notifier] to implement custom wait/block behavior. 2 | 3 | use slab::Slab; 4 | use std::sync::{Arc, Mutex}; 5 | use thiserror::Error; 6 | 7 | use crate::double_mapped_buffer::{DoubleMappedBuffer, DoubleMappedBufferError}; 8 | 9 | /// Error setting up the underlying buffer. 10 | #[derive(Error, Debug)] 11 | pub enum CircularError { 12 | /// Failed to allocate double mapped buffer. 13 | #[error("Failed to allocate double mapped buffer.")] 14 | Allocation(DoubleMappedBufferError), 15 | } 16 | 17 | /// A custom notifier can be used to trigger arbitrary mechanism to signal to a 18 | /// reader or writer that data or buffer space is available. This could be a 19 | /// write to an sync/async channel or a condition variable. 20 | pub trait Notifier { 21 | /// Arm the notifier. 22 | fn arm(&mut self); 23 | /// The implementation must 24 | /// - only notify if armed 25 | /// - notify 26 | /// - unarm 27 | fn notify(&mut self); 28 | } 29 | 30 | /// Custom metadata to annotate items. 31 | pub trait Metadata { 32 | type Item: Clone; 33 | 34 | /// Create metadata container. 35 | fn new() -> Self; 36 | /// Add metadata, applying `offset` shift to items. 37 | fn add(&mut self, offset: usize, tags: Vec); 38 | /// Get metadata. 39 | fn get(&self) -> Vec; 40 | /// Prune metadata, i.e., delete consumed [items](Self::Item) and update offsets for the remaining. 41 | fn consume(&mut self, items: usize); 42 | } 43 | 44 | /// Void implementation for the [Metadata] trait for buffers that don't use metadata. 45 | pub struct NoMetadata; 46 | impl Metadata for NoMetadata { 47 | type Item = (); 48 | 49 | fn new() -> Self { 50 | Self 51 | } 52 | fn add(&mut self, _offset: usize, _tags: Vec) {} 53 | fn get(&self) -> Vec { 54 | Vec::new() 55 | } 56 | fn consume(&mut self, _items: usize) {} 57 | } 58 | 59 | /// Gerneric Circular Buffer Constructor 60 | pub struct Circular; 61 | 62 | impl Circular { 63 | /// Create a buffer that can hold at least `min_items` items of type `T`. 64 | /// 65 | /// The size is the least common multiple of the page size and the size of `T`. 66 | pub fn with_capacity(min_items: usize) -> Result, CircularError> 67 | where 68 | N: Notifier, 69 | M: Metadata, 70 | { 71 | let buffer = match DoubleMappedBuffer::new(min_items) { 72 | Ok(buffer) => Arc::new(buffer), 73 | Err(e) => return Err(CircularError::Allocation(e)), 74 | }; 75 | 76 | let state = Arc::new(Mutex::new(State { 77 | writer_offset: 0, 78 | writer_ab: false, 79 | writer_done: false, 80 | readers: Slab::new(), 81 | })); 82 | 83 | let writer = Writer { 84 | buffer, 85 | state, 86 | last_space: 0, 87 | }; 88 | 89 | Ok(writer) 90 | } 91 | } 92 | 93 | struct State 94 | where 95 | N: Notifier, 96 | M: Metadata, 97 | { 98 | writer_offset: usize, 99 | writer_ab: bool, 100 | writer_done: bool, 101 | readers: Slab>, 102 | } 103 | struct ReaderState { 104 | ab: bool, 105 | offset: usize, 106 | reader_notifier: N, 107 | writer_notifier: N, 108 | meta: M, 109 | } 110 | 111 | /// Writer for a generic circular buffer with items of type `T` and [Notifier] of type `N`. 112 | pub struct Writer 113 | where 114 | N: Notifier, 115 | M: Metadata, 116 | { 117 | last_space: usize, 118 | buffer: Arc>, 119 | state: Arc>>, 120 | } 121 | 122 | impl Writer 123 | where 124 | N: Notifier, 125 | M: Metadata, 126 | { 127 | /// Add a [Reader] to the buffer. 128 | pub fn add_reader(&self, reader_notifier: N, writer_notifier: N) -> Reader { 129 | let mut state = self.state.lock().unwrap(); 130 | let reader_state = ReaderState { 131 | ab: state.writer_ab, 132 | offset: state.writer_offset, 133 | reader_notifier, 134 | writer_notifier, 135 | meta: M::new(), 136 | }; 137 | let id = state.readers.insert(reader_state); 138 | 139 | Reader { 140 | id, 141 | last_space: 0, 142 | buffer: self.buffer.clone(), 143 | state: self.state.clone(), 144 | } 145 | } 146 | 147 | fn space_and_offset(&self, arm: bool) -> (usize, usize) { 148 | let mut state = self.state.lock().unwrap(); 149 | let capacity = self.buffer.capacity(); 150 | let w_off = state.writer_offset; 151 | let w_ab = state.writer_ab; 152 | 153 | let mut space = capacity; 154 | 155 | for (_, reader) in state.readers.iter_mut() { 156 | let r_off = reader.offset; 157 | let r_ab = reader.ab; 158 | 159 | let s = if w_off > r_off { 160 | r_off + capacity - w_off 161 | } else if w_off < r_off { 162 | r_off - w_off 163 | } else if r_ab == w_ab { 164 | capacity 165 | } else { 166 | 0 167 | }; 168 | 169 | space = std::cmp::min(space, s); 170 | 171 | if s == 0 && arm { 172 | reader.writer_notifier.arm(); 173 | break; 174 | } 175 | if s == 0 { 176 | break; 177 | } 178 | } 179 | 180 | (space, w_off) 181 | } 182 | 183 | /// Get a slice for the output buffer space. Might be empty. 184 | pub fn slice(&mut self, arm: bool) -> &mut [T] { 185 | let (space, offset) = self.space_and_offset(arm); 186 | self.last_space = space; 187 | unsafe { &mut self.buffer.slice_with_offset_mut(offset)[0..space] } 188 | } 189 | 190 | /// Indicates that `n` items were written to the output buffer. 191 | /// 192 | /// It is ok if `n` is zero. 193 | /// 194 | /// # Panics 195 | /// 196 | /// If produced more than space was available in the last provided slice. 197 | pub fn produce(&mut self, n: usize, meta: Vec) { 198 | if n == 0 { 199 | return; 200 | } 201 | 202 | debug_assert!(self.space_and_offset(false).0 >= n); 203 | 204 | assert!(n <= self.last_space, "vmcircbuffer: produced too much"); 205 | self.last_space -= n; 206 | 207 | let mut state = self.state.lock().unwrap(); 208 | 209 | let w_off = state.writer_offset; 210 | let w_ab = state.writer_ab; 211 | let capacity = self.buffer.capacity(); 212 | 213 | for (_, r) in state.readers.iter_mut() { 214 | let r_off = r.offset; 215 | let r_ab = r.ab; 216 | 217 | let space = if r_off > w_off { 218 | w_off + capacity - r_off 219 | } else if r_off < w_off { 220 | w_off - r_off 221 | } else if r_ab == w_ab { 222 | 0 223 | } else { 224 | capacity 225 | }; 226 | 227 | r.meta.add(space, meta.clone()); 228 | r.reader_notifier.notify(); 229 | } 230 | 231 | if state.writer_offset + n >= self.buffer.capacity() { 232 | state.writer_ab = !state.writer_ab; 233 | } 234 | state.writer_offset = (state.writer_offset + n) % self.buffer.capacity(); 235 | } 236 | } 237 | 238 | impl Drop for Writer 239 | where 240 | N: Notifier, 241 | M: Metadata, 242 | { 243 | fn drop(&mut self) { 244 | let mut state = self.state.lock().unwrap(); 245 | state.writer_done = true; 246 | for (_, r) in state.readers.iter_mut() { 247 | r.reader_notifier.notify(); 248 | } 249 | } 250 | } 251 | 252 | /// Reader for a generic circular buffer with items of type `T` and [Notifier] of type `N`. 253 | pub struct Reader 254 | where 255 | N: Notifier, 256 | M: Metadata, 257 | { 258 | id: usize, 259 | last_space: usize, 260 | buffer: Arc>, 261 | state: Arc>>, 262 | } 263 | 264 | impl Reader 265 | where 266 | N: Notifier, 267 | M: Metadata, 268 | { 269 | fn space_and_offset_and_meta(&self, arm: bool) -> (usize, usize, bool, Vec) { 270 | let mut state = self.state.lock().unwrap(); 271 | 272 | let capacity = self.buffer.capacity(); 273 | let done = state.writer_done; 274 | let w_off = state.writer_offset; 275 | let w_ab = state.writer_ab; 276 | 277 | let my = unsafe { state.readers.get_unchecked_mut(self.id) }; 278 | let r_off = my.offset; 279 | let r_ab = my.ab; 280 | 281 | let space = if r_off > w_off { 282 | w_off + capacity - r_off 283 | } else if r_off < w_off { 284 | w_off - r_off 285 | } else if r_ab == w_ab { 286 | 0 287 | } else { 288 | capacity 289 | }; 290 | 291 | if space == 0 && arm { 292 | my.reader_notifier.arm(); 293 | } 294 | 295 | (space, r_off, done, my.meta.get()) 296 | } 297 | 298 | /// Get a slice with the items available to read. 299 | /// 300 | /// Returns `None` if the reader was dropped and all data was read. 301 | pub fn slice(&mut self, arm: bool) -> Option<(&[T], Vec)> { 302 | let (space, offset, done, tags) = self.space_and_offset_and_meta(arm); 303 | self.last_space = space; 304 | if space == 0 && done { 305 | None 306 | } else { 307 | unsafe { Some((&self.buffer.slice_with_offset(offset)[0..space], tags)) } 308 | } 309 | } 310 | 311 | /// Indicates that `n` items were read. 312 | /// 313 | /// # Panics 314 | /// 315 | /// If consumed more than space was available in the last provided slice. 316 | pub fn consume(&mut self, n: usize) { 317 | if n == 0 { 318 | return; 319 | } 320 | 321 | debug_assert!(self.space_and_offset_and_meta(false).0 >= n); 322 | 323 | assert!(n <= self.last_space, "vmcircbuffer: consumed too much!"); 324 | self.last_space -= n; 325 | 326 | let mut state = self.state.lock().unwrap(); 327 | let my = unsafe { state.readers.get_unchecked_mut(self.id) }; 328 | 329 | my.meta.consume(n); 330 | 331 | if my.offset + n >= self.buffer.capacity() { 332 | my.ab = !my.ab; 333 | } 334 | my.offset = (my.offset + n) % self.buffer.capacity(); 335 | 336 | my.writer_notifier.notify(); 337 | } 338 | } 339 | 340 | impl Drop for Reader 341 | where 342 | N: Notifier, 343 | M: Metadata, 344 | { 345 | fn drop(&mut self) { 346 | let mut state = self.state.lock().unwrap(); 347 | let mut s = state.readers.remove(self.id); 348 | s.writer_notifier.notify(); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Double Mapped Circular Buffer 2 | //! 3 | //! - Thread-safe. 4 | //! - Supports multiple readers. 5 | //! - Generic over the item type. 6 | //! - Provides access to all items (not n-1). 7 | //! - Supports Linux, macOS, Windows, and Android. 8 | //! - [Sync](sync), [async](asynchronous), and [non-blocking](nonblocking) implementations. 9 | //! - [Generic](crate::generic) variant that allows specifying custom [Notifiers](crate::generic::Notifier) to ease integration. 10 | //! - Underlying data structure (i.e., [DoubleMappedBuffer](double_mapped_buffer::DoubleMappedBuffer)) is exported to allow custom implementations. 11 | //! 12 | //! # Quick Start 13 | //! 14 | //! ``` 15 | //! # #[cfg(feature = "sync")] { 16 | //! # use vmcircbuffer::sync; 17 | //! # use vmcircbuffer::generic::CircularError; 18 | //! let mut w = sync::Circular::new::().unwrap(); 19 | //! let mut r = w.add_reader(); 20 | //! 21 | //! // delay producing by 1 sec 22 | //! let now = std::time::Instant::now(); 23 | //! let delay = std::time::Duration::from_millis(1000); 24 | //! 25 | //! // producer thread 26 | //! std::thread::spawn(move || { 27 | //! std::thread::sleep(delay); 28 | //! let w_buff = w.slice(); 29 | //! for v in w_buff.iter_mut() { 30 | //! *v = 23; 31 | //! } 32 | //! let l = w_buff.len(); 33 | //! w.produce(l); 34 | //! }); 35 | //! 36 | //! // blocks until data becomes available 37 | //! let r_buff = r.slice().unwrap(); 38 | //! assert!(now.elapsed() > delay); 39 | //! for v in r_buff { 40 | //! assert_eq!(*v, 23); 41 | //! } 42 | //! let l = r_buff.len(); 43 | //! r.consume(l); 44 | //! # } 45 | //! ``` 46 | //! 47 | //! # Commonalities 48 | //! 49 | //! There are some commonalities between the implementations: 50 | //! - The `Circular` struct is a factory to create the `Writer`. 51 | //! - If there are no `Reader`s, the `Writer` will not block but continuously overwrite the buffer. 52 | //! - The `Writer` has an `add_reader()` method to add `Reader`s. 53 | //! - When the `Writer` is dropped, the `Reader` can read the remaining items. Afterwards, the `slice()` will return `None`. 54 | //! 55 | //! # Details 56 | //! 57 | //! This circular buffer implementation maps the underlying buffer twice, 58 | //! back-to-back into the virtual address space of the process. This arrangement 59 | //! allows the circular buffer to present the available data sequentially, 60 | //! (i.e., as a slice) without having to worry about wrapping. 61 | //! 62 | //! On Unix-based systems, the mapping is setup with a temporary file. This file 63 | //! is created in the folder, determined through [std::env::temp_dir], which 64 | //! considers environment variables. This can be used, if the standard paths are 65 | //! not present of not writable on the platform. 66 | //! 67 | //! # Features 68 | //! 69 | //! The `async`, `nonblocking`, and `sync` feature flags, allow to disable the 70 | //! corresponding implementations. By default, all are enabled. In addition, the 71 | //! `generic` flag allows to disable the generic implementation, leaving only 72 | //! the [DoubleMappedBuffer](double_mapped_buffer::DoubleMappedBuffer). 73 | 74 | #[cfg(feature = "async")] 75 | pub mod asynchronous; 76 | pub mod double_mapped_buffer; 77 | #[cfg(feature = "generic")] 78 | pub mod generic; 79 | #[cfg(feature = "nonblocking")] 80 | pub mod nonblocking; 81 | #[cfg(feature = "sync")] 82 | pub mod sync; 83 | -------------------------------------------------------------------------------- /src/nonblocking.rs: -------------------------------------------------------------------------------- 1 | //! Non-blocking Circular Buffer that can only check if data is available right now. 2 | 3 | use crate::generic; 4 | use crate::generic::CircularError; 5 | use crate::generic::NoMetadata; 6 | use crate::generic::Notifier; 7 | 8 | struct NullNotifier; 9 | 10 | impl Notifier for NullNotifier { 11 | fn arm(&mut self) {} 12 | fn notify(&mut self) {} 13 | } 14 | 15 | /// Builder for the *non-blocking* circular buffer implementation. 16 | pub struct Circular; 17 | 18 | impl Circular { 19 | /// Create a buffer for items of type `T` with minimal capacity (usually a page size). 20 | /// 21 | /// The actual size is the least common multiple of the page size and the size of `T`. 22 | #[allow(clippy::new_ret_no_self)] 23 | pub fn new() -> Result, CircularError> { 24 | Self::with_capacity(0) 25 | } 26 | 27 | /// Create a buffer that can hold at least `min_items` items of type `T`. 28 | /// 29 | /// The size is the least common multiple of the page size and the size of `T`. 30 | pub fn with_capacity(min_items: usize) -> Result, CircularError> { 31 | let writer = generic::Circular::with_capacity(min_items)?; 32 | 33 | Ok(Writer { writer }) 34 | } 35 | } 36 | 37 | /// Writer for a non-blocking circular buffer with items of type `T`. 38 | pub struct Writer { 39 | writer: generic::Writer, 40 | } 41 | 42 | impl Writer { 43 | /// Add a reader to the buffer. 44 | /// 45 | /// All readers can block the buffer, i.e., the writer will only overwrite 46 | /// data, if data was [consume](crate::sync::Reader::consume)ed by all 47 | /// readers. 48 | pub fn add_reader(&self) -> Reader { 49 | let reader = self.writer.add_reader(NullNotifier, NullNotifier); 50 | Reader { reader } 51 | } 52 | 53 | /// Get a slice to the free slots, available for writing. 54 | /// 55 | /// This function return immediately. The slice might be [empty](slice::is_empty). 56 | #[inline] 57 | pub fn try_slice(&mut self) -> &mut [T] { 58 | self.writer.slice(false) 59 | } 60 | 61 | /// Indicates that `n` items were written to the output buffer. 62 | /// 63 | /// It is ok if `n` is zero. 64 | /// 65 | /// # Panics 66 | /// 67 | /// If produced more than space was available in the last provided slice. 68 | #[inline] 69 | pub fn produce(&mut self, n: usize) { 70 | self.writer.produce(n, Vec::new()); 71 | } 72 | } 73 | 74 | /// ReaderState for a non-blocking circular buffer with items of type `T`. 75 | pub struct Reader { 76 | reader: generic::Reader, 77 | } 78 | 79 | impl Reader { 80 | /// Checks if there is data to read. 81 | /// 82 | /// If all data is read and the writer is dropped, all following calls will 83 | /// return `None`. If there is no data to read, `Some` is returned with an 84 | /// empty slice. 85 | #[inline] 86 | pub fn try_slice(&mut self) -> Option<&[T]> { 87 | self.reader.slice(false).map(|x| x.0) 88 | } 89 | 90 | /// Indicates that `n` items were read. 91 | /// 92 | /// # Panics 93 | /// 94 | /// If consumed more than space was available in the last provided slice. 95 | #[inline] 96 | pub fn consume(&mut self, n: usize) { 97 | self.reader.consume(n); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | //! Blocking Circular Buffer that blocks until data becomes available. 2 | 3 | use core::slice; 4 | use std::sync::mpsc::{channel, Receiver, Sender}; 5 | 6 | use crate::generic; 7 | use crate::generic::CircularError; 8 | use crate::generic::NoMetadata; 9 | use crate::generic::Notifier; 10 | 11 | struct BlockingNotifier { 12 | chan: Sender<()>, 13 | armed: bool, 14 | } 15 | 16 | impl Notifier for BlockingNotifier { 17 | fn arm(&mut self) { 18 | self.armed = true; 19 | } 20 | fn notify(&mut self) { 21 | if self.armed { 22 | let _ = self.chan.send(()); 23 | self.armed = false; 24 | } 25 | } 26 | } 27 | 28 | /// Builder for the *blocking* circular buffer implementation. 29 | pub struct Circular; 30 | 31 | impl Circular { 32 | /// Create a buffer for items of type `T` with minimal capacity (usually a page size). 33 | /// 34 | /// The actual size is the least common multiple of the page size and the size of `T`. 35 | #[allow(clippy::new_ret_no_self)] 36 | pub fn new() -> Result, CircularError> { 37 | Self::with_capacity(0) 38 | } 39 | 40 | /// Create a buffer that can hold at least `min_items` items of type `T`. 41 | /// 42 | /// The size is the least common multiple of the page size and the size of `T`. 43 | pub fn with_capacity(min_items: usize) -> Result, CircularError> { 44 | let writer = generic::Circular::with_capacity(min_items)?; 45 | 46 | let (tx, rx) = channel(); 47 | Ok(Writer { 48 | writer, 49 | writer_sender: tx, 50 | chan: rx, 51 | }) 52 | } 53 | } 54 | 55 | /// Writer for a blocking circular buffer with items of type `T`. 56 | pub struct Writer { 57 | writer_sender: Sender<()>, 58 | chan: Receiver<()>, 59 | writer: generic::Writer, 60 | } 61 | 62 | impl Writer { 63 | /// Add a reader to the buffer. 64 | /// 65 | /// All readers can block the buffer, i.e., the writer will only overwrite 66 | /// data, if data was [consume](crate::sync::Reader::consume)ed by all 67 | /// readers. 68 | pub fn add_reader(&self) -> Reader { 69 | let w_notifier = BlockingNotifier { 70 | chan: self.writer_sender.clone(), 71 | armed: false, 72 | }; 73 | 74 | let (tx, rx) = channel(); 75 | let r_notififer = BlockingNotifier { 76 | chan: tx, 77 | armed: false, 78 | }; 79 | 80 | let reader = self.writer.add_reader(r_notififer, w_notifier); 81 | Reader { reader, chan: rx } 82 | } 83 | 84 | /// Blocking call to get a slice to the available output space. 85 | /// 86 | /// The function returns as soon as any output space is available. 87 | /// The returned slice will never be empty. 88 | pub fn slice(&mut self) -> &mut [T] { 89 | // ugly workaround for borrow-checker problem 90 | // https://github.com/rust-lang/rust/issues/21906 91 | let (p, s) = loop { 92 | match self.writer.slice(true) { 93 | [] => { 94 | let _ = self.chan.recv(); 95 | } 96 | s => break (s.as_mut_ptr(), s.len()), 97 | } 98 | }; 99 | unsafe { slice::from_raw_parts_mut(p, s) } 100 | } 101 | 102 | /// Get a slice to the free slots, available for writing. 103 | /// 104 | /// This function return immediately. The slice might be [empty](slice::is_empty). 105 | #[inline] 106 | pub fn try_slice(&mut self) -> &mut [T] { 107 | self.writer.slice(false) 108 | } 109 | 110 | /// Indicates that `n` items were written to the output buffer. 111 | /// 112 | /// It is ok if `n` is zero. 113 | /// 114 | /// # Panics 115 | /// 116 | /// If produced more than space was available in the last provided slice. 117 | #[inline] 118 | pub fn produce(&mut self, n: usize) { 119 | self.writer.produce(n, Vec::new()); 120 | } 121 | } 122 | 123 | /// Reader for a blocking circular buffer with items of type `T`. 124 | pub struct Reader { 125 | chan: Receiver<()>, 126 | reader: generic::Reader, 127 | } 128 | 129 | impl Reader { 130 | /// Blocks until there is data to read or until the writer is dropped. 131 | /// 132 | /// If all data is read and the writer is dropped, all following calls will 133 | /// return `None`. If `Some` is returned, the contained slice is never empty. 134 | pub fn slice(&mut self) -> Option<&[T]> { 135 | // ugly workaround for borrow-checker problem 136 | // https://github.com/rust-lang/rust/issues/21906 137 | let r = loop { 138 | match self.reader.slice(true) { 139 | Some(([], _)) => { 140 | let _ = self.chan.recv(); 141 | } 142 | Some((s, _)) => break Some((s.as_ptr(), s.len())), 143 | None => break None, 144 | } 145 | }; 146 | if let Some((p, s)) = r { 147 | unsafe { Some(slice::from_raw_parts(p, s)) } 148 | } else { 149 | None 150 | } 151 | } 152 | 153 | /// Checks if there is data to read. 154 | /// 155 | /// If all data is read and the writer is dropped, all following calls will 156 | /// return `None`. If there is no data to read, `Some` is returned with an 157 | /// empty slice. 158 | #[inline] 159 | pub fn try_slice(&mut self) -> Option<&[T]> { 160 | self.reader.slice(false).map(|x| x.0) 161 | } 162 | 163 | /// Indicates that `n` items were read. 164 | /// 165 | /// # Panics 166 | /// 167 | /// If consumed more than space was available in the last provided slice. 168 | #[inline] 169 | pub fn consume(&mut self, n: usize) { 170 | self.reader.consume(n); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /tests/async.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::{Distribution, Uniform}; 2 | use std::iter::repeat_with; 3 | 4 | use vmcircbuffer::asynchronous; 5 | 6 | #[test] 7 | fn wait_reader() { 8 | smol::block_on(async { 9 | let mut w = asynchronous::Circular::new::().unwrap(); 10 | let mut r = w.add_reader(); 11 | 12 | let l = w.slice().await.len(); 13 | w.produce(l); 14 | 15 | let now = std::time::Instant::now(); 16 | let delay = std::time::Duration::from_millis(1000); 17 | 18 | smol::spawn(async move { 19 | smol::Timer::after(delay).await; 20 | let l = r.slice().await.unwrap().len(); 21 | r.consume(l); 22 | }) 23 | .detach(); 24 | 25 | let _ = w.slice().await; 26 | assert!(now.elapsed() > delay); 27 | }); 28 | } 29 | 30 | #[test] 31 | fn wait_writer() { 32 | smol::block_on(async { 33 | let mut w = asynchronous::Circular::new::().unwrap(); 34 | let mut r = w.add_reader(); 35 | 36 | let now = std::time::Instant::now(); 37 | let delay = std::time::Duration::from_millis(1000); 38 | 39 | smol::spawn(async move { 40 | smol::Timer::after(delay).await; 41 | let l = w.slice().await.len(); 42 | w.produce(l); 43 | }) 44 | .detach(); 45 | 46 | let _ = r.slice().await; 47 | assert!(now.elapsed() > delay); 48 | }); 49 | } 50 | 51 | #[test] 52 | fn fuzz_async() { 53 | smol::block_on(async { 54 | let mut w = asynchronous::Circular::new::().unwrap(); 55 | let mut r = w.add_reader(); 56 | let size = w.slice().await.len(); 57 | 58 | let input: Vec = repeat_with(rand::random::).take(1231233).collect(); 59 | 60 | let mut rng = rand::thread_rng(); 61 | let n_writes_dist = Uniform::from(0..4); 62 | let n_samples_dist = Uniform::from(0..size / 2); 63 | 64 | let mut w_off = 0; 65 | let mut r_off = 0; 66 | 67 | while r_off < input.len() { 68 | let n_writes = n_writes_dist.sample(&mut rng); 69 | for _ in 0..n_writes { 70 | let s = w.slice().await; 71 | let n = std::cmp::min(s.len(), input.len() - w_off); 72 | let n = std::cmp::min(n, n_samples_dist.sample(&mut rng)); 73 | 74 | for (i, v) in s.iter_mut().take(n).enumerate() { 75 | *v = input[w_off + i]; 76 | } 77 | w.produce(n); 78 | w_off += n; 79 | } 80 | 81 | let s = r.try_slice().unwrap(); 82 | assert_eq!(s.len(), w_off - r_off); 83 | 84 | for (i, v) in s.iter().enumerate() { 85 | assert_eq!(*v, input[r_off + i]); 86 | } 87 | let l = s.len(); 88 | r_off += l; 89 | r.consume(l); 90 | } 91 | }); 92 | } 93 | -------------------------------------------------------------------------------- /tests/nonblocking.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::{Distribution, Uniform}; 2 | use std::iter::repeat_with; 3 | 4 | use vmcircbuffer::nonblocking::Circular; 5 | 6 | #[test] 7 | fn create_many() { 8 | let mut v = Vec::new(); 9 | for _ in 0..100 { 10 | v.push(Circular::new::().unwrap()); 11 | } 12 | } 13 | 14 | #[test] 15 | fn zero_size() { 16 | let mut w = Circular::new::().unwrap(); 17 | assert!(!w.try_slice().is_empty()); 18 | } 19 | 20 | #[test] 21 | fn no_reader() { 22 | let mut w = Circular::new::().unwrap(); 23 | let s = w.try_slice(); 24 | let l = s.len(); 25 | w.produce(l); 26 | assert!(!w.try_slice().is_empty()); 27 | } 28 | 29 | #[test] 30 | #[should_panic] 31 | fn produce_too_much() { 32 | let mut w = Circular::new::().unwrap(); 33 | let s = w.try_slice(); 34 | let l = s.len(); 35 | w.produce(l + 1); 36 | } 37 | 38 | #[test] 39 | #[should_panic] 40 | fn consume_too_much() { 41 | let mut w = Circular::new::().unwrap(); 42 | let mut r = w.add_reader(); 43 | let s = w.try_slice(); 44 | let l = s.len(); 45 | w.produce(l + 1); 46 | let s = r.try_slice().unwrap(); 47 | let l = s.len(); 48 | r.consume(l + 1); 49 | } 50 | 51 | #[test] 52 | fn late_reader() { 53 | let mut w = Circular::new::().unwrap(); 54 | let s = w.try_slice(); 55 | for (i, v) in s.iter_mut().take(200).enumerate() { 56 | *v = i as u32; 57 | } 58 | w.produce(100); 59 | 60 | let mut r = w.add_reader(); 61 | assert_eq!(r.try_slice().unwrap().len(), 0); 62 | w.produce(100); 63 | assert_eq!(r.try_slice().unwrap().len(), 100); 64 | for (i, v) in r.try_slice().unwrap().iter().enumerate() { 65 | assert_eq!(*v, 100 + i as u32); 66 | } 67 | } 68 | 69 | #[test] 70 | fn several_readers() { 71 | let mut w = Circular::new::().unwrap(); 72 | 73 | let mut r1 = w.add_reader(); 74 | let mut r2 = w.add_reader(); 75 | 76 | for (i, v) in w.try_slice().iter_mut().enumerate() { 77 | *v = i as u32; 78 | } 79 | let all = w.try_slice().len(); 80 | assert_eq!(r1.try_slice().unwrap().len(), 0); 81 | let l = w.try_slice().len(); 82 | w.produce(l); 83 | assert_eq!(r2.try_slice().unwrap().len(), all); 84 | 85 | let _ = r1.try_slice(); 86 | r1.consume(100); 87 | 88 | assert_eq!(r1.try_slice().unwrap().len(), all - 100); 89 | for (i, v) in r1.try_slice().unwrap().iter().enumerate() { 90 | assert_eq!(*v, 100 + i as u32); 91 | } 92 | } 93 | 94 | #[test] 95 | fn fuzz_nonblocking() { 96 | let mut w = Circular::new::().unwrap(); 97 | let mut r = w.add_reader(); 98 | let size = w.try_slice().len(); 99 | 100 | let input: Vec = repeat_with(rand::random::).take(1231233).collect(); 101 | 102 | let mut rng = rand::thread_rng(); 103 | let n_writes_dist = Uniform::from(0..4); 104 | let n_samples_dist = Uniform::from(0..size / 2); 105 | 106 | let mut w_off = 0; 107 | let mut r_off = 0; 108 | 109 | while r_off < input.len() { 110 | let n_writes = n_writes_dist.sample(&mut rng); 111 | for _ in 0..n_writes { 112 | let s = w.try_slice(); 113 | let n = std::cmp::min(s.len(), input.len() - w_off); 114 | let n = std::cmp::min(n, n_samples_dist.sample(&mut rng)); 115 | 116 | for (i, v) in s.iter_mut().take(n).enumerate() { 117 | *v = input[w_off + i]; 118 | } 119 | w.produce(n); 120 | w_off += n; 121 | } 122 | 123 | let s = r.try_slice().unwrap(); 124 | assert_eq!(s.len(), w_off - r_off); 125 | 126 | for (i, v) in s.iter().enumerate() { 127 | assert_eq!(*v, input[r_off + i]); 128 | } 129 | let l = s.len(); 130 | r.consume(l); 131 | r_off += l; 132 | } 133 | } 134 | 135 | #[test] 136 | fn minimal() { 137 | let mut w = Circular::new::().unwrap(); 138 | let mut r = w.add_reader(); 139 | 140 | for v in w.try_slice() { 141 | *v = 123; 142 | } 143 | let l = w.try_slice().len(); 144 | w.produce(l); 145 | 146 | for v in r.try_slice().unwrap() { 147 | assert_eq!(*v, 123); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /tests/sync.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::{Distribution, Uniform}; 2 | use std::iter::repeat_with; 3 | 4 | use vmcircbuffer::sync::Circular; 5 | 6 | #[test] 7 | fn create_many() { 8 | let mut v = Vec::new(); 9 | for _ in 0..100 { 10 | v.push(Circular::new::().unwrap()); 11 | } 12 | } 13 | 14 | #[test] 15 | fn zero_size() { 16 | let mut w = Circular::new::().unwrap(); 17 | assert!(!w.slice().is_empty()); 18 | } 19 | 20 | #[test] 21 | fn no_reader() { 22 | let mut w = Circular::new::().unwrap(); 23 | let s = w.slice(); 24 | let l = s.len(); 25 | w.produce(l); 26 | assert!(!w.slice().is_empty()); 27 | } 28 | 29 | #[test] 30 | #[should_panic] 31 | fn produce_too_much() { 32 | let mut w = Circular::new::().unwrap(); 33 | let s = w.slice(); 34 | let l = s.len(); 35 | w.produce(l + 1); 36 | } 37 | 38 | #[test] 39 | #[should_panic] 40 | fn consume_too_much() { 41 | let mut w = Circular::new::().unwrap(); 42 | let mut r = w.add_reader(); 43 | let s = w.slice(); 44 | let l = s.len(); 45 | w.produce(l + 1); 46 | let s = r.slice().unwrap(); 47 | let l = s.len(); 48 | r.consume(l + 1); 49 | } 50 | 51 | #[test] 52 | fn late_reader() { 53 | let mut w = Circular::new::().unwrap(); 54 | let s = w.slice(); 55 | for (i, v) in s.iter_mut().take(200).enumerate() { 56 | *v = i as u32; 57 | } 58 | w.produce(100); 59 | 60 | let mut r = w.add_reader(); 61 | assert_eq!(r.try_slice().unwrap().len(), 0); 62 | w.produce(100); 63 | assert_eq!(r.slice().unwrap().len(), 100); 64 | for (i, v) in r.slice().unwrap().iter().enumerate() { 65 | assert_eq!(*v, 100 + i as u32); 66 | } 67 | } 68 | 69 | #[test] 70 | fn several_readers() { 71 | let mut w = Circular::new::().unwrap(); 72 | 73 | let mut r1 = w.add_reader(); 74 | let mut r2 = w.add_reader(); 75 | 76 | for (i, v) in w.slice().iter_mut().enumerate() { 77 | *v = i as u32; 78 | } 79 | let all = w.slice().len(); 80 | assert_eq!(r1.try_slice().unwrap().len(), 0); 81 | let l = w.slice().len(); 82 | w.produce(l); 83 | assert_eq!(r2.slice().unwrap().len(), all); 84 | 85 | let _ = r1.slice(); 86 | r1.consume(100); 87 | 88 | assert_eq!(r1.slice().unwrap().len(), all - 100); 89 | for (i, v) in r1.slice().unwrap().iter().enumerate() { 90 | assert_eq!(*v, 100 + i as u32); 91 | } 92 | } 93 | 94 | #[test] 95 | fn minimal() { 96 | let mut w = Circular::new::().unwrap(); 97 | let mut r = w.add_reader(); 98 | 99 | for v in w.slice() { 100 | *v = 123; 101 | } 102 | let l = w.slice().len(); 103 | w.produce(l); 104 | 105 | for v in r.slice().unwrap() { 106 | assert_eq!(*v, 123); 107 | } 108 | } 109 | 110 | #[test] 111 | fn block_writer() { 112 | let mut w = Circular::new::().unwrap(); 113 | let mut r = w.add_reader(); 114 | 115 | let l = w.slice().len(); 116 | w.produce(l); 117 | 118 | let now = std::time::Instant::now(); 119 | let delay = std::time::Duration::from_millis(1000); 120 | 121 | std::thread::spawn(move || { 122 | std::thread::sleep(delay); 123 | let l = r.slice().unwrap().len(); 124 | r.consume(l); 125 | }); 126 | 127 | let _ = w.slice(); 128 | assert!(now.elapsed() > delay); 129 | } 130 | 131 | #[test] 132 | fn block_reader() { 133 | let mut w = Circular::new::().unwrap(); 134 | let mut r = w.add_reader(); 135 | 136 | let now = std::time::Instant::now(); 137 | let delay = std::time::Duration::from_millis(1000); 138 | 139 | std::thread::spawn(move || { 140 | std::thread::sleep(delay); 141 | let l = w.slice().len(); 142 | w.produce(l); 143 | }); 144 | 145 | let _ = r.slice(); 146 | assert!(now.elapsed() > delay); 147 | } 148 | 149 | #[test] 150 | fn fuzz_sync() { 151 | let mut w = Circular::new::().unwrap(); 152 | let mut r = w.add_reader(); 153 | let size = w.slice().len(); 154 | 155 | let input: Vec = repeat_with(rand::random::).take(1231233).collect(); 156 | 157 | let mut rng = rand::thread_rng(); 158 | let n_writes_dist = Uniform::from(0..4); 159 | let n_samples_dist = Uniform::from(0..size / 2); 160 | 161 | let mut w_off = 0; 162 | let mut r_off = 0; 163 | 164 | while r_off < input.len() { 165 | let n_writes = n_writes_dist.sample(&mut rng); 166 | for _ in 0..n_writes { 167 | let s = w.slice(); 168 | let n = std::cmp::min(s.len(), input.len() - w_off); 169 | let n = std::cmp::min(n, n_samples_dist.sample(&mut rng)); 170 | 171 | for (i, v) in s.iter_mut().take(n).enumerate() { 172 | *v = input[w_off + i]; 173 | } 174 | w.produce(n); 175 | w_off += n; 176 | } 177 | 178 | let s = r.try_slice().unwrap(); 179 | assert_eq!(s.len(), w_off - r_off); 180 | 181 | for (i, v) in s.iter().enumerate() { 182 | assert_eq!(*v, input[r_off + i]); 183 | } 184 | let l = s.len(); 185 | r.consume(l); 186 | r_off += l; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /tests/tags.rs: -------------------------------------------------------------------------------- 1 | use vmcircbuffer::generic::Circular; 2 | use vmcircbuffer::generic::Metadata; 3 | use vmcircbuffer::generic::Notifier; 4 | 5 | struct MyNotifier; 6 | 7 | impl Notifier for MyNotifier { 8 | fn arm(&mut self) {} 9 | fn notify(&mut self) {} 10 | } 11 | 12 | #[derive(Clone)] 13 | struct Tag { 14 | item: usize, 15 | data: String, 16 | } 17 | 18 | struct MyMetadata { 19 | tags: Vec, 20 | } 21 | 22 | impl Metadata for MyMetadata { 23 | type Item = Tag; 24 | 25 | fn new() -> Self { 26 | MyMetadata { tags: Vec::new() } 27 | } 28 | fn add(&mut self, offset: usize, mut tags: Vec) { 29 | for t in tags.iter_mut() { 30 | t.item += offset; 31 | } 32 | self.tags.append(&mut tags); 33 | } 34 | fn get(&self) -> Vec { 35 | self.tags.clone() 36 | } 37 | fn consume(&mut self, items: usize) { 38 | self.tags.retain(|x| x.item >= items); 39 | for t in self.tags.iter_mut() { 40 | t.item -= items; 41 | } 42 | } 43 | } 44 | 45 | #[test] 46 | fn tags() { 47 | let mut w = Circular::with_capacity::(1).unwrap(); 48 | 49 | let mut r = w.add_reader(MyNotifier, MyNotifier); 50 | 51 | let out = w.slice(false); 52 | for v in out.iter_mut() { 53 | *v = 123; 54 | } 55 | let len = out.len(); 56 | 57 | w.produce( 58 | len, 59 | vec![ 60 | Tag { 61 | item: 0, 62 | data: String::from("first"), 63 | }, 64 | Tag { 65 | item: 10, 66 | data: String::from("tenth"), 67 | }, 68 | ], 69 | ); 70 | 71 | let (i, tags) = r.slice(false).unwrap(); 72 | 73 | assert_eq!(i[0], 123); 74 | assert_eq!(tags.len(), 2); 75 | assert_eq!(tags[0].data, String::from("first")); 76 | assert_eq!(tags[0].item, 0); 77 | assert_eq!(tags[1].data, String::from("tenth")); 78 | assert_eq!(tags[1].item, 10); 79 | 80 | r.consume(5); 81 | let (i, tags) = r.slice(false).unwrap(); 82 | 83 | assert_eq!(i[0], 123); 84 | assert_eq!(tags.len(), 1); 85 | assert_eq!(tags[0].data, String::from("tenth")); 86 | assert_eq!(tags[0].item, 5); 87 | } 88 | --------------------------------------------------------------------------------