├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE.md ├── README.md ├── build.rs ├── examples ├── ipmpsc-receive.rs └── ipmpsc-send.rs ├── ipc-benchmarks ├── Cargo.toml ├── README.md └── src │ └── lib.rs └── src ├── bitmask.rs ├── lib.rs ├── posix.rs └── windows.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | ipc-benchmarks/target 3 | **/*.rs.bk 4 | Cargo.lock 5 | .gdb_history 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ipmpsc" 3 | description = "Inter-process Multiple Producer, Single Consumer Channels" 4 | readme = "README.md" 5 | repository = "https://github.com/dicej/ipmpsc" 6 | documentation = "https://docs.rs/ipmpsc/" 7 | keywords = ["ipc", "shared"] 8 | license = "MIT" 9 | version = "0.5.1" 10 | authors = ["Joel Dice "] 11 | edition = "2018" 12 | include = [ "Cargo.toml", "LICENSE.md", "README.md", "src/**/*", "examples/**/*" ] 13 | 14 | [features] 15 | fork = ["anyhow", "errno"] 16 | 17 | [dependencies] 18 | serde = { version = "1", features = ["derive"] } 19 | bincode = "1" 20 | tempfile = "3" 21 | memmap2 = "0.2" 22 | libc = "0.2" 23 | thiserror = "1" 24 | anyhow = { version = "1", optional = true } 25 | 26 | [target.'cfg(windows)'.dependencies] 27 | winapi = { version = "0.3", features = ["synchapi"] } 28 | sha2 = "0.9" 29 | hex = "0.4" 30 | 31 | [target.'cfg(unix)'.dependencies] 32 | errno = { version = "0.2", optional = true } 33 | 34 | [dev-dependencies] 35 | anyhow = "1" 36 | proptest = "0.9" 37 | clap = "2" 38 | serde_bytes = "0.11" 39 | 40 | [target.'cfg(unix)'.dev-dependencies] 41 | errno = "0.2" 42 | 43 | [build-dependencies] 44 | anyhow = "1" 45 | vergen = "5" 46 | 47 | [profile.release] 48 | debug = true 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Joel Dice 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 | # ipmpsc 2 | 3 | Inter-Process Multiple Producer, Single Consumer Channels for Rust 4 | 5 | [![Crates.io](https://img.shields.io/crates/v/ipmpsc)](https://crates.io/crates/ipmpsc) 6 | [![Build Status](https://travis-ci.org/dicej/ipmpsc.svg?branch=master)](https://travis-ci.org/dicej/ipmpsc) 7 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.md) 8 | 9 | ## Summary 10 | 11 | This library provides a type-safe, high-performance inter-process channel 12 | implementation based on a shared memory ring buffer. It uses 13 | [bincode](https://github.com/TyOverby/bincode) for (de)serialization, including 14 | zero-copy deserialization, making it ideal for messages with large `&str` or 15 | `&[u8]` fields. And it has a name that rolls right off the tongue. 16 | 17 | ## Examples 18 | 19 | The examples directory contains a sender and receiver pair, which you can run 20 | in separate terminals like so: 21 | 22 | ```bash 23 | cargo run --example ipmpsc-receive -- --zero-copy /tmp/ipmpsc 24 | ``` 25 | 26 | ```bash 27 | cargo run --example ipmpsc-send -- /tmp/ipmpsc 28 | ``` 29 | 30 | Type some lines of text into the sender and observe that they are printed by 31 | the receiver. You can also run additional senders from other terminals -- the 32 | receiver will receive messages from any of them. 33 | 34 | ## Performance 35 | 36 | `ipmpsc::Receiver::zero_copy_context`, used in combination with 37 | [serde_bytes](https://github.com/serde-rs/bytes), is capable of supporting very 38 | high bandwidth, low latency transfers (e.g. uncompressed video frames). 39 | 40 | See the ipc-benchmarks subcrate for a few simple benchmarks that compare 41 | `ipmpsc` to `ipc_channel`'s high- and low-level interfaces. Here are the 42 | results from my Ubuntu laptop: 43 | 44 | ``` 45 | test tests::bench_ipc_channel ... bench: 16,013,621 ns/iter (+/- 932,033) 46 | test tests::bench_ipc_channel_bytes ... bench: 4,777,240 ns/iter (+/- 242,369) 47 | test tests::bench_ipmpsc ... bench: 1,380,406 ns/iter (+/- 62,038) 48 | ``` 49 | 50 | ## Security and Safety 51 | 52 | The ring buffer is backed by a shared memory-mapped file, which means any 53 | process with access to that file can read from or write to it depending on its 54 | privileges. This may or may not be acceptable depending on the security needs 55 | of your application and the environment in which it runs. 56 | 57 | Note that zero-copy deserialization can provide shared references to the mapped 58 | file, and internally ipmpsc uses both shared and unique references to segments 59 | of the file while reading from and writing to the ring buffer. These references 60 | are only safe if all processes which access the file obey Rust's memory safety 61 | rules (which normally only have meaning within a single process). ipmpsc itself 62 | should follow the rules (please report a bug if not), but safety cannot be 63 | guaranteed if any process fails to do so. 64 | 65 | ## Platform Support 66 | 67 | This library currently works on Linux, Android, and Windows. It does 68 | not work reliably on MacOS, unfortunately. See 69 | https://github.com/dicej/ipmpsc/issues/4 for details. PRs to fix that are welcome! 70 | 71 | ## Similar Projects 72 | 73 | [ipc-channel](https://github.com/servo/ipc-channel) - mature and robust IPC 74 | channels. Does not yet support Android, Windows, multiple simultaneous 75 | senders, or zero-copy deserialization. 76 | 77 | [shared_memory](https://github.com/elast0ny/shared_memory-rs) - low-level, 78 | cross-platform shared memory support. May be used as the basis for a 79 | ring-buffer based channel, but does not yet support Android. 80 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use vergen::{Config, ShaKind}; 3 | 4 | fn main() -> Result<()> { 5 | let mut config = Config::default(); 6 | *config.git_mut().sha_kind_mut() = ShaKind::Short; 7 | vergen::vergen(config) 8 | } 9 | -------------------------------------------------------------------------------- /examples/ipmpsc-receive.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use clap::{App, Arg}; 4 | use ipmpsc::{Receiver, SharedRingBuffer}; 5 | 6 | fn main() -> Result<(), Box> { 7 | let matches = App::new("ipmpsc-send") 8 | .about("ipmpsc sender example") 9 | .version(env!("CARGO_PKG_VERSION")) 10 | .author(env!("CARGO_PKG_AUTHORS")) 11 | .arg( 12 | Arg::with_name("map file") 13 | .help( 14 | "File to use for shared memory ring buffer. \ 15 | This file will be cleared if it already exists or created if it doesn't.", 16 | ) 17 | .required(true), 18 | ) 19 | .arg( 20 | Arg::with_name("zero copy") 21 | .long("zero-copy") 22 | .help("Use zero-copy deserialization"), 23 | ) 24 | .get_matches(); 25 | 26 | let map_file = matches.value_of("map file").unwrap(); 27 | let mut rx = Receiver::new(SharedRingBuffer::create(map_file, 32 * 1024)?); 28 | let zero_copy = matches.is_present("zero copy"); 29 | 30 | println!( 31 | "Ready! Now run `cargo run --example ipmpsc-send {}` in another terminal.", 32 | map_file 33 | ); 34 | 35 | loop { 36 | if zero_copy { 37 | println!("received {:?}", rx.zero_copy_context().recv::<&str>()?); 38 | } else { 39 | println!("received {:?}", rx.recv::()?); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/ipmpsc-send.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use clap::{App, Arg}; 4 | use ipmpsc::{Sender, SharedRingBuffer}; 5 | use std::io::{self, BufRead}; 6 | 7 | fn main() -> Result<(), Box> { 8 | let matches = App::new("ipmpsc-send") 9 | .about("ipmpsc sender example") 10 | .version(env!("CARGO_PKG_VERSION")) 11 | .author(env!("CARGO_PKG_AUTHORS")) 12 | .arg( 13 | Arg::with_name("map file") 14 | .help( 15 | "File to use for shared memory ring buffer. \ 16 | This should have already been created and initialized by the receiver.", 17 | ) 18 | .required(true), 19 | ) 20 | .get_matches(); 21 | 22 | let map_file = matches.value_of("map file").unwrap(); 23 | let tx = Sender::new(SharedRingBuffer::open(map_file)?); 24 | 25 | let mut buffer = String::new(); 26 | let stdin = io::stdin(); 27 | let mut handle = stdin.lock(); 28 | 29 | println!("Ready! Enter some lines of text to send them to the receiver."); 30 | 31 | while handle.read_line(&mut buffer)? > 0 { 32 | tx.send(&buffer)?; 33 | buffer.clear(); 34 | } 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /ipc-benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ipc-benchmarks" 3 | version = "0.1.0" 4 | authors = ["Joel Dice "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ipmpsc = { path = "..", features = ["fork"] } 9 | ipc-channel = "0.14" 10 | bincode = "1" 11 | serde = "1" 12 | serde_derive = "1" 13 | serde_bytes = "0.11" 14 | anyhow = "1" 15 | -------------------------------------------------------------------------------- /ipc-benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Interprocess communication benchmarks 2 | 3 | This crate provides a few simple benchmarks comparing `ipc_channel` to `ipmpsc`. 4 | 5 | ## Running 6 | 7 | ```bash 8 | cargo +nightly bench 9 | ``` 10 | -------------------------------------------------------------------------------- /ipc-benchmarks/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use serde_derive::{Deserialize, Serialize}; 6 | 7 | use std::time::Duration; 8 | 9 | #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] 10 | pub struct YuvFrameInfo { 11 | pub width: u32, 12 | pub height: u32, 13 | pub y_stride: u32, 14 | pub u_stride: u32, 15 | pub v_stride: u32, 16 | } 17 | 18 | #[derive(Serialize, Deserialize)] 19 | pub struct YuvFrame<'a> { 20 | pub info: YuvFrameInfo, 21 | #[serde(with = "serde_bytes")] 22 | pub y_pixels: &'a [u8], 23 | #[serde(with = "serde_bytes")] 24 | pub u_pixels: &'a [u8], 25 | #[serde(with = "serde_bytes")] 26 | pub v_pixels: &'a [u8], 27 | } 28 | 29 | #[derive(Serialize, Deserialize)] 30 | pub struct OwnedYuvFrame { 31 | pub info: YuvFrameInfo, 32 | #[serde(with = "serde_bytes")] 33 | pub y_pixels: Vec, 34 | #[serde(with = "serde_bytes")] 35 | pub u_pixels: Vec, 36 | #[serde(with = "serde_bytes")] 37 | pub v_pixels: Vec, 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use anyhow::{anyhow, Error, Result}; 44 | use ipc_channel::ipc; 45 | use ipmpsc::{Receiver, Sender, SharedRingBuffer}; 46 | use test::Bencher; 47 | 48 | const SMALL: (usize, usize) = (3, 2); 49 | const LARGE: (usize, usize) = (3840, 2160); 50 | 51 | fn y_stride(width: usize) -> usize { 52 | width 53 | } 54 | 55 | fn uv_stride(width: usize) -> usize { 56 | width / 2 57 | } 58 | 59 | #[bench] 60 | fn bench_ipmpsc_small(bencher: &mut Bencher) -> Result<()> { 61 | bench_ipmpsc(bencher, SMALL) 62 | } 63 | 64 | #[bench] 65 | fn bench_ipmpsc_large(bencher: &mut Bencher) -> Result<()> { 66 | bench_ipmpsc(bencher, LARGE) 67 | } 68 | 69 | fn bench_ipmpsc(bencher: &mut Bencher, (width, height): (usize, usize)) -> Result<()> { 70 | let (name, buffer) = SharedRingBuffer::create_temp(32 * 1024 * 1024)?; 71 | let mut rx = Receiver::new(buffer); 72 | 73 | let (exit_name, exit_buffer) = SharedRingBuffer::create_temp(1)?; 74 | let exit_tx = Sender::new(exit_buffer); 75 | 76 | let sender = ipmpsc::fork(move || { 77 | let buffer = SharedRingBuffer::open(&name)?; 78 | let tx = Sender::new(buffer); 79 | 80 | let exit_buffer = SharedRingBuffer::open(&exit_name)?; 81 | let exit_rx = Receiver::new(exit_buffer); 82 | 83 | let y_pixels = vec![128_u8; y_stride(width) * height]; 84 | let u_pixels = vec![192_u8; uv_stride(width) * height / 2]; 85 | let v_pixels = vec![255_u8; uv_stride(width) * height / 2]; 86 | 87 | let frame = YuvFrame { 88 | info: YuvFrameInfo { 89 | width: width as _, 90 | height: height as _, 91 | y_stride: y_stride(width) as _, 92 | u_stride: uv_stride(width) as _, 93 | v_stride: uv_stride(width) as _, 94 | }, 95 | y_pixels: &y_pixels, 96 | u_pixels: &u_pixels, 97 | v_pixels: &v_pixels, 98 | }; 99 | 100 | while exit_rx.try_recv::()?.is_none() { 101 | tx.send_timeout(&frame, Duration::from_millis(100))?; 102 | } 103 | 104 | Ok(()) 105 | })?; 106 | 107 | // wait for first frame to arrive 108 | { 109 | let mut context = rx.zero_copy_context(); 110 | if let Err(e) = context.recv::() { 111 | panic!("error receiving: {:?}", e); 112 | }; 113 | } 114 | 115 | bencher.iter(|| { 116 | let mut context = rx.zero_copy_context(); 117 | match context.recv::() { 118 | Err(e) => panic!("error receiving: {:?}", e), 119 | Ok(frame) => test::black_box(&frame), 120 | }; 121 | }); 122 | 123 | exit_tx.send(&1_u8)?; 124 | 125 | sender.join().map_err(|e| anyhow!("{:?}", e))??; 126 | 127 | Ok(()) 128 | } 129 | 130 | #[bench] 131 | fn bench_ipc_channel_small(bencher: &mut Bencher) -> Result<()> { 132 | bench_ipc_channel(bencher, SMALL) 133 | } 134 | 135 | #[bench] 136 | fn bench_ipc_channel_large(bencher: &mut Bencher) -> Result<()> { 137 | bench_ipc_channel(bencher, LARGE) 138 | } 139 | 140 | fn bench_ipc_channel(bencher: &mut Bencher, (width, height): (usize, usize)) -> Result<()> { 141 | let (tx, rx) = ipc::channel()?; 142 | 143 | let (exit_name, exit_buffer) = SharedRingBuffer::create_temp(1)?; 144 | let exit_tx = Sender::new(exit_buffer); 145 | 146 | let sender = ipmpsc::fork(move || { 147 | let exit_buffer = SharedRingBuffer::open(&exit_name)?; 148 | let exit_rx = Receiver::new(exit_buffer); 149 | 150 | while exit_rx.try_recv::()?.is_none() { 151 | let y_pixels = vec![128_u8; y_stride(width) * height]; 152 | let u_pixels = vec![192_u8; uv_stride(width) * height / 2]; 153 | let v_pixels = vec![255_u8; uv_stride(width) * height / 2]; 154 | 155 | let frame = OwnedYuvFrame { 156 | info: YuvFrameInfo { 157 | width: width as _, 158 | height: height as _, 159 | y_stride: y_stride(width) as _, 160 | u_stride: uv_stride(width) as _, 161 | v_stride: uv_stride(width) as _, 162 | }, 163 | y_pixels, 164 | u_pixels, 165 | v_pixels, 166 | }; 167 | 168 | if let Err(e) = tx.send(frame) { 169 | if exit_rx.try_recv::()?.is_none() { 170 | return Err(Error::from(e)); 171 | } else { 172 | break; 173 | } 174 | } 175 | } 176 | 177 | Ok(()) 178 | })?; 179 | 180 | // wait for first frame to arrive 181 | rx.recv().map_err(|e| anyhow!("{:?}", e))?; 182 | 183 | bencher.iter(|| { 184 | match rx.recv() { 185 | Err(e) => panic!("error receiving: {:?}", e), 186 | Ok(frame) => test::black_box(&frame), 187 | }; 188 | }); 189 | 190 | exit_tx.send(&1_u8)?; 191 | 192 | while rx.recv().is_ok() {} 193 | 194 | sender.join().map_err(|e| anyhow!("{:?}", e))??; 195 | 196 | Ok(()) 197 | } 198 | 199 | #[bench] 200 | fn bench_ipc_channel_bytes_small(bencher: &mut Bencher) -> Result<()> { 201 | bench_ipc_channel_bytes(bencher, SMALL) 202 | } 203 | 204 | #[bench] 205 | fn bench_ipc_channel_bytes_large(bencher: &mut Bencher) -> Result<()> { 206 | bench_ipc_channel_bytes(bencher, LARGE) 207 | } 208 | 209 | fn bench_ipc_channel_bytes( 210 | bencher: &mut Bencher, 211 | (width, height): (usize, usize), 212 | ) -> Result<()> { 213 | let (tx, rx) = ipc::bytes_channel()?; 214 | 215 | let (exit_name, exit_buffer) = SharedRingBuffer::create_temp(1)?; 216 | let exit_tx = Sender::new(exit_buffer); 217 | 218 | let sender = ipmpsc::fork(move || { 219 | let exit_buffer = SharedRingBuffer::open(&exit_name)?; 220 | let exit_rx = Receiver::new(exit_buffer); 221 | 222 | let y_pixels = vec![128_u8; y_stride(width) * height]; 223 | let u_pixels = vec![192_u8; uv_stride(width) * height / 2]; 224 | let v_pixels = vec![255_u8; uv_stride(width) * height / 2]; 225 | 226 | let frame = YuvFrame { 227 | info: YuvFrameInfo { 228 | width: width as _, 229 | height: height as _, 230 | y_stride: y_stride(width) as _, 231 | u_stride: uv_stride(width) as _, 232 | v_stride: uv_stride(width) as _, 233 | }, 234 | y_pixels: &y_pixels, 235 | u_pixels: &u_pixels, 236 | v_pixels: &v_pixels, 237 | }; 238 | 239 | let size = bincode::serialized_size(&frame).unwrap() as usize; 240 | let mut buffer = vec![0_u8; size]; 241 | 242 | while exit_rx.try_recv::()?.is_none() { 243 | bincode::serialize_into(&mut buffer as &mut [u8], &frame).unwrap(); 244 | if let Err(e) = tx.send(&buffer) { 245 | if exit_rx.try_recv::()?.is_none() { 246 | return Err(Error::from(e)); 247 | } else { 248 | break; 249 | } 250 | } 251 | } 252 | 253 | Ok(()) 254 | })?; 255 | 256 | // wait for first frame to arrive 257 | rx.recv().map_err(|e| anyhow!("{:?}", e))?; 258 | 259 | bencher.iter(|| { 260 | match rx.recv() { 261 | Err(e) => panic!("error receiving: {:?}", e), 262 | Ok(frame) => test::black_box(bincode::deserialize::(&frame).unwrap()), 263 | }; 264 | }); 265 | 266 | exit_tx.send(&1_u8)?; 267 | 268 | while rx.recv().is_ok() {} 269 | 270 | sender.join().map_err(|e| anyhow!("{:?}", e))??; 271 | 272 | Ok(()) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/bitmask.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::PhantomData, mem}; 2 | 3 | #[derive(Copy, Clone, Default)] 4 | pub struct BitMask(u128); 5 | 6 | impl BitMask { 7 | pub const fn capacity() -> u8 { 8 | (mem::size_of::() * 8) as u8 9 | } 10 | 11 | pub fn ones(self) -> impl Iterator { 12 | struct Ones; 13 | 14 | impl Strategy for Ones { 15 | fn accept(value: bool) -> bool { 16 | value 17 | } 18 | } 19 | 20 | BitMaskIterator:: { 21 | mask: self, 22 | index: 0, 23 | _data: PhantomData, 24 | } 25 | } 26 | 27 | pub fn zeros(self) -> impl Iterator { 28 | struct Zeros; 29 | 30 | impl Strategy for Zeros { 31 | fn accept(value: bool) -> bool { 32 | !value 33 | } 34 | } 35 | 36 | BitMaskIterator:: { 37 | mask: self, 38 | index: 0, 39 | _data: PhantomData, 40 | } 41 | } 42 | 43 | pub fn get(self, index: u8) -> bool { 44 | (self.0 & 1_u128.checked_shl(index.into()).unwrap()) != 0 45 | } 46 | 47 | pub fn set(self, index: u8) -> Self { 48 | Self(self.0 | 1_u128.checked_shl(index.into()).unwrap()) 49 | } 50 | 51 | pub fn clear(&self, index: u8) -> Self { 52 | Self(self.0 & !(1_u128.checked_shl(index.into()).unwrap())) 53 | } 54 | } 55 | 56 | trait Strategy { 57 | fn accept(value: bool) -> bool; 58 | } 59 | 60 | struct BitMaskIterator { 61 | mask: BitMask, 62 | index: u8, 63 | _data: PhantomData, 64 | } 65 | 66 | impl Iterator for BitMaskIterator { 67 | type Item = u8; 68 | 69 | fn next(&mut self) -> Option { 70 | for index in self.index..BitMask::capacity() { 71 | if T::accept(self.mask.get(index)) { 72 | self.index = index + 1; 73 | return Some(index); 74 | } 75 | } 76 | 77 | None 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Inter-Process Multiple Producer, Single Consumer Channels for Rust 2 | //! 3 | //! This library provides a type-safe, high-performance inter-process channel implementation based on a shared 4 | //! memory ring buffer. It uses [bincode](https://github.com/TyOverby/bincode) for (de)serialization, including 5 | //! zero-copy deserialization, making it ideal for messages with large `&str` or `&[u8]` fields. And it has a name 6 | //! that rolls right off the tongue. 7 | 8 | #![deny(warnings)] 9 | 10 | use memmap2::MmapMut; 11 | use os::{Buffer, Header, View}; 12 | use serde::{Deserialize, Serialize}; 13 | use std::{ 14 | cell::UnsafeCell, 15 | ffi::c_void, 16 | fs::{File, OpenOptions}, 17 | mem, 18 | sync::{ 19 | atomic::Ordering::{Acquire, Relaxed, Release}, 20 | Arc, 21 | }, 22 | time::{Duration, Instant}, 23 | }; 24 | use tempfile::NamedTempFile; 25 | use thiserror::Error as ThisError; 26 | 27 | #[cfg(unix)] 28 | mod posix; 29 | 30 | #[cfg(unix)] 31 | use posix as os; 32 | 33 | #[cfg(windows)] 34 | mod bitmask; 35 | 36 | #[cfg(windows)] 37 | mod windows; 38 | 39 | #[cfg(windows)] 40 | use windows as os; 41 | 42 | #[cfg(feature = "fork")] 43 | pub use os::test::fork; 44 | 45 | /// Crate version (e.g. for logging at runtime) 46 | pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 47 | 48 | /// Commit hash of code from which this crate was built, if available (e.g. for logging at runtime) 49 | pub const GIT_COMMIT_SHA_SHORT: Option<&str> = option_env!("VERGEN_GIT_SHA_SHORT"); 50 | 51 | /// Offset into shared memory file to find beginning of ring buffer data. 52 | const BEGINNING: u32 = mem::size_of::
() as u32; 53 | 54 | /// If set, indicates the ring buffer was created by a 64-bit process (32-bit otherwise) 55 | const FLAG_64_BIT: u32 = 1; 56 | 57 | /// `ipmpsc`-specific error type 58 | #[derive(ThisError, Debug)] 59 | pub enum Error { 60 | /// Error indicating that the caller has attempted to read more than one message from a given 61 | /// [`ZeroCopyContext`](struct.ZeroCopyContext.html). 62 | #[error("A ZeroCopyContext may only be used to receive one message")] 63 | AlreadyReceived, 64 | 65 | /// Error indicating that the caller attempted to send a message of zero serialized size, which is not 66 | /// supported. 67 | #[error("Serialized size of message is zero")] 68 | ZeroSizedMessage, 69 | 70 | /// Error indicating that the caller attempted to send a message of serialized size greater than the ring 71 | /// buffer capacity. 72 | #[error("Serialized size of message is too large for ring buffer")] 73 | MessageTooLarge, 74 | 75 | /// Error indicating the the maximum number of simultaneous senders has been exceeded. 76 | #[error("Too many simultaneous senders")] 77 | TooManySenders, 78 | 79 | /// Error indicating the ring buffer was initialized by an incompatible version of `ipmpsc` and/or by a process 80 | /// with a different word size (32-bit vs. 64-bit) 81 | #[error("Incompatible ring buffer (e.g. 32-bit vs. 64-bit or wrong ipmpsc version)")] 82 | IncompatibleRingBuffer, 83 | 84 | /// Implementation-specific runtime failure (e.g. a libc mutex error). 85 | #[error("{0}")] 86 | Runtime(String), 87 | 88 | /// Implementation-specific runtime I/O failure (e.g. filesystem error). 89 | #[error(transparent)] 90 | Io(#[from] std::io::Error), 91 | 92 | /// Wrapped bincode error encountered during (de)serialization. 93 | #[error(transparent)] 94 | Bincode(#[from] bincode::Error), 95 | } 96 | 97 | /// `ipmpsc`-specific Result type alias 98 | pub type Result = std::result::Result; 99 | 100 | fn flags() -> u32 { 101 | if mem::size_of::<*const c_void>() == 8 { 102 | FLAG_64_BIT 103 | } else { 104 | 0 105 | } 106 | } 107 | 108 | fn map(file: &File) -> Result { 109 | unsafe { 110 | let map = MmapMut::map_mut(file)?; 111 | 112 | #[allow(clippy::cast_ptr_alignment)] 113 | (*(map.as_ptr() as *const Header)).init()?; 114 | 115 | Ok(map) 116 | } 117 | } 118 | 119 | /// Represents a file-backed shared memory ring buffer, suitable for constructing a 120 | /// [`Receiver`](struct.Receiver.html) or [`Sender`](struct.Sender.html). 121 | /// 122 | /// Note that it is possible to create multiple [`SharedRingBuffer`](struct.SharedRingBuffer.html)s for a given 123 | /// path in a single process, but it is much more efficient to clone an exisiting instance than construct one from 124 | /// scratch using one of the constructors. 125 | #[derive(Clone)] 126 | pub struct SharedRingBuffer(View); 127 | 128 | unsafe impl Sync for SharedRingBuffer {} 129 | 130 | unsafe impl Send for SharedRingBuffer {} 131 | 132 | impl SharedRingBuffer { 133 | /// Creates a new [`SharedRingBuffer`](struct.SharedRingBuffer.html) backed by a file with the specified name. 134 | /// 135 | /// The file will be created if it does not already exist or truncated otherwise. 136 | /// 137 | /// Once this function completes successfully, the same path may be used to create one or more corresponding 138 | /// instances in other processes using the [`SharedRingBuffer::open`](struct.SharedRingBuffer.html#method.open) 139 | /// method. 140 | pub fn create(path: &str, size_in_bytes: u32) -> Result { 141 | let file = OpenOptions::new() 142 | .read(true) 143 | .write(true) 144 | .create(true) 145 | .truncate(true) 146 | .open(path)?; 147 | 148 | file.set_len(u64::from(BEGINNING + size_in_bytes + 8))?; 149 | 150 | Ok(Self(View::try_new(Arc::new(UnsafeCell::new( 151 | Buffer::try_new(path, map(&file)?, None)?, 152 | )))?)) 153 | } 154 | 155 | /// Creates a new [`SharedRingBuffer`](struct.SharedRingBuffer.html) backed by a temporary file which will be 156 | /// deleted when the [`SharedRingBuffer`](struct.SharedRingBuffer.html) is dropped. 157 | /// 158 | /// The name of the file is returned along with the [`SharedRingBuffer`](struct.SharedRingBuffer.html) and may 159 | /// be used to create one or more corresponding instances in other processes using the 160 | /// [`SharedRingBuffer::open`](struct.SharedRingBuffer.html#method.open) method. 161 | pub fn create_temp(size_in_bytes: u32) -> Result<(String, Self)> { 162 | let file = NamedTempFile::new()?; 163 | 164 | file.as_file() 165 | .set_len(u64::from(BEGINNING + size_in_bytes + 8))?; 166 | 167 | let path = file 168 | .path() 169 | .to_str() 170 | .ok_or_else(|| Error::Runtime("unable to represent path as string".into()))? 171 | .to_owned(); 172 | 173 | let map = map(file.as_file())?; 174 | 175 | Ok(( 176 | path.to_owned(), 177 | Self(View::try_new(Arc::new(UnsafeCell::new(Buffer::try_new( 178 | &path, 179 | map, 180 | Some(file), 181 | )?)))?), 182 | )) 183 | } 184 | 185 | /// Creates a new [`SharedRingBuffer`](struct.SharedRingBuffer.html) backed by a file with the specified name. 186 | /// 187 | /// The file must already exist and have been initialized by a call to 188 | /// [`SharedRingBuffer::create`](struct.SharedRingBuffer.html#method.create) or 189 | /// [`SharedRingBuffer::create_temp`](struct.SharedRingBuffer.html#method.create_temp). 190 | pub fn open(path: &str) -> Result { 191 | let file = OpenOptions::new().read(true).write(true).open(path)?; 192 | let map = unsafe { MmapMut::map_mut(&file)? }; 193 | 194 | let buffer = Buffer::try_new(path, map, None)?; 195 | 196 | if buffer.header().flags.load(Relaxed) != crate::flags() { 197 | return Err(Error::IncompatibleRingBuffer); 198 | } 199 | 200 | Ok(Self(View::try_new(Arc::new(UnsafeCell::new(buffer)))?)) 201 | } 202 | } 203 | 204 | /// Represents the receiving end of an inter-process channel, capable of receiving any message type implementing 205 | /// [`serde::Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html). 206 | pub struct Receiver(SharedRingBuffer); 207 | 208 | impl Receiver { 209 | /// Constructs a [`Receiver`](struct.Receiver.html) from the specified 210 | /// [`SharedRingBuffer`](struct.SharedRingBuffer.html) 211 | pub fn new(buffer: SharedRingBuffer) -> Self { 212 | Self(buffer) 213 | } 214 | 215 | fn seek(&self, position: u32) -> Result<()> { 216 | let buffer = self.0 .0.buffer(); 217 | let mut lock = buffer.lock()?; 218 | buffer.header().read.store(position, Relaxed); 219 | lock.notify_all() 220 | } 221 | 222 | /// Attempt to read a message without blocking. 223 | /// 224 | /// This will return `Ok(None)` if there are no messages immediately available. 225 | pub fn try_recv(&self) -> Result> 226 | where 227 | T: for<'de> Deserialize<'de>, 228 | { 229 | Ok(if let Some((value, position)) = self.try_recv_0()? { 230 | self.seek(position)?; 231 | 232 | Some(value) 233 | } else { 234 | None 235 | }) 236 | } 237 | 238 | fn try_recv_0<'a, T: Deserialize<'a>>(&'a self) -> Result> { 239 | let buffer = self.0 .0.buffer(); 240 | let map = buffer.map(); 241 | 242 | let mut read = buffer.header().read.load(Relaxed); 243 | let write = buffer.header().write.load(Acquire); 244 | 245 | Ok(loop { 246 | if write != read { 247 | let slice = map.as_ref(); 248 | let start = read + 4; 249 | let size = bincode::deserialize::(&slice[read as usize..start as usize])?; 250 | if size > 0 { 251 | let end = start + size; 252 | break Some(( 253 | bincode::deserialize(&slice[start as usize..end as usize])?, 254 | end, 255 | )); 256 | } else if write < read { 257 | read = BEGINNING; 258 | let mut lock = buffer.lock()?; 259 | buffer.header().read.store(read, Relaxed); 260 | lock.notify_all()?; 261 | } else { 262 | return Err(Error::Runtime("corrupt ring buffer".into())); 263 | } 264 | } else { 265 | break None; 266 | } 267 | }) 268 | } 269 | 270 | /// Attempt to read a message, blocking if necessary until one becomes available. 271 | pub fn recv(&self) -> Result 272 | where 273 | T: for<'de> Deserialize<'de>, 274 | { 275 | let (value, position) = self.recv_timeout_0(None)?.unwrap(); 276 | 277 | self.seek(position)?; 278 | 279 | Ok(value) 280 | } 281 | 282 | /// Attempt to read a message, blocking for up to the specified duration if necessary until one becomes 283 | /// available. 284 | pub fn recv_timeout(&self, timeout: Duration) -> Result> 285 | where 286 | T: for<'de> Deserialize<'de>, 287 | { 288 | Ok( 289 | if let Some((value, position)) = self.recv_timeout_0(Some(timeout))? { 290 | self.seek(position)?; 291 | 292 | Some(value) 293 | } else { 294 | None 295 | }, 296 | ) 297 | } 298 | 299 | /// Borrows this receiver for deserializing a message with references that refer directly to this 300 | /// [`Receiver`](struct.Receiver.html)'s ring buffer rather than copying out of it. 301 | /// 302 | /// Because those references refer directly to the ring buffer, the read pointer cannot be advanced until the 303 | /// lifetime of those references ends. 304 | /// 305 | /// To ensure the above, the following rules apply: 306 | /// 307 | /// 1. The underlying [`Receiver`](struct.Receiver.html) cannot be used while a 308 | /// [`ZeroCopyContext`](struct.ZeroCopyContext.html) borrows it (enforced at compile time). 309 | /// 310 | /// 2. References in a message deserialized using a given [`ZeroCopyContext`](struct.ZeroCopyContext.html) 311 | /// cannot outlive that instance (enforced at compile time). 312 | /// 313 | /// 3. A given [`ZeroCopyContext`](struct.ZeroCopyContext.html) can only be used to deserialize a single 314 | /// message before it must be discarded since the read pointer is advanced only when the instance is dropped 315 | /// (enforced at run time). 316 | pub fn zero_copy_context(&mut self) -> ZeroCopyContext { 317 | ZeroCopyContext { 318 | receiver: self, 319 | position: None, 320 | } 321 | } 322 | 323 | fn recv_timeout_0<'a, T: Deserialize<'a>>( 324 | &'a self, 325 | timeout: Option, 326 | ) -> Result> { 327 | let mut deadline = None; 328 | loop { 329 | if let Some(value_and_position) = self.try_recv_0()? { 330 | return Ok(Some(value_and_position)); 331 | } 332 | 333 | let buffer = self.0 .0.buffer(); 334 | 335 | let mut now = Instant::now(); 336 | deadline = deadline.or_else(|| timeout.map(|timeout| now + timeout)); 337 | 338 | let read = buffer.header().read.load(Relaxed); 339 | 340 | let mut lock = buffer.lock()?; 341 | while read == buffer.header().write.load(Acquire) { 342 | if deadline.map(|deadline| deadline > now).unwrap_or(true) { 343 | lock.timed_wait(&self.0 .0, deadline.map(|deadline| deadline - now))?; 344 | 345 | now = Instant::now(); 346 | } else { 347 | return Ok(None); 348 | } 349 | } 350 | } 351 | } 352 | } 353 | 354 | /// Borrows a [`Receiver`](struct.Receiver.html) for the purpose of doing zero-copy deserialization of messages 355 | /// containing references. 356 | /// 357 | /// An instance of this type may only be used to deserialize a single message before it is dropped because the 358 | /// [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) implementation is what advances the ring buffer 359 | /// pointer. Also, the borrowed [`Receiver`](struct.Receiver.html) may not be used directly while it is borrowed 360 | /// by a [`ZeroCopyContext`](struct.ZeroCopyContext.html). 361 | /// 362 | /// Use [`Receiver::zero_copy_context`](struct.Receiver.html#method.zero_copy_context) to create an instance. 363 | pub struct ZeroCopyContext<'a> { 364 | receiver: &'a Receiver, 365 | position: Option, 366 | } 367 | 368 | impl<'a> ZeroCopyContext<'a> { 369 | /// Attempt to read a message without blocking. 370 | /// 371 | /// This will return `Ok(None)` if there are no messages immediately available. It will return 372 | /// `Err(`[`Error::AlreadyReceived`](enum.Error.html#variant.AlreadyReceived)`))` if this instance has already 373 | /// been used to read a message. 374 | pub fn try_recv<'b, T: Deserialize<'b>>(&'b mut self) -> Result> { 375 | if self.position.is_some() { 376 | Err(Error::AlreadyReceived) 377 | } else { 378 | Ok( 379 | if let Some((value, position)) = self.receiver.try_recv_0()? { 380 | self.position = Some(position); 381 | Some(value) 382 | } else { 383 | None 384 | }, 385 | ) 386 | } 387 | } 388 | 389 | /// Attempt to read a message, blocking if necessary until one becomes available. 390 | /// 391 | /// This will return `Err(`[`Error::AlreadyReceived`](enum.Error.html#variant.AlreadyReceived)`))` if this 392 | /// instance has already been used to read a message. 393 | pub fn recv<'b, T: Deserialize<'b>>(&'b mut self) -> Result { 394 | let (value, position) = self.receiver.recv_timeout_0(None)?.unwrap(); 395 | 396 | self.position = Some(position); 397 | 398 | Ok(value) 399 | } 400 | 401 | /// Attempt to read a message, blocking for up to the specified duration if necessary until one becomes 402 | /// available. 403 | /// 404 | /// This will return `Err(`[`Error::AlreadyReceived`](enum.Error.html#variant.AlreadyReceived)`))` if this 405 | /// instance has already been used to read a message. 406 | pub fn recv_timeout<'b, T: Deserialize<'b>>( 407 | &'b mut self, 408 | timeout: Duration, 409 | ) -> Result> { 410 | if self.position.is_some() { 411 | Err(Error::AlreadyReceived) 412 | } else { 413 | Ok( 414 | if let Some((value, position)) = self.receiver.recv_timeout_0(Some(timeout))? { 415 | self.position = Some(position); 416 | Some(value) 417 | } else { 418 | None 419 | }, 420 | ) 421 | } 422 | } 423 | } 424 | 425 | impl<'a> Drop for ZeroCopyContext<'a> { 426 | fn drop(&mut self) { 427 | if let Some(position) = self.position.take() { 428 | let _ = self.receiver.seek(position); 429 | } 430 | } 431 | } 432 | 433 | /// Represents the sending end of an inter-process channel. 434 | #[derive(Clone)] 435 | pub struct Sender(SharedRingBuffer); 436 | 437 | impl Sender { 438 | /// Constructs a [`Sender`](struct.Sender.html) from the specified 439 | /// [`SharedRingBuffer`](struct.SharedRingBuffer.html) 440 | pub fn new(buffer: SharedRingBuffer) -> Self { 441 | Self(buffer) 442 | } 443 | 444 | /// Send the specified message, waiting for sufficient contiguous space to become available in the ring buffer 445 | /// if necessary. 446 | /// 447 | /// The serialized size of the message must be greater than zero or else this method will return 448 | /// `Err(`[`Error::ZeroSizedMessage`](enum.Error.html#variant.ZeroSizedMessage)`))`. If the serialized size is 449 | /// greater than the ring buffer capacity, this method will return 450 | /// `Err(`[`Error::MessageTooLarge`](enum.Error.html#variant.MessageTooLarge)`))`. 451 | pub fn send(&self, value: &impl Serialize) -> Result<()> { 452 | self.send_timeout_0(value, false, None).map(drop) 453 | } 454 | 455 | /// Send the specified message, waiting for sufficient contiguous space to become available in the ring buffer 456 | /// if necessary, but only up to the specified timeout. 457 | /// 458 | /// This will return `Ok(true)` if the message was sent, or `Ok(false)` if it timed out while waiting. 459 | /// 460 | /// The serialized size of the message must be greater than zero or else this method will return 461 | /// `Err(`[`Error::ZeroSizedMessage`](enum.Error.html#variant.ZeroSizedMessage)`))`. If the serialized size is 462 | /// greater than the ring buffer capacity, this method will return 463 | /// `Err(`[`Error::MessageTooLarge`](enum.Error.html#variant.MessageTooLarge)`))`. 464 | pub fn send_timeout(&self, value: &impl Serialize, timeout: Duration) -> Result { 465 | self.send_timeout_0(value, false, Some(timeout)) 466 | } 467 | 468 | /// Send the specified message, waiting for the ring buffer to become completely empty first. 469 | /// 470 | /// This method is appropriate for sending time-sensitive messages where buffering would introduce undesirable 471 | /// latency. 472 | /// 473 | /// The serialized size of the message must be greater than zero or else this method will return 474 | /// `Err(`[`Error::ZeroSizedMessage`](enum.Error.html#variant.ZeroSizedMessage)`))`. If the serialized size 475 | /// is greater than the ring buffer capacity, this method will return 476 | /// `Err(`[`Error::MessageTooLarge`](enum.Error.html#variant.MessageTooLarge)`))`. 477 | pub fn send_when_empty(&self, value: &impl Serialize) -> Result<()> { 478 | self.send_timeout_0(value, true, None).map(drop) 479 | } 480 | 481 | fn send_timeout_0( 482 | &self, 483 | value: &impl Serialize, 484 | wait_until_empty: bool, 485 | timeout: Option, 486 | ) -> Result { 487 | let buffer = self.0 .0.buffer(); 488 | let map = self.0 .0.map_mut(); 489 | 490 | let size = bincode::serialized_size(value)? as u32; 491 | 492 | if size == 0 { 493 | return Err(Error::ZeroSizedMessage); 494 | } 495 | 496 | let map_len = map.len(); 497 | 498 | if (BEGINNING + size + 8) as usize > map_len { 499 | return Err(Error::MessageTooLarge); 500 | } 501 | 502 | let mut lock = buffer.lock()?; 503 | let mut deadline = None; 504 | let mut write; 505 | loop { 506 | write = buffer.header().write.load(Relaxed); 507 | let read = buffer.header().read.load(Relaxed); 508 | 509 | if write == read || (write > read && !wait_until_empty) { 510 | if (write + size + 8) as usize <= map_len { 511 | break; 512 | } else if read != BEGINNING { 513 | assert!(write > BEGINNING); 514 | 515 | bincode::serialize_into( 516 | &mut map[write as usize..(write + 4) as usize], 517 | &0_u32, 518 | )?; 519 | write = BEGINNING; 520 | buffer.header().write.store(write, Release); 521 | lock.notify_all()?; 522 | continue; 523 | } 524 | } else if write + size + 8 <= read && !wait_until_empty { 525 | break; 526 | } 527 | 528 | let now = Instant::now(); 529 | deadline = deadline.or_else(|| timeout.map(|timeout| now + timeout)); 530 | 531 | if deadline.map(|deadline| deadline > now).unwrap_or(true) { 532 | lock.timed_wait(&self.0 .0, deadline.map(|deadline| deadline - now))?; 533 | } else { 534 | return Ok(false); 535 | } 536 | } 537 | 538 | let start = write + 4; 539 | bincode::serialize_into(&mut map[write as usize..start as usize], &size)?; 540 | 541 | let end = start + size; 542 | bincode::serialize_into(&mut map[start as usize..end as usize], value)?; 543 | 544 | buffer.header().write.store(end, Release); 545 | 546 | lock.notify_all()?; 547 | 548 | Ok(true) 549 | } 550 | } 551 | 552 | #[cfg(test)] 553 | mod tests { 554 | use super::*; 555 | use anyhow::{anyhow, Result}; 556 | use proptest::{arbitrary::any, collection::vec, prop_assume, proptest, strategy::Strategy}; 557 | use std::thread; 558 | 559 | #[derive(Debug)] 560 | struct Case { 561 | channel_size: u32, 562 | data: Vec>, 563 | sender_count: u32, 564 | } 565 | 566 | impl Case { 567 | fn run(&self) -> Result<()> { 568 | let (name, buffer) = SharedRingBuffer::create_temp(self.channel_size)?; 569 | let rx = Receiver::new(buffer); 570 | 571 | let receiver_thread = if self.sender_count == 1 { 572 | // Only one sender means we can expect to receive in a predictable order: 573 | let expected = self.data.clone(); 574 | thread::spawn(move || -> Result<()> { 575 | for item in &expected { 576 | let received = rx.recv::>()?; 577 | assert_eq!(item, &received); 578 | } 579 | 580 | Ok(()) 581 | }) 582 | } else { 583 | // Multiple senders mean we'll receive in an unpredictable order, so just verify we receive the 584 | // expected number of messages: 585 | let expected = self.data.len() * self.sender_count as usize; 586 | thread::spawn(move || -> Result<()> { 587 | for _ in 0..expected { 588 | rx.recv::>()?; 589 | } 590 | Ok(()) 591 | }) 592 | }; 593 | 594 | let data = Arc::new(self.data.clone()); 595 | let sender_threads = (0..self.sender_count) 596 | .map(move |_| { 597 | os::test::fork({ 598 | let name = name.clone(); 599 | let data = data.clone(); 600 | move || -> Result<()> { 601 | let tx = Sender::new(SharedRingBuffer::open(&name)?); 602 | 603 | for item in data.as_ref() { 604 | tx.send(item)?; 605 | } 606 | 607 | Ok(()) 608 | } 609 | }) 610 | }) 611 | .collect::>>()?; 612 | 613 | for thread in sender_threads { 614 | thread.join().map_err(|e| anyhow!("{:?}", e))??; 615 | } 616 | 617 | receiver_thread.join().map_err(|e| anyhow!("{:?}", e))??; 618 | 619 | Ok(()) 620 | } 621 | } 622 | 623 | fn arb_case() -> impl Strategy { 624 | ((32_u32..1024), (1_u32..5)).prop_flat_map(|(channel_size, sender_count)| { 625 | vec(vec(any::(), 0..(channel_size as usize - 24)), 1..1024).prop_map(move |data| { 626 | Case { 627 | channel_size, 628 | data, 629 | sender_count, 630 | } 631 | }) 632 | }) 633 | } 634 | 635 | #[test] 636 | fn simple_case() -> Result<()> { 637 | Case { 638 | channel_size: 1024, 639 | data: (0..1024) 640 | .map(|_| (0_u8..101).collect::>()) 641 | .collect::>(), 642 | sender_count: 1, 643 | } 644 | .run() 645 | } 646 | 647 | #[test] 648 | fn zero_copy() -> Result<()> { 649 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 650 | struct Foo<'a> { 651 | borrowed_str: &'a str, 652 | #[serde(with = "serde_bytes")] 653 | borrowed_bytes: &'a [u8], 654 | } 655 | 656 | let sent = Foo { 657 | borrowed_str: "hi", 658 | borrowed_bytes: &[0, 1, 2, 3], 659 | }; 660 | 661 | let (name, buffer) = SharedRingBuffer::create_temp(256)?; 662 | let mut rx = Receiver::new(buffer); 663 | let tx = Sender::new(SharedRingBuffer::open(&name)?); 664 | 665 | tx.send(&sent)?; 666 | tx.send(&42_u32)?; 667 | 668 | { 669 | let mut rx = rx.zero_copy_context(); 670 | let received = rx.recv()?; 671 | 672 | assert_eq!(sent, received); 673 | } 674 | 675 | assert_eq!(42_u32, rx.recv()?); 676 | 677 | Ok(()) 678 | } 679 | 680 | #[test] 681 | fn slow_sender_with_recv_timeout() -> Result<()> { 682 | let (name, buffer) = SharedRingBuffer::create_temp(256)?; 683 | let rx = Receiver::new(buffer); 684 | let tx = Sender::new(SharedRingBuffer::open(&name)?); 685 | 686 | let sender = os::test::fork(move || { 687 | thread::sleep(Duration::from_secs(1)); 688 | tx.send(&42_u32).map_err(anyhow::Error::from) 689 | })?; 690 | 691 | loop { 692 | if let Some(value) = rx.recv_timeout(Duration::from_millis(1))? { 693 | assert_eq!(42_u32, value); 694 | break; 695 | } 696 | } 697 | 698 | sender.join().map_err(|e| anyhow!("{:?}", e))??; 699 | 700 | Ok(()) 701 | } 702 | 703 | #[test] 704 | fn slow_receiver_with_send_timeout() -> Result<()> { 705 | let (name, buffer) = SharedRingBuffer::create_temp(256)?; 706 | let rx = Receiver::new(buffer); 707 | let tx = Sender::new(SharedRingBuffer::open(&name)?); 708 | 709 | let sender = os::test::fork(move || loop { 710 | if tx.send_timeout(&42_u32, Duration::from_millis(1))? { 711 | break Ok(()); 712 | } 713 | })?; 714 | 715 | thread::sleep(Duration::from_secs(1)); 716 | assert_eq!(42_u32, rx.recv()?); 717 | 718 | sender.join().map_err(|e| anyhow!("{:?}", e))??; 719 | 720 | Ok(()) 721 | } 722 | 723 | proptest! { 724 | #[test] 725 | fn arbitrary_case(case in arb_case()) { 726 | let result = thread::spawn(move || { 727 | let result = case.run(); 728 | if let Err(e) = &result { 729 | println!("\ntrouble: {:?}", e); 730 | } else if false { 731 | print!("."); 732 | std::io::Write::flush(&mut std::io::stdout())?; 733 | } 734 | result 735 | }).join().unwrap(); 736 | 737 | prop_assume!(result.is_ok(), "error: {:?}", result.unwrap_err()); 738 | } 739 | } 740 | } 741 | -------------------------------------------------------------------------------- /src/posix.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use memmap2::MmapMut; 3 | use std::{ 4 | cell::UnsafeCell, 5 | mem::MaybeUninit, 6 | os::raw::c_long, 7 | sync::{ 8 | atomic::{AtomicU32, Ordering::Relaxed}, 9 | Arc, 10 | }, 11 | time::{Duration, SystemTime}, 12 | }; 13 | use tempfile::NamedTempFile; 14 | 15 | // libc::PTHREAD_PROCESS_SHARED doesn't exist for Android for some 16 | // reason, so we need to declare it ourselves: 17 | #[cfg(target_os = "android")] 18 | const PTHREAD_PROCESS_SHARED: i32 = 1; 19 | 20 | #[cfg(not(target_os = "android"))] 21 | const PTHREAD_PROCESS_SHARED: i32 = libc::PTHREAD_PROCESS_SHARED; 22 | 23 | macro_rules! nonzero { 24 | ($x:expr) => {{ 25 | let x = $x; 26 | if x == 0 { 27 | Ok(()) 28 | } else { 29 | Err(Error::Runtime(format!("{} failed: {}", stringify!($x), x))) 30 | } 31 | }}; 32 | } 33 | 34 | #[repr(C)] 35 | pub struct Header { 36 | pub flags: AtomicU32, 37 | mutex: UnsafeCell, 38 | condition: UnsafeCell, 39 | pub read: AtomicU32, 40 | pub write: AtomicU32, 41 | } 42 | 43 | impl Header { 44 | pub fn init(&self) -> Result<()> { 45 | self.flags.store(crate::flags(), Relaxed); 46 | 47 | unsafe { 48 | let mut attr = MaybeUninit::::uninit(); 49 | nonzero!(libc::pthread_mutexattr_init(attr.as_mut_ptr()))?; 50 | nonzero!(libc::pthread_mutexattr_setpshared( 51 | attr.as_mut_ptr(), 52 | PTHREAD_PROCESS_SHARED 53 | ))?; 54 | nonzero!(libc::pthread_mutex_init(self.mutex.get(), attr.as_ptr()))?; 55 | nonzero!(libc::pthread_mutexattr_destroy(attr.as_mut_ptr()))?; 56 | 57 | let mut attr = MaybeUninit::::uninit(); 58 | nonzero!(libc::pthread_condattr_init(attr.as_mut_ptr()))?; 59 | nonzero!(libc::pthread_condattr_setpshared( 60 | attr.as_mut_ptr(), 61 | PTHREAD_PROCESS_SHARED 62 | ))?; 63 | nonzero!(libc::pthread_cond_init(self.condition.get(), attr.as_ptr()))?; 64 | nonzero!(libc::pthread_condattr_destroy(attr.as_mut_ptr()))?; 65 | } 66 | 67 | self.read.store(crate::BEGINNING, Relaxed); 68 | self.write.store(crate::BEGINNING, Relaxed); 69 | 70 | Ok(()) 71 | } 72 | } 73 | 74 | #[derive(Clone)] 75 | pub struct View(Arc>); 76 | 77 | impl View { 78 | pub fn try_new(buffer: Arc>) -> Result { 79 | Ok(View(buffer)) 80 | } 81 | 82 | pub fn buffer(&self) -> &Buffer { 83 | unsafe { &*self.0.get() } 84 | } 85 | 86 | #[allow(clippy::mut_from_ref)] 87 | pub fn map_mut(&self) -> &mut MmapMut { 88 | unsafe { (*self.0.get()).map_mut() } 89 | } 90 | } 91 | 92 | pub struct Buffer { 93 | map: MmapMut, 94 | _file: Option, 95 | } 96 | 97 | impl Buffer { 98 | pub fn try_new(_path: &str, map: MmapMut, file: Option) -> Result { 99 | Ok(Buffer { map, _file: file }) 100 | } 101 | 102 | pub fn header(&self) -> &Header { 103 | #[allow(clippy::cast_ptr_alignment)] 104 | unsafe { 105 | &*(self.map.as_ptr() as *const Header) 106 | } 107 | } 108 | 109 | pub fn lock(&self) -> Result { 110 | Lock::try_new(self) 111 | } 112 | 113 | pub fn map(&self) -> &MmapMut { 114 | &self.map 115 | } 116 | 117 | pub fn map_mut(&mut self) -> &mut MmapMut { 118 | &mut self.map 119 | } 120 | } 121 | 122 | pub struct Lock<'a>(&'a Buffer); 123 | 124 | impl<'a> Lock<'a> { 125 | pub fn try_new(buffer: &Buffer) -> Result { 126 | unsafe { 127 | nonzero!(libc::pthread_mutex_lock(buffer.header().mutex.get()))?; 128 | } 129 | Ok(Lock(buffer)) 130 | } 131 | 132 | pub fn notify_all(&mut self) -> Result<()> { 133 | unsafe { 134 | nonzero!(libc::pthread_cond_broadcast( 135 | self.0.header().condition.get() 136 | )) 137 | } 138 | } 139 | 140 | pub fn wait(&mut self, _view: &View) -> Result<()> { 141 | unsafe { 142 | nonzero!(libc::pthread_cond_wait( 143 | self.0.header().condition.get(), 144 | self.0.header().mutex.get() 145 | )) 146 | } 147 | } 148 | 149 | #[allow(clippy::cast_lossless)] 150 | pub fn timed_wait(&mut self, view: &View, timeout: Option) -> Result<()> { 151 | if let Some(timeout) = timeout { 152 | let then = SystemTime::now() 153 | .duration_since(SystemTime::UNIX_EPOCH) 154 | .unwrap() 155 | + timeout; 156 | 157 | let then = libc::timespec { 158 | tv_sec: then.as_secs() as libc::time_t, 159 | tv_nsec: then.subsec_nanos() as c_long, 160 | }; 161 | 162 | let timeout_ok = |result| if result == libc::ETIMEDOUT { 0 } else { result }; 163 | 164 | unsafe { 165 | nonzero!(timeout_ok(libc::pthread_cond_timedwait( 166 | self.0.header().condition.get(), 167 | self.0.header().mutex.get(), 168 | &then 169 | ))) 170 | } 171 | } else { 172 | self.wait(view) 173 | } 174 | } 175 | } 176 | 177 | impl<'a> Drop for Lock<'a> { 178 | fn drop(&mut self) { 179 | unsafe { 180 | libc::pthread_mutex_unlock(self.0.header().mutex.get()); 181 | } 182 | } 183 | } 184 | 185 | #[cfg(any(test, feature = "fork"))] 186 | pub mod test { 187 | use anyhow::{anyhow, Result}; 188 | use errno::errno; 189 | use std::{ 190 | ffi::c_void, 191 | io::Write, 192 | os::raw::c_int, 193 | process, 194 | thread::{self, JoinHandle}, 195 | }; 196 | 197 | struct Descriptor(c_int); 198 | 199 | impl Descriptor { 200 | fn forward(&self, dst: &mut dyn Write) -> Result<()> { 201 | let mut buffer = [0u8; 1024]; 202 | 203 | loop { 204 | let count = 205 | unsafe { libc::read(self.0, buffer.as_mut_ptr() as *mut c_void, buffer.len()) }; 206 | 207 | match count { 208 | -1 => { 209 | break Err(anyhow!("unable to read; errno: {}", errno())); 210 | } 211 | 0 => { 212 | break Ok(()); 213 | } 214 | _ => { 215 | dst.write_all(&buffer[..count as usize])?; 216 | } 217 | } 218 | } 219 | } 220 | } 221 | 222 | impl Drop for Descriptor { 223 | fn drop(&mut self) { 224 | unsafe { libc::close(self.0) }; 225 | } 226 | } 227 | 228 | fn pipe() -> Result<(Descriptor, Descriptor)> { 229 | let mut fds = [0; 2]; 230 | if -1 == unsafe { libc::pipe(fds.as_mut_ptr()) } { 231 | return Err(anyhow!("pipe failed; errno: {}", errno())); 232 | } 233 | 234 | Ok((Descriptor(fds[0]), Descriptor(fds[1]))) 235 | } 236 | 237 | pub fn fork Result<()>>( 238 | fun: F, 239 | ) -> Result>> { 240 | let (_, out_tx) = pipe()?; 241 | let (err_rx, err_tx) = pipe()?; 242 | let (alive_rx, alive_tx) = pipe()?; 243 | 244 | match unsafe { libc::fork() } { 245 | -1 => Err(anyhow!("fork failed; errno: {}", errno())), 246 | 0 => { 247 | // I'm the child process -- forward stdout and stderr to parent, call function, and exit, but exit 248 | // early if parent dies 249 | 250 | drop(alive_tx); 251 | 252 | thread::spawn(move || { 253 | let mut buffer = [0u8; 1]; 254 | 255 | unsafe { 256 | libc::read(alive_rx.0, buffer.as_mut_ptr() as *mut c_void, buffer.len()) 257 | }; 258 | 259 | // if the above read returns, we assume the parent is dead, so time to exit 260 | process::exit(1); 261 | }); 262 | 263 | // stdout (FD 1) 264 | if -1 == unsafe { libc::dup2(out_tx.0, 1) } { 265 | return Err(anyhow!("dup2 failed; errno: {}", errno())); 266 | } 267 | 268 | // stderr (FD 2) 269 | if -1 == unsafe { libc::dup2(err_tx.0, 2) } { 270 | return Err(anyhow!("dup2 failed; errno: {}", errno())); 271 | } 272 | 273 | if let Err(e) = fun() { 274 | eprintln!("{:?}", e); 275 | process::exit(1); 276 | } else { 277 | process::exit(0); 278 | } 279 | } 280 | pid => { 281 | // I'm the parent process -- spawn a thread to monitor the child 282 | 283 | Ok(thread::spawn(move || { 284 | let _alive_tx = alive_tx; 285 | let mut stderr = Vec::::new(); 286 | err_rx.forward(&mut stderr)?; 287 | 288 | let mut status = 0; 289 | if -1 == unsafe { libc::waitpid(pid, &mut status, 0) } { 290 | return Err(anyhow!("waitpid failed; errno: {}", errno())); 291 | } 292 | 293 | if libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0 { 294 | Ok(()) 295 | } else { 296 | Err(anyhow!( 297 | "child exited{}{}", 298 | if libc::WIFEXITED(status) { 299 | format!(" (exit status {})", libc::WEXITSTATUS(status)) 300 | } else if libc::WIFSIGNALED(status) { 301 | format!(" (killed by signal {})", libc::WTERMSIG(status)) 302 | } else { 303 | String::new() 304 | }, 305 | if stderr.is_empty() { 306 | String::new() 307 | } else { 308 | format!("; stderr:\n{}", String::from_utf8_lossy(&stderr)) 309 | } 310 | )) 311 | } 312 | })) 313 | } 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | use crate::{bitmask::BitMask, Error, Result}; 2 | use memmap2::MmapMut; 3 | use sha2::{Digest, Sha256}; 4 | use std::{ 5 | cell::UnsafeCell, 6 | convert::TryInto, 7 | ffi::{CStr, CString}, 8 | ptr, slice, 9 | sync::{ 10 | atomic::{AtomicU32, Ordering::Relaxed}, 11 | Arc, Mutex, 12 | }, 13 | time::Duration, 14 | }; 15 | use tempfile::NamedTempFile; 16 | use winapi::{ 17 | shared::{ 18 | minwindef::{self, LPVOID, ULONG}, 19 | winerror, 20 | }, 21 | um::{ 22 | errhandlingapi, handleapi, synchapi, winbase, 23 | winnt::{HANDLE, LPSTR}, 24 | }, 25 | }; 26 | 27 | pub struct Defer(F); 28 | 29 | impl Drop for Defer { 30 | fn drop(&mut self) { 31 | (self.0)(); 32 | } 33 | } 34 | 35 | macro_rules! defer { 36 | ($e:expr) => { 37 | let _defer = Defer($e); 38 | }; 39 | } 40 | 41 | fn sha256(string: &str) -> String { 42 | let mut hasher = Sha256::default(); 43 | 44 | hasher.update(string.as_bytes()); 45 | 46 | hex::encode(&hasher.finalize() as &[u8]) 47 | } 48 | 49 | fn get_last_error() -> String { 50 | unsafe { 51 | let error = errhandlingapi::GetLastError(); 52 | 53 | if error == 0 { 54 | None 55 | } else { 56 | let mut buffer: LPSTR = ptr::null_mut(); 57 | let size = winbase::FormatMessageA( 58 | winbase::FORMAT_MESSAGE_ALLOCATE_BUFFER 59 | | winbase::FORMAT_MESSAGE_FROM_SYSTEM 60 | | winbase::FORMAT_MESSAGE_IGNORE_INSERTS, 61 | ptr::null(), 62 | error, 63 | 0, 64 | &mut buffer as *mut _ as LPSTR, 65 | 0, 66 | ptr::null_mut(), 67 | ); 68 | 69 | if buffer.is_null() { 70 | None 71 | } else { 72 | defer!(|| { 73 | winbase::LocalFree(buffer as LPVOID); 74 | }); 75 | 76 | slice::from_raw_parts_mut(buffer, size as usize)[(size - 1) as usize] = 0; 77 | 78 | CStr::from_ptr(buffer) 79 | .to_str() 80 | .ok() 81 | .map(|s| s.trim().to_owned()) 82 | } 83 | } 84 | } 85 | .unwrap_or_else(|| "unknown error".to_owned()) 86 | } 87 | 88 | macro_rules! expect { 89 | ($x:expr) => {{ 90 | let x = $x; 91 | if x { 92 | Ok(()) 93 | } else { 94 | Err(Error::Runtime(format!( 95 | "{} failed: {}", 96 | stringify!($x), 97 | get_last_error() 98 | ))) 99 | } 100 | }}; 101 | } 102 | 103 | #[repr(C)] 104 | pub struct Header { 105 | pub flags: AtomicU32, 106 | threads: UnsafeCell, 107 | waiters: UnsafeCell, 108 | pub read: AtomicU32, 109 | pub write: AtomicU32, 110 | } 111 | 112 | impl Header { 113 | pub fn init(&self) -> Result<()> { 114 | self.flags.store(crate::flags(), Relaxed); 115 | 116 | unsafe { 117 | *self.threads.get() = BitMask::default(); 118 | *self.waiters.get() = BitMask::default(); 119 | } 120 | 121 | self.read.store(crate::BEGINNING, Relaxed); 122 | self.write.store(crate::BEGINNING, Relaxed); 123 | 124 | Ok(()) 125 | } 126 | } 127 | 128 | pub struct View { 129 | buffer: Arc>, 130 | index: u8, 131 | } 132 | 133 | impl View { 134 | pub fn try_new(buffer: Arc>) -> Result { 135 | let mut lock = unsafe { (*buffer.get()).lock()? }; 136 | 137 | let index = lock.threads().zeros().next().ok_or(Error::TooManySenders)?; 138 | 139 | *lock.threads() = lock.threads().set(index); 140 | 141 | Ok(Self { buffer, index }) 142 | } 143 | 144 | pub fn buffer(&self) -> &Buffer { 145 | unsafe { &*self.buffer.get() } 146 | } 147 | 148 | #[allow(clippy::mut_from_ref)] 149 | pub fn map_mut(&self) -> &mut MmapMut { 150 | unsafe { (*self.buffer.get()).map_mut() } 151 | } 152 | } 153 | 154 | impl Clone for View { 155 | fn clone(&self) -> Self { 156 | Self::try_new(self.buffer.clone()).unwrap() 157 | } 158 | } 159 | 160 | impl Drop for View { 161 | fn drop(&mut self) { 162 | if let Ok(mut lock) = self.buffer().lock() { 163 | *lock.threads() = lock.threads().clear(self.index); 164 | } 165 | } 166 | } 167 | 168 | pub struct Buffer { 169 | map: MmapMut, 170 | unique_id: String, 171 | _file: Option, 172 | mutex: HANDLE, 173 | semaphores: Mutex<[HANDLE; BitMask::capacity() as usize]>, 174 | } 175 | 176 | impl Buffer { 177 | pub fn try_new(path: &str, map: MmapMut, file: Option) -> Result { 178 | let mut buffer = Self { 179 | map, 180 | // We derive the mutex and semaphore names from a hex-encoded hash of the path to ensure they're 181 | // unique, predictable, and contain no disallowed characters 182 | unique_id: sha256(path), 183 | _file: file, 184 | mutex: ptr::null_mut(), 185 | semaphores: Mutex::new([ptr::null_mut(); BitMask::capacity() as usize]), 186 | }; 187 | 188 | let mutex_string = format!("ipmpsc-mutex-{}", buffer.unique_id); 189 | let mutex_name = CString::new(mutex_string.clone()) 190 | .expect("should not fail -- unique_id should have no null characters"); 191 | 192 | buffer.mutex = unsafe { 193 | synchapi::CreateMutexA(ptr::null_mut(), minwindef::FALSE, mutex_name.as_ptr()) 194 | }; 195 | 196 | if buffer.mutex.is_null() { 197 | return Err(Error::Runtime(format!( 198 | "CreateMutex for {} failed: {}", 199 | mutex_string, 200 | get_last_error() 201 | ))); 202 | } 203 | 204 | Ok(buffer) 205 | } 206 | 207 | fn semaphore(&self, index: u8) -> Result { 208 | let index = index as usize; 209 | let mut semaphores = self.semaphores.lock().unwrap(); 210 | 211 | if semaphores[index].is_null() { 212 | let semaphore_string = format!("ipmpsc-semaphore-{}-{}", self.unique_id, index); 213 | let semaphore_name = CString::new(semaphore_string.clone()) 214 | .expect("should not fail -- unique_id should have no null characters"); 215 | 216 | semaphores[index] = unsafe { 217 | winbase::CreateSemaphoreA(ptr::null_mut(), 0, 1, semaphore_name.as_ptr()) 218 | }; 219 | 220 | if semaphores[index].is_null() { 221 | return Err(Error::Runtime(format!( 222 | "CreateSemaphore for {} failed: {}", 223 | semaphore_string, 224 | get_last_error() 225 | ))); 226 | } 227 | } 228 | 229 | Ok(semaphores[index]) 230 | } 231 | 232 | pub fn header(&self) -> &Header { 233 | #[allow(clippy::cast_ptr_alignment)] 234 | unsafe { 235 | &*(self.map.as_ptr() as *const Header) 236 | } 237 | } 238 | 239 | pub fn lock(&self) -> Result { 240 | Lock::try_new(self) 241 | } 242 | 243 | pub fn map(&self) -> &MmapMut { 244 | &self.map 245 | } 246 | 247 | pub fn map_mut(&mut self) -> &mut MmapMut { 248 | &mut self.map 249 | } 250 | } 251 | 252 | impl Drop for Buffer { 253 | fn drop(&mut self) { 254 | if !self.mutex.is_null() { 255 | unsafe { handleapi::CloseHandle(self.mutex) }; 256 | } 257 | 258 | for &semaphore in self.semaphores.lock().unwrap().iter() { 259 | if !semaphore.is_null() { 260 | unsafe { handleapi::CloseHandle(semaphore) }; 261 | } 262 | } 263 | } 264 | } 265 | 266 | pub struct Lock<'a> { 267 | locked: bool, 268 | buffer: &'a Buffer, 269 | } 270 | 271 | impl<'a> Lock<'a> { 272 | pub fn try_new(buffer: &Buffer) -> Result { 273 | expect!( 274 | winbase::WAIT_OBJECT_0 275 | == unsafe { synchapi::WaitForSingleObject(buffer.mutex, winbase::INFINITE) } 276 | )?; 277 | 278 | Ok(Lock { 279 | locked: true, 280 | buffer, 281 | }) 282 | } 283 | 284 | fn do_wait(&mut self, view: &View, milliseconds: ULONG) -> Result<()> { 285 | let index = view.index; 286 | 287 | *self.waiters() = self.waiters().set(index); 288 | 289 | expect!(minwindef::TRUE == unsafe { synchapi::ReleaseMutex(self.buffer.mutex) })?; 290 | 291 | self.locked = false; 292 | 293 | expect!(matches!( 294 | unsafe { synchapi::WaitForSingleObject(self.buffer.semaphore(index)?, milliseconds) }, 295 | winbase::WAIT_OBJECT_0 | winerror::WAIT_TIMEOUT 296 | ))?; 297 | 298 | expect!( 299 | winbase::WAIT_OBJECT_0 300 | == unsafe { synchapi::WaitForSingleObject(self.buffer.mutex, winbase::INFINITE) } 301 | )?; 302 | 303 | self.locked = true; 304 | 305 | *self.waiters() = self.waiters().clear(index); 306 | 307 | Ok(()) 308 | } 309 | 310 | pub fn wait(&mut self, view: &View) -> Result<()> { 311 | self.do_wait(view, winbase::INFINITE) 312 | } 313 | 314 | pub fn timed_wait(&mut self, view: &View, timeout: Option) -> Result<()> { 315 | if let Some(timeout) = timeout { 316 | self.do_wait( 317 | view, 318 | timeout.as_millis().try_into().map_err(|_| { 319 | Error::Runtime("unable to represent timeout in milliseconds as ULONG".into()) 320 | })?, 321 | ) 322 | } else { 323 | self.wait(view) 324 | } 325 | } 326 | 327 | pub fn notify_all(&mut self) -> Result<()> { 328 | let waiters = *self.waiters(); 329 | 330 | for index in waiters.ones() { 331 | // We're ignoring the return value below because TRUE means the sempahore was successfully incremented 332 | // and FALSE (presumably) means it had already been incremented to its maximum, which is also fine. 333 | // 334 | // Unfortunately, there's no reliable way to distinguish between different error conditions 335 | // programatically since the exact code returned by GetLastError is not considered part of the 336 | // supported Windows API by Microsoft. 337 | unsafe { 338 | synchapi::ReleaseSemaphore(self.buffer.semaphore(index)?, 1, ptr::null_mut()) 339 | }; 340 | } 341 | 342 | *self.waiters() = BitMask::default(); 343 | 344 | Ok(()) 345 | } 346 | 347 | fn waiters(&mut self) -> &mut BitMask { 348 | unsafe { &mut *self.buffer.header().waiters.get() } 349 | } 350 | 351 | fn threads(&mut self) -> &mut BitMask { 352 | unsafe { &mut *self.buffer.header().threads.get() } 353 | } 354 | } 355 | 356 | impl<'a> Drop for Lock<'a> { 357 | fn drop(&mut self) { 358 | if self.locked { 359 | unsafe { synchapi::ReleaseMutex(self.buffer.mutex) }; 360 | } 361 | } 362 | } 363 | 364 | #[cfg(any(test, feature = "fork"))] 365 | pub mod test { 366 | use anyhow::Result; 367 | use std::thread::{self, JoinHandle}; 368 | 369 | pub fn fork Result<()>>( 370 | fun: F, 371 | ) -> Result>> { 372 | Ok(thread::spawn(fun)) 373 | } 374 | } 375 | --------------------------------------------------------------------------------