├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── aio-bindings ├── Cargo.toml ├── build.rs ├── src │ └── lib.rs └── wrapper.h └── src ├── aio.rs ├── eventfd.rs ├── lib.rs └── sync.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | **/target/ 13 | **/*.rs.bk 14 | Cargo.lock 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | addons: 5 | apt: 6 | packages: 7 | - libcurl4-openssl-dev 8 | - libelf-dev 9 | - libdw-dev 10 | - binutils-dev 11 | - gcc-multilib 12 | 13 | rust: 14 | - stable 15 | - beta 16 | - nightly 17 | matrix: 18 | allow_failures: 19 | - rust: nightly 20 | cache: cargo 21 | 22 | script: 23 | - cargo build --verbose --all 24 | - cargo test --verbose --all 25 | 26 | after_success: 27 | - cargo install cargo-kcov 28 | - cargo kcov --print-install-kcov-sh | bash 29 | - cargo kcov --coveralls -- --verify 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-linux-aio" 3 | version = "0.1.11" 4 | authors = ["Hans-Martin Will "] 5 | description = "Tokio bindings for Linux kernel AIO" 6 | documentation = "https://docs.rs/tokio-linux-aio/" 7 | repository= "https://github.com/hmwill/tokio-linux-aio" 8 | license = "MIT" 9 | readme = "README.md" 10 | categories = ["api-bindings", "asynchronous", "external-ffi-bindings", "filesystem", "os::unix-apis"] 11 | keywords = ["async", "non-blocking", "futures", "linux", "aio"] 12 | 13 | [badges] 14 | travis-ci = { repository = "hmwill/tokio-linux-aio" } 15 | 16 | [dependencies] 17 | rand = "0.6" 18 | tokio = "0.1" 19 | futures = "0.1" 20 | futures-cpupool = "0.1" 21 | mio = "0.6.13" 22 | aio-bindings = { path = "aio-bindings", version = "0.1.2" } 23 | libc = "0.2" 24 | memmap = "0.7.0" 25 | 26 | [workspace] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hans-Martin Will 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 | # tokio-linux-aio 2 | 3 | [![Version](https://img.shields.io/crates/v/tokio-linux-aio.svg)](https://crates.io/crates/tokio-linux-aio) 4 | [![License](https://img.shields.io/crates/l/tokio-linux-aio.svg)](https://github.com/hmwill/tokio-linux-aio/blob/master/LICENSE) 5 | [![Docs](https://docs.rs/tokio-linux-aio/badge.svg)](https://docs.rs/tokio-linux-aio/) 6 | [![Build Status](https://travis-ci.org/hmwill/tokio-linux-aio.svg?branch=master)](https://travis-ci.org/hmwill/tokio-linux-aio) 7 | [![Join the chat at https://gitter.im/tokio-linux-aio/Lobby](https://badges.gitter.im/tokio-linux-aio/Lobby.svg)](https://gitter.im/tokio-linux-aio/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | 9 | This package provides an integration of Linux kernel-level asynchronous I/O to the [Tokio platform](https://tokio.rs/). 10 | 11 | Linux kernel-level asynchronous I/O is different from the [Posix AIO library](http://man7.org/linux/man-pages/man7/aio.7.html). Posix AIO is implemented using a pool of userland threads, which invoke regular, blocking system calls to perform file I/O. [Linux kernel-level AIO](http://lse.sourceforge.net/io/aio.html), on the other hand, provides kernel-level asynchronous scheduling of I/O operations to the underlying block device. 12 | 13 | __Note__: Implementation and test development is still in progress. I'm waiting for tokio 0.2 to stabilize before doing a 14 | next revision of this crate. In the interim, I'm working on vervolg, an implementation of a front-end for a subset of the 15 | SQL language. Overall, my goal is to put together a test bed and experimentation platform for database kernels. 16 | 17 | ## Usage 18 | 19 | Add this to your `Cargo.toml`: 20 | 21 | [dependencies] 22 | tokio-linux-aio = "0.1" 23 | 24 | Next, add this to the root module of your crate: 25 | 26 | extern crate tokio_linux_aio; 27 | 28 | ## Examples 29 | 30 | Once you have added the crate to your project you should be able to write something like this: 31 | 32 | ```rust 33 | // Let's use a standard thread pool 34 | let pool = futures_cpupool::CpuPool::new(5); 35 | 36 | // These are handle objects for memory regions 37 | let buffer = MemoryHandle::new(); 38 | 39 | { 40 | // Here we go: create an execution context, which uses the pool for background work 41 | let context = AioContext::new(&pool, 10).unwrap(); 42 | 43 | // Create a future to read from a given file (fd) at the given offset into our buffer 44 | let read_future = context 45 | .read(fd, 0, buffer) 46 | .map(move |result_buffer| { 47 | // do something upon successfully reading the data 48 | assert!(validate_block(result_buffer.as_ref())); 49 | }) 50 | .map_err(|err| { 51 | // do something else when things go wrong 52 | panic!("{:?}", err); 53 | }); 54 | 55 | // Execute the future and wait for its completion 56 | let cpu_future = pool.spawn(read_future); 57 | let result = cpu_future.wait(); 58 | 59 | // Should be OK 60 | assert!(result.is_ok()); 61 | } 62 | ``` 63 | 64 | ## License 65 | 66 | This code is licensed under the [MIT license](https://github.com/hmwill/tokio-linux-aio/blob/master/LICENSE). 67 | -------------------------------------------------------------------------------- /aio-bindings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aio-bindings" 3 | version = "0.1.2" 4 | authors = ["Hans-Martin Will "] 5 | description = "Native kernel call bindings for Linux AIO" 6 | repository="https://github.com/hmwill/tokio-linux-aio/aio-bindings" 7 | license = "MIT" 8 | 9 | [dependencies] 10 | 11 | [build-dependencies] 12 | bindgen = "0.47.1" 13 | -------------------------------------------------------------------------------- /aio-bindings/build.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | let target = env::var("TARGET").expect("Cargo build scripts always have TARGET"); 8 | 9 | // The bindgen::Builder is the main entry point 10 | // to bindgen, and lets you build up options for 11 | // the resulting bindings. 12 | let mut bindings = bindgen::Builder::default() 13 | .trust_clang_mangling(false) 14 | .clang_arg("-target") 15 | .clang_arg(target); 16 | 17 | if let Ok(sysroot) = env::var("SYSROOT") { 18 | bindings = bindings 19 | .clang_arg("--sysroot") 20 | .clang_arg(sysroot); 21 | } 22 | 23 | let bindings = bindings 24 | // The input header we would like to generate 25 | // bindings for. 26 | .header("wrapper.h") 27 | // Finish the builder and generate the bindings. 28 | .generate() 29 | // Unwrap the Result and panic on failure. 30 | .expect("Unable to generate bindings"); 31 | 32 | // Write the bindings to the $OUT_DIR/bindings.rs file. 33 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 34 | bindings 35 | .write_to_file(out_path.join("bindings.rs")) 36 | .expect("Couldn't write bindings!"); 37 | } 38 | -------------------------------------------------------------------------------- /aio-bindings/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 6 | 7 | /* 8 | * extracted from https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/fs.h#L372 9 | * Flags for preadv2/pwritev2: 10 | */ 11 | 12 | /* per-IO O_DSYNC */ 13 | //#define RWF_DSYNC ((__force __kernel_rwf_t)0x00000002) 14 | pub const RWF_DSYNC: u32 = 2; 15 | 16 | /* per-IO O_SYNC */ 17 | //#define RWF_SYNC ((__force __kernel_rwf_t)0x00000004) 18 | pub const RWF_SYNC: u32 = 4; 19 | 20 | /* per-IO, return -EAGAIN if operation would block */ 21 | //#define RWF_NOWAIT ((__force __kernel_rwf_t)0x00000008) 22 | pub const RWF_NOWAIT: u32 = 8; 23 | -------------------------------------------------------------------------------- /aio-bindings/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | -------------------------------------------------------------------------------- /src/aio.rs: -------------------------------------------------------------------------------- 1 | // =============================================================================================== 2 | // Copyright (c) 2018 Hans-Martin Will 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // =============================================================================================== 22 | 23 | pub use libc::c_long; 24 | 25 | // Relevant symbols from the native bindings exposed via aio-bindings 26 | pub use aio_bindings::{aio_context_t, io_event, iocb, syscall, timespec, 27 | __NR_io_destroy, __NR_io_getevents, __NR_io_setup, __NR_io_submit, 28 | IOCB_CMD_PREAD, IOCB_CMD_PWRITE, IOCB_CMD_FSYNC, IOCB_CMD_FDSYNC, IOCB_FLAG_RESFD, 29 | RWF_DSYNC, RWF_SYNC}; 30 | 31 | // ----------------------------------------------------------------------------------------------- 32 | // Inline functions that wrap the kernel calls for the entry points corresponding to Linux 33 | // AIO functions 34 | // ----------------------------------------------------------------------------------------------- 35 | 36 | // Initialize an AIO context for a given submission queue size within the kernel. 37 | // 38 | // See [io_setup(7)](http://man7.org/linux/man-pages/man2/io_setup.2.html) for details. 39 | #[inline(always)] 40 | pub unsafe fn io_setup(nr: c_long, ctxp: *mut aio_context_t) -> c_long { 41 | syscall(__NR_io_setup as c_long, nr, ctxp) 42 | } 43 | 44 | // Destroy an AIO context. 45 | // 46 | // See [io_destroy(7)](http://man7.org/linux/man-pages/man2/io_destroy.2.html) for details. 47 | #[inline(always)] 48 | pub unsafe fn io_destroy(ctx: aio_context_t) -> c_long { 49 | syscall(__NR_io_destroy as c_long, ctx) 50 | } 51 | 52 | // Submit a batch of IO operations. 53 | // 54 | // See [io_sumit(7)](http://man7.org/linux/man-pages/man2/io_submit.2.html) for details. 55 | #[inline(always)] 56 | pub unsafe fn io_submit(ctx: aio_context_t, nr: c_long, iocbpp: *mut *mut iocb) -> c_long { 57 | syscall(__NR_io_submit as c_long, ctx, nr, iocbpp) 58 | } 59 | 60 | // Retrieve completion events for previously submitted IO requests. 61 | // 62 | // See [io_getevents(7)](http://man7.org/linux/man-pages/man2/io_getevents.2.html) for details. 63 | #[inline(always)] 64 | pub unsafe fn io_getevents( 65 | ctx: aio_context_t, 66 | min_nr: c_long, 67 | max_nr: c_long, 68 | events: *mut io_event, 69 | timeout: *mut timespec, 70 | ) -> c_long { 71 | syscall( 72 | __NR_io_getevents as c_long, 73 | ctx, 74 | min_nr, 75 | max_nr, 76 | events, 77 | timeout, 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /src/eventfd.rs: -------------------------------------------------------------------------------- 1 | // =============================================================================================== 2 | // Copyright (c) 2018 Hans-Martin Will 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // =============================================================================================== 22 | 23 | use std::io; 24 | use std::mem; 25 | 26 | use std::os::unix::io::RawFd; 27 | 28 | use libc::{c_uint, close, eventfd, read, write, EAGAIN, O_CLOEXEC}; 29 | 30 | use futures; 31 | use futures::Future; 32 | 33 | use mio; 34 | 35 | use tokio::reactor; 36 | 37 | use aio_bindings::{EFD_NONBLOCK, EFD_SEMAPHORE}; 38 | 39 | // ----------------------------------------------------------------------------------------------- 40 | // EventFd Implementation 41 | // ----------------------------------------------------------------------------------------------- 42 | 43 | pub struct EventFdInner { 44 | pub fd: RawFd, 45 | } 46 | 47 | impl Drop for EventFdInner { 48 | fn drop(&mut self) { 49 | if self.fd >= 0 { 50 | unsafe { close(self.fd) }; 51 | } 52 | } 53 | } 54 | 55 | impl mio::Evented for EventFdInner { 56 | fn register( 57 | &self, 58 | poll: &mio::Poll, 59 | token: mio::Token, 60 | interest: mio::Ready, 61 | opts: mio::PollOpt, 62 | ) -> io::Result<()> { 63 | mio::unix::EventedFd(&self.fd).register(poll, token, interest, opts) 64 | } 65 | 66 | fn reregister( 67 | &self, 68 | poll: &mio::Poll, 69 | token: mio::Token, 70 | interest: mio::Ready, 71 | opts: mio::PollOpt, 72 | ) -> io::Result<()> { 73 | mio::unix::EventedFd(&self.fd).reregister(poll, token, interest, opts) 74 | } 75 | 76 | fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { 77 | mio::unix::EventedFd(&self.fd).deregister(poll) 78 | } 79 | } 80 | 81 | pub struct EventFd { 82 | pub evented: reactor::PollEvented, 83 | } 84 | 85 | impl EventFd { 86 | pub fn create(init: usize, semaphore: bool) -> Result { 87 | let flags = if semaphore { 88 | O_CLOEXEC | EFD_NONBLOCK as i32 | EFD_SEMAPHORE as i32 89 | } else { 90 | O_CLOEXEC | EFD_NONBLOCK as i32 91 | }; 92 | 93 | let fd = unsafe { eventfd(init as c_uint, flags) }; 94 | 95 | if fd < 0 { 96 | Err(io::Error::last_os_error()) 97 | } else { 98 | reactor::PollEvented::new(EventFdInner { fd }, &reactor::Handle::default()) 99 | .map(|evented| EventFd { evented }) 100 | } 101 | } 102 | 103 | pub fn read(&mut self) -> Result, io::Error> { 104 | match self.evented.poll_read() { 105 | futures::Async::NotReady => return Ok(futures::Async::NotReady), 106 | _ => (), 107 | }; 108 | 109 | let fd = self.evented.get_ref().fd; 110 | let mut result: u64 = 0; 111 | 112 | let rc = unsafe { read(fd, mem::transmute(&mut result), mem::size_of::()) }; 113 | 114 | if rc < 0 { 115 | let error = io::Error::last_os_error(); 116 | 117 | if error.raw_os_error().unwrap() != EAGAIN { 118 | // this is a regular error 119 | return Err(io::Error::last_os_error()); 120 | } else { 121 | if let Err(err) = self.evented.need_read() { 122 | return Err(err); 123 | } else { 124 | return Ok(futures::Async::NotReady); 125 | } 126 | } 127 | } else { 128 | if rc as usize != mem::size_of::() { 129 | panic!( 130 | "Reading from an eventfd should transfer exactly {} bytes", 131 | mem::size_of::() 132 | ) 133 | } 134 | 135 | // eventfd should never return 0 value; it either blocks or fails with EAGAIN 136 | assert!(result != 0); 137 | Ok(futures::Async::Ready(result as u64)) 138 | } 139 | } 140 | 141 | pub fn add(&mut self, increment: u64) -> Result<(), io::Error> { 142 | let fd = { self.evented.get_ref().fd }; 143 | 144 | let result = unsafe { write(fd, mem::transmute(&increment), mem::size_of::()) }; 145 | 146 | if result == -1 { 147 | Err(io::Error::last_os_error()) 148 | } else { 149 | if result as usize != mem::size_of_val(&increment) { 150 | panic!( 151 | "Writing to an eventfd should consume exactly {} bytes", 152 | mem::size_of::() 153 | ) 154 | } 155 | 156 | Ok(()) 157 | } 158 | } 159 | } 160 | 161 | impl futures::Future for EventFd { 162 | type Item = u64; 163 | type Error = io::Error; 164 | 165 | fn poll(&mut self) -> Result, Self::Error> { 166 | self.read() 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use tokio::executor::current_thread; 173 | use futures::future::lazy; 174 | use super::*; 175 | 176 | #[test] 177 | fn read_eventfd_standard() { 178 | let efd = EventFd::create(2, false).unwrap(); 179 | let result = efd.wait(); 180 | 181 | assert!(result.is_ok()); 182 | assert!(result.unwrap() == 2); 183 | } 184 | 185 | #[test] 186 | fn read_eventfd_semaphore() { 187 | let efd = EventFd::create(2, true).unwrap(); 188 | let result = efd.wait(); 189 | 190 | assert!(result.is_ok()); 191 | assert!(result.unwrap() == 1); 192 | } 193 | 194 | #[test] 195 | fn read_add_eventfd() { 196 | current_thread::run(|_| { 197 | let efd = EventFd::create(0, false).unwrap(); 198 | let fd = efd.evented.get_ref().fd; 199 | 200 | // The execution context is setup, futures may be executed. 201 | current_thread::spawn(efd.map(|res| { 202 | assert!(res == 1); 203 | }).map_err(|_| { 204 | panic!("Error!!!"); 205 | })); 206 | 207 | current_thread::spawn(lazy(move || { 208 | let increment: u64 = 1; 209 | 210 | let result = 211 | unsafe { write(fd, mem::transmute(&increment), mem::size_of::()) }; 212 | assert!(result as usize == mem::size_of::()); 213 | Ok(()) 214 | })); 215 | }); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // =============================================================================================== 2 | // Copyright (c) 2018 Hans-Martin Will 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // =============================================================================================== 22 | 23 | //! Tokio Bindings for Linux Kernel AIO 24 | //! 25 | //! This package provides an integration of Linux kernel-level asynchronous I/O to the 26 | //! [Tokio platform](https://tokio.rs/). 27 | //! 28 | //! Linux kernel-level asynchronous I/O is different from the [Posix AIO library](http://man7.org/linux/man-pages/man7/aio.7.html). 29 | //! Posix AIO is implemented using a pool of userland threads, which invoke regular, blocking system 30 | //! calls to perform file I/O. [Linux kernel-level AIO](http://lse.sourceforge.net/io/aio.html), on the 31 | //! other hand, provides kernel-level asynchronous scheduling of I/O operations to the underlying block device. 32 | //! 33 | //! The core abstraction exposed by this library is the `AioContext`, which essentially wraps 34 | //! a kernel-level I/O submission queue with limited capacity. The capacity of the underlying queue 35 | //! is a constructor argument when creating an instance of `AioContext`. Once created, the context 36 | //! can be used to issue read and write requests. Each such invocations will create a suitable instance 37 | //! of `futures::Future`, which can be executed within the context of Tokio. 38 | //! 39 | //! There's a few gotchas to be aware of when using this library: 40 | //! 41 | //! 1. Linux AIO requires the underlying file to be opened in direct mode (`O_DIRECT`), bypassing 42 | //! any other buffering at the OS level. If you attempt to use this library on files opened regularly, 43 | //! likely it won't work. 44 | //! 45 | //! 2. Because Linux AIO operates on files in direct mode, by corrollary the memory buffers associated 46 | //! with read/write requests need to be suitable for direct DMA transfers. This means that those buffers 47 | //! should be aligned to hardware page boundaries, and the memory needs to be mapped to pysical RAM. 48 | //! The best way to accomplish this is to have a mmapped region that is locked in physical memory. 49 | //! 50 | //! 3. Due to the asynchronous nature of this library, memory buffers are represented using generic 51 | //! handle types. For the purpose of the inner workings of this library, the important aspect is that 52 | //! those handle types can be dereferenced into a `&[u8]` or, respectively, a `&mut [u8]` type. Because 53 | //! we hand off those buffers to the kernel (and ultimately hardware DMA) it is mandatory that those 54 | //! bytes slices have a fixed address in main memory during I/O processing. 55 | //! 56 | //! 4. The general idea is that those generic handle types for memory access can implement smart 57 | //! pointer semantics. For example, a conceivable implementation of a memory handle type is a smart 58 | //! pointer that acquires a write-lock on a page while a data transfer is in progress, and releases 59 | //! such a lock once the operation has completed. 60 | 61 | extern crate aio_bindings; 62 | extern crate futures; 63 | extern crate futures_cpupool; 64 | extern crate libc; 65 | extern crate memmap; 66 | extern crate mio; 67 | extern crate rand; 68 | extern crate tokio; 69 | 70 | use std::convert; 71 | use std::error; 72 | use std::fmt; 73 | use std::io; 74 | use std::mem; 75 | use std::ops; 76 | use std::ptr; 77 | 78 | use std::os::unix::io::RawFd; 79 | 80 | use libc::{c_long, c_void, mlock}; 81 | 82 | use futures::Future; 83 | use ops::Deref; 84 | 85 | // local modules 86 | mod aio; 87 | mod eventfd; 88 | mod sync; 89 | 90 | // ----------------------------------------------------------------------------------------------- 91 | // Bindings for Linux AIO start here 92 | // ----------------------------------------------------------------------------------------------- 93 | 94 | // field values that we need to transfer into a kernel IOCB 95 | struct IocbInfo { 96 | // the I/O opcode 97 | opcode: u32, 98 | 99 | // file fd identifying the file to operate on 100 | fd: RawFd, 101 | 102 | // an absolute file offset, if applicable for the command 103 | offset: u64, 104 | 105 | // the base address of the transfer buffer, if applicable 106 | buf: u64, 107 | 108 | // the number of bytes to be transferred, if applicable 109 | len: u64, 110 | 111 | // flags to provide additional parameters 112 | flags: u32, 113 | } 114 | 115 | // State information that is associated with an I/O request that is currently in flight. 116 | #[derive(Debug)] 117 | struct RequestState { 118 | // Linux kernal I/O control block which can be submitted to io_submit 119 | request: aio::iocb, 120 | 121 | // Concurrency primitive to notify completion to the associated future 122 | completed_receiver: futures::sync::oneshot::Receiver, 123 | 124 | // We have both sides of a oneshot channel here 125 | completed_sender: Option>, 126 | } 127 | 128 | // Common data structures for futures returned by `AioContext`. 129 | struct AioBaseFuture { 130 | // reference to the `AioContext` that controls the submission queue for asynchronous I/O 131 | context: std::sync::Arc, 132 | 133 | // request information captured for the kernel request 134 | iocb_info: IocbInfo, 135 | 136 | // the associated request state 137 | state: Option>, 138 | 139 | // acquire future 140 | acquire_state: Option, 141 | } 142 | 143 | impl AioBaseFuture { 144 | // Attempt to submit the I/O request; this may need to wait until a submission slot is 145 | // available. 146 | fn submit_request(&mut self) -> Result, io::Error> { 147 | if self.state.is_none() { 148 | // See if we can secure a submission slot 149 | if self.acquire_state.is_none() { 150 | self.acquire_state = Some(self.context.have_capacity.acquire()); 151 | } 152 | 153 | match self.acquire_state.as_mut().unwrap().poll() { 154 | Err(err) => return Err(err), 155 | Ok(futures::Async::NotReady) => return Ok(futures::Async::NotReady), 156 | Ok(futures::Async::Ready(_)) => { 157 | // retrieve a state container from the set of available ones and move it into the future 158 | let mut guard = self.context.capacity.write(); 159 | match guard { 160 | Ok(ref mut guard) => { 161 | self.state = guard.state.pop(); 162 | } 163 | Err(_) => panic!("TODO: Figure out how to handle this kind of error"), 164 | } 165 | } 166 | } 167 | 168 | assert!(self.state.is_some()); 169 | let state = self.state.as_mut().unwrap(); 170 | let state_addr = state.deref().deref() as *const RequestState; 171 | 172 | // Fill in the iocb data structure to be submitted to the kernel 173 | state.request.aio_data = unsafe { mem::transmute::<_, usize>(state_addr) } as u64; 174 | state.request.aio_resfd = self.context.completed_fd as u32; 175 | state.request.aio_flags = aio::IOCB_FLAG_RESFD | self.iocb_info.flags; 176 | state.request.aio_fildes = self.iocb_info.fd as u32; 177 | state.request.aio_offset = self.iocb_info.offset as i64; 178 | state.request.aio_buf = self.iocb_info.buf; 179 | state.request.aio_nbytes = self.iocb_info.len; 180 | state.request.aio_lio_opcode = self.iocb_info.opcode as u16; 181 | 182 | // attach synchronization primitives that are used to indicate completion of this request 183 | let (sender, receiver) = futures::sync::oneshot::channel(); 184 | state.completed_receiver = receiver; 185 | state.completed_sender = Some(sender); 186 | 187 | // submit the request 188 | let mut request_ptr_array: [*mut aio::iocb; 1] = 189 | [&mut state.request as *mut aio::iocb; 1]; 190 | 191 | let result = unsafe { 192 | aio::io_submit( 193 | self.context.context, 194 | 1, 195 | &mut request_ptr_array[0] as *mut *mut aio::iocb, 196 | ) 197 | }; 198 | 199 | // if we have submission error, capture it as future result 200 | if result != 1 { 201 | return Err(io::Error::last_os_error()); 202 | } 203 | } 204 | 205 | Ok(futures::Async::Ready(())) 206 | } 207 | 208 | // Attempt to retrieve the result of a previously submitted I/O request; this may need to 209 | // wait until the I/O operation has been completed 210 | fn retrieve_result(&mut self) -> Result, io::Error> { 211 | // Check if we have received a notification indicating completion of the I/O request 212 | let result_code = match self.state.as_mut().unwrap().completed_receiver.poll() { 213 | Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)), 214 | Ok(futures::Async::NotReady) => return Ok(futures::Async::NotReady), 215 | Ok(futures::Async::Ready(n)) => n, 216 | }; 217 | 218 | // Release the kernel queue slot and the state variable that we just processed 219 | match self.context.capacity.write() { 220 | Ok(ref mut guard) => { 221 | guard.state.push(self.state.take().unwrap()); 222 | } 223 | Err(_) => panic!("TODO: Figure out how to handle this kind of error"), 224 | } 225 | 226 | // notify others that we release a state slot 227 | self.context.have_capacity.release(); 228 | 229 | if result_code < 0 { 230 | Err(io::Error::from_raw_os_error(result_code as i32)) 231 | } else { 232 | Ok(futures::Async::Ready(())) 233 | } 234 | } 235 | } 236 | 237 | // Common future base type for all asynchronous operations supperted by this API 238 | impl futures::Future for AioBaseFuture { 239 | type Item = (); 240 | type Error = io::Error; 241 | 242 | fn poll(&mut self) -> Result, io::Error> { 243 | let result = self.submit_request(); 244 | 245 | match result { 246 | Ok(futures::Async::Ready(())) => self.retrieve_result(), 247 | Ok(futures::Async::NotReady) => Ok(futures::Async::NotReady), 248 | Err(err) => Err(err), 249 | } 250 | } 251 | } 252 | 253 | /// An error type for I/O operations that allows us to return the memory handle in failure cases. 254 | pub struct AioError { 255 | // The buffer handle that we want to return to the caller 256 | pub buffer: Handle, 257 | 258 | // The error value 259 | pub error: io::Error, 260 | } 261 | 262 | impl fmt::Debug for AioError { 263 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 264 | self.error.fmt(f) 265 | } 266 | } 267 | 268 | impl fmt::Display for AioError { 269 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 270 | self.error.fmt(f) 271 | } 272 | } 273 | 274 | impl error::Error for AioError { 275 | fn description(&self) -> &str { 276 | self.error.description() 277 | } 278 | 279 | fn cause(&self) -> Option<&error::Error> { 280 | self.error.cause() 281 | } 282 | } 283 | 284 | /// Future returned as result of submitting a read request via `AioContext::read`. 285 | pub struct AioReadResultFuture 286 | where 287 | ReadWriteHandle: convert::AsMut<[u8]>, 288 | { 289 | // common AIO future state 290 | base: AioBaseFuture, 291 | 292 | // memory handle where data read from the underlying block device is being written to. 293 | // Holding on to this value is important in the case where it implements Drop. 294 | buffer: Option, 295 | } 296 | 297 | impl futures::Future for AioReadResultFuture 298 | where 299 | ReadWriteHandle: convert::AsMut<[u8]>, 300 | { 301 | type Item = ReadWriteHandle; 302 | type Error = AioError; 303 | 304 | fn poll(&mut self) -> Result, Self::Error> { 305 | self.base 306 | .poll() 307 | .map(|val| val.map(|_| self.buffer.take().unwrap())) 308 | .map_err(|err| AioError { 309 | buffer: self.buffer.take().unwrap(), 310 | error: err, 311 | }) 312 | } 313 | } 314 | 315 | /// Future returned as result of submitting a write request via `AioContext::write`. 316 | pub struct AioWriteResultFuture 317 | where 318 | ReadOnlyHandle: convert::AsRef<[u8]>, 319 | { 320 | // common AIO future state 321 | base: AioBaseFuture, 322 | 323 | // memory handle where data written to the underlying block device is being read from. 324 | // Holding on to this value is important in the case where it implements Drop. 325 | buffer: Option, 326 | } 327 | 328 | impl futures::Future for AioWriteResultFuture 329 | where 330 | ReadOnlyHandle: convert::AsRef<[u8]>, 331 | { 332 | type Item = ReadOnlyHandle; 333 | type Error = AioError; 334 | 335 | fn poll(&mut self) -> Result, Self::Error> { 336 | self.base 337 | .poll() 338 | .map(|val| val.map(|_| self.buffer.take().unwrap())) 339 | .map_err(|err| AioError { 340 | buffer: self.buffer.take().unwrap(), 341 | error: err, 342 | }) 343 | } 344 | } 345 | 346 | /// Future returned as result of submitting a write request via `AioContext::sync` or 347 | /// `AioContext::data_sync`. 348 | pub struct AioSyncResultFuture 349 | { 350 | // common AIO future state 351 | base: AioBaseFuture, 352 | } 353 | 354 | impl futures::Future for AioSyncResultFuture 355 | { 356 | type Item = (); 357 | type Error = io::Error; 358 | 359 | fn poll(&mut self) -> Result, Self::Error> { 360 | self.base.poll() 361 | } 362 | } 363 | 364 | // A future spawned as background task to retrieve I/O completion events from the kernel 365 | // and distributing the results to the current futures in flight. 366 | pub struct AioPollFuture { 367 | // the context handle for retrieving AIO completions from the kernel 368 | context: aio::aio_context_t, 369 | 370 | // the eventfd on which the kernel will notify I/O completions 371 | eventfd: eventfd::EventFd, 372 | 373 | // a buffer to retrieve completion status from the kernel 374 | events: Vec, 375 | } 376 | 377 | impl futures::Future for AioPollFuture { 378 | type Item = (); 379 | type Error = io::Error; 380 | 381 | // This poll function will never return completion 382 | fn poll(&mut self) -> Result, Self::Error> { 383 | loop { 384 | // check the eventfd for completed I/O operations 385 | let available = match self.eventfd.read() { 386 | Err(err) => return Err(err), 387 | Ok(futures::Async::NotReady) => return Ok(futures::Async::NotReady), 388 | Ok(futures::Async::Ready(value)) => value as usize, 389 | }; 390 | 391 | assert!(available > 0); 392 | self.events.clear(); 393 | 394 | unsafe { 395 | let result = aio::io_getevents( 396 | self.context, 397 | available as c_long, 398 | available as c_long, 399 | self.events.as_mut_ptr(), 400 | ptr::null_mut::(), 401 | ); 402 | 403 | // adjust the vector size to the actual number of items returned 404 | if result < 0 { 405 | return Err(io::Error::last_os_error()); 406 | } 407 | 408 | assert!(result as usize == available); 409 | self.events.set_len(available); 410 | }; 411 | 412 | // dispatch the retrieved events to the associated futures 413 | for ref event in &self.events { 414 | let request_state: &mut RequestState = unsafe { mem::transmute(event.data as usize) } ; 415 | request_state 416 | .completed_sender 417 | .take() 418 | .unwrap() 419 | .send(event.res) 420 | .unwrap(); 421 | } 422 | } 423 | } 424 | } 425 | 426 | // Shared state within AioContext that is backing I/O requests as represented by the individual futures. 427 | #[derive(Debug)] 428 | struct Capacity { 429 | // pre-allocated eventfds and iocbs that are associated with scheduled I/O requests 430 | state: Vec>, 431 | } 432 | 433 | impl Capacity { 434 | fn new(nr: usize) -> Result { 435 | let mut state = Vec::with_capacity(nr); 436 | 437 | // using a for loop to properly handle the error case 438 | // range map collect would only allow for using unwrap(), thereby turning an error into a panic 439 | for _ in 0..nr { 440 | let (_, receiver) = futures::sync::oneshot::channel(); 441 | 442 | state.push(Box::new(RequestState { 443 | request: unsafe { mem::zeroed() }, 444 | completed_receiver: receiver, 445 | completed_sender: None, 446 | })); 447 | } 448 | 449 | Ok(Capacity { state }) 450 | } 451 | } 452 | 453 | // The inner state, which is shared between the AioContext object returned to clients and 454 | // used internally by futures in flight. 455 | #[derive(Debug)] 456 | struct AioContextInner { 457 | // the context handle for submitting AIO requests to the kernel 458 | context: aio::aio_context_t, 459 | 460 | // the fd embedded in the completed eventfd, which can be passed to kernel functions; 461 | // the handle is managed by the Eventfd object that is owned by the AioPollFuture 462 | // that we spawn when creating an AioContext. 463 | completed_fd: RawFd, 464 | 465 | // do we have capacity? 466 | have_capacity: sync::Semaphore, 467 | 468 | // pre-allocated eventfds and a capacity semaphore 469 | capacity: std::sync::RwLock, 470 | 471 | // handle for the spawned background task; dropping it will cancel the task 472 | // we are using an Option value with delayed initialization to keep the generic 473 | // executor type parameter out of AioContextInner 474 | poll_task_handle: Option>, 475 | } 476 | 477 | impl AioContextInner { 478 | fn new(fd: RawFd, nr: usize) -> Result { 479 | let mut context: aio::aio_context_t = 0; 480 | 481 | unsafe { 482 | if aio::io_setup(nr as c_long, &mut context) != 0 { 483 | return Err(io::Error::last_os_error()); 484 | } 485 | }; 486 | 487 | Ok(AioContextInner { 488 | context, 489 | capacity: std::sync::RwLock::new(Capacity::new(nr)?), 490 | have_capacity: sync::Semaphore::new(nr), 491 | completed_fd: fd, 492 | poll_task_handle: None, 493 | }) 494 | } 495 | } 496 | 497 | impl Drop for AioContextInner { 498 | fn drop(&mut self) { 499 | let result = unsafe { aio::io_destroy(self.context) }; 500 | assert!(result == 0); 501 | } 502 | } 503 | 504 | /// AioContext provides a submission queue for asycnronous I/O operations to 505 | /// block devices within the Linux kernel. 506 | #[derive(Clone, Debug)] 507 | pub struct AioContext { 508 | inner: std::sync::Arc, 509 | } 510 | 511 | /// Synchronization levels associated with I/O operations 512 | #[derive(Copy, Clone, Debug)] 513 | pub enum SyncLevel { 514 | /// No synchronization requirement 515 | None = 0, 516 | 517 | /// Data is written to device, but not necessarily meta data 518 | Data = aio::RWF_DSYNC as isize, 519 | 520 | /// Data and associated meta data is written to device 521 | Full = aio::RWF_SYNC as isize, 522 | } 523 | 524 | impl AioContext { 525 | /// Create a new AioContext that is driven by the provided event loop. 526 | /// 527 | /// # Params 528 | /// - executor: The executor used to spawn the background polling task 529 | /// - nr: Number of submission slots for IO requests 530 | pub fn new(executor: &E, nr: usize) -> Result 531 | where 532 | E: futures::future::Executor>, 533 | { 534 | // An eventfd that we use for I/O completion notifications from the kernel 535 | let eventfd = eventfd::EventFd::create(0, false)?; 536 | let fd = eventfd.evented.get_ref().fd; 537 | 538 | let mut inner = AioContextInner::new(fd, nr)?; 539 | let context = inner.context; 540 | 541 | let poll_future = AioPollFuture { 542 | context, 543 | eventfd, 544 | events: Vec::with_capacity(nr), 545 | }; 546 | 547 | inner.poll_task_handle = Some(futures::sync::oneshot::spawn(poll_future, executor)); 548 | 549 | Ok(AioContext { 550 | inner: std::sync::Arc::new(inner), 551 | }) 552 | } 553 | 554 | /// Initiate an asynchronous read operation on the given file descriptor for reading 555 | /// data from the provided absolute file offset into the buffer. The buffer also determines 556 | /// the number of bytes to be read, which should be a multiple of the underlying device block 557 | /// size. 558 | /// 559 | /// # Params: 560 | /// - fd: The file descriptor of the file from which to read 561 | /// - offset: The file offset where we want to read from 562 | /// - buffer: A buffer to receive the read results 563 | pub fn read( 564 | &self, 565 | fd: RawFd, 566 | offset: u64, 567 | mut buffer_obj: ReadWriteHandle, 568 | ) -> AioReadResultFuture 569 | where 570 | ReadWriteHandle: convert::AsMut<[u8]>, 571 | { 572 | let (ptr, len) = { 573 | let buffer = buffer_obj.as_mut(); 574 | let len = buffer.len() as u64; 575 | let ptr = unsafe { mem::transmute::<_, usize>(buffer.as_ptr()) } as u64; 576 | (ptr, len) 577 | }; 578 | 579 | // nothing really happens here until someone calls poll 580 | AioReadResultFuture { 581 | base: AioBaseFuture { 582 | context: self.inner.clone(), 583 | iocb_info: IocbInfo { 584 | opcode: aio::IOCB_CMD_PREAD, 585 | fd, 586 | offset, 587 | len, 588 | buf: ptr, 589 | flags: 0, 590 | }, 591 | state: None, 592 | acquire_state: None, 593 | }, 594 | buffer: Some(buffer_obj), 595 | } 596 | } 597 | 598 | /// Initiate an asynchronous write operation on the given file descriptor for writing 599 | /// data to the provided absolute file offset from the buffer. The buffer also determines 600 | /// the number of bytes to be written, which should be a multiple of the underlying device block 601 | /// size. 602 | /// 603 | /// # Params: 604 | /// - fd: The file descriptor of the file to which to write 605 | /// - offset: The file offset where we want to write to 606 | /// - buffer: A buffer holding the data to be written 607 | pub fn write( 608 | &self, 609 | fd: RawFd, 610 | offset: u64, 611 | buffer: ReadOnlyHandle, 612 | ) -> AioWriteResultFuture 613 | where 614 | ReadOnlyHandle: convert::AsRef<[u8]>, 615 | { 616 | self.write_sync(fd, offset, buffer, SyncLevel::None) 617 | } 618 | 619 | /// Initiate an asynchronous write operation on the given file descriptor for writing 620 | /// data to the provided absolute file offset from the buffer. The buffer also determines 621 | /// the number of bytes to be written, which should be a multiple of the underlying device block 622 | /// size. 623 | /// 624 | /// # Params: 625 | /// - fd: The file descriptor of the file to which to write 626 | /// - offset: The file offset where we want to write to 627 | /// - buffer: A buffer holding the data to be written 628 | /// - sync_level: A synchronization level to apply for this write operation 629 | pub fn write_sync( 630 | &self, 631 | fd: RawFd, 632 | offset: u64, 633 | buffer_obj: ReadOnlyHandle, 634 | sync_level: SyncLevel 635 | ) -> AioWriteResultFuture 636 | where 637 | ReadOnlyHandle: convert::AsRef<[u8]>, 638 | { 639 | let (ptr, len) = { 640 | let buffer = buffer_obj.as_ref(); 641 | let len = buffer.len() as u64; 642 | let ptr = unsafe { mem::transmute::<_, usize>(buffer.as_ptr()) } as c_long; 643 | (ptr, len) 644 | }; 645 | 646 | // nothing really happens here until someone calls poll 647 | AioWriteResultFuture { 648 | base: AioBaseFuture { 649 | context: self.inner.clone(), 650 | iocb_info: IocbInfo { 651 | opcode: aio::IOCB_CMD_PWRITE, 652 | fd, 653 | offset, 654 | len, 655 | buf: ptr as u64, 656 | flags: sync_level as u32, 657 | }, 658 | state: None, 659 | acquire_state: None, 660 | }, 661 | buffer: Some(buffer_obj), 662 | } 663 | } 664 | 665 | /// Initiate an asynchronous sync operation on the given file descriptor. 666 | /// 667 | /// __Caveat:__ While this operation is defined in the ABI, this command is known to 668 | /// fail with an invalid argument error (`EINVAL`) in many, if not all, cases. You are kind of 669 | /// on your own. 670 | /// 671 | /// # Params: 672 | /// - fd: The file descriptor of the file to which to write 673 | pub fn sync( 674 | &self, 675 | fd: RawFd, 676 | ) -> AioSyncResultFuture 677 | { 678 | // nothing really happens here until someone calls poll 679 | AioSyncResultFuture { 680 | base: AioBaseFuture { 681 | context: self.inner.clone(), 682 | iocb_info: IocbInfo { 683 | opcode: aio::IOCB_CMD_FSYNC, 684 | fd, 685 | buf: 0, 686 | len: 0, 687 | offset: 0, 688 | flags: 0, 689 | }, 690 | state: None, 691 | acquire_state: None, 692 | }, 693 | } 694 | } 695 | 696 | 697 | /// Initiate an asynchronous data sync operation on the given file descriptor. 698 | /// 699 | /// __Caveat:__ While this operation is defined in the ABI, this command is known to 700 | /// fail with an invalid argument error (`EINVAL`) in many, if not all, cases. You are kind of 701 | /// on your own. 702 | /// 703 | /// # Params: 704 | /// - fd: The file descriptor of the file to which to write 705 | pub fn data_sync( 706 | &self, 707 | fd: RawFd, 708 | ) -> AioSyncResultFuture 709 | { 710 | // nothing really happens here until someone calls poll 711 | AioSyncResultFuture { 712 | base: AioBaseFuture { 713 | context: self.inner.clone(), 714 | iocb_info: IocbInfo { 715 | opcode: aio::IOCB_CMD_FDSYNC, 716 | fd, 717 | buf: 0, 718 | len: 0, 719 | offset: 0, 720 | flags: 0, 721 | }, 722 | state: None, 723 | acquire_state: None, 724 | }, 725 | } 726 | } 727 | } 728 | 729 | // --------------------------------------------------------------------------- 730 | // Test code starts here 731 | // --------------------------------------------------------------------------- 732 | 733 | #[cfg(test)] 734 | mod tests { 735 | use super::*; 736 | 737 | use std::borrow::{Borrow, BorrowMut}; 738 | use std::env; 739 | use std::fs; 740 | use std::io::Write; 741 | use std::os::unix::ffi::OsStrExt; 742 | use std::path; 743 | use std::sync; 744 | 745 | use rand::Rng; 746 | 747 | use tokio::executor::current_thread; 748 | 749 | use memmap; 750 | use futures_cpupool; 751 | 752 | use libc::{close, open, O_DIRECT, O_RDWR}; 753 | 754 | const FILE_SIZE: u64 = 1024 * 512; 755 | 756 | // Create a temporary file name within the temporary directory configured in the environment. 757 | fn temp_file_name() -> path::PathBuf { 758 | let mut rng = rand::thread_rng(); 759 | let mut result = env::temp_dir(); 760 | let filename = format!("test-aio-{}.dat", rng.gen::()); 761 | result.push(filename); 762 | result 763 | } 764 | 765 | // Create a temporary file with some content 766 | fn create_temp_file(path: &path::Path) { 767 | let mut file = fs::File::create(path).unwrap(); 768 | let mut data: [u8; FILE_SIZE as usize] = [0; FILE_SIZE as usize]; 769 | 770 | for index in 0..data.len() { 771 | data[index] = index as u8; 772 | } 773 | 774 | let result = file.write(&data).and_then(|_| file.sync_all()); 775 | assert!(result.is_ok()); 776 | } 777 | 778 | // Delete the temporary file 779 | fn remove_file(path: &path::Path) { 780 | let _ = fs::remove_file(path); 781 | } 782 | 783 | #[test] 784 | fn create_and_drop() { 785 | let pool = futures_cpupool::CpuPool::new(3); 786 | let _context = AioContext::new(&pool, 10).unwrap(); 787 | } 788 | 789 | struct MemoryBlock { 790 | bytes: sync::RwLock, 791 | } 792 | 793 | impl MemoryBlock { 794 | fn new() -> MemoryBlock { 795 | let map = memmap::MmapMut::map_anon(8192).unwrap(); 796 | unsafe { mlock(map.as_ref().as_ptr() as *const c_void, map.len()) }; 797 | 798 | MemoryBlock { 799 | // for real uses, we'll have a buffer pool with locks associated with individual pages 800 | // simplifying the logic here for test case development 801 | bytes: sync::RwLock::new(map), 802 | } 803 | } 804 | } 805 | 806 | struct MemoryHandle { 807 | block: sync::Arc, 808 | } 809 | 810 | impl MemoryHandle { 811 | fn new() -> MemoryHandle { 812 | MemoryHandle { 813 | block: sync::Arc::new(MemoryBlock::new()), 814 | } 815 | } 816 | } 817 | 818 | impl Clone for MemoryHandle { 819 | fn clone(&self) -> MemoryHandle { 820 | MemoryHandle { 821 | block: self.block.clone(), 822 | } 823 | } 824 | } 825 | 826 | impl convert::AsRef<[u8]> for MemoryHandle { 827 | fn as_ref(&self) -> &[u8] { 828 | unsafe { mem::transmute(&(*self.block.bytes.read().unwrap())[..]) } 829 | } 830 | } 831 | 832 | impl convert::AsMut<[u8]> for MemoryHandle { 833 | fn as_mut(&mut self) -> &mut [u8] { 834 | unsafe { mem::transmute(&mut (*self.block.bytes.write().unwrap())[..]) } 835 | } 836 | } 837 | 838 | #[test] 839 | fn read_block_mt() { 840 | let file_name = temp_file_name(); 841 | create_temp_file(&file_name); 842 | 843 | { 844 | let owned_fd = OwnedFd::new_from_raw_fd(unsafe { 845 | open( 846 | mem::transmute(file_name.as_os_str().as_bytes().as_ptr()), 847 | O_DIRECT | O_RDWR, 848 | ) 849 | }); 850 | let fd = owned_fd.fd; 851 | 852 | let pool = futures_cpupool::CpuPool::new(5); 853 | let buffer = MemoryHandle::new(); 854 | 855 | { 856 | let context = AioContext::new(&pool, 10).unwrap(); 857 | let read_future = context 858 | .read(fd, 0, buffer) 859 | .map(move |result_buffer| { 860 | assert!(validate_block(result_buffer.as_ref())); 861 | }) 862 | .map_err(|err| { 863 | panic!("{:?}", err); 864 | }); 865 | 866 | let cpu_future = pool.spawn(read_future); 867 | let result = cpu_future.wait(); 868 | 869 | assert!(result.is_ok()); 870 | } 871 | } 872 | 873 | remove_file(&file_name); 874 | } 875 | 876 | #[test] 877 | fn write_block_mt() { 878 | use io::{Read, Seek}; 879 | 880 | let file_name = temp_file_name(); 881 | create_temp_file(&file_name); 882 | 883 | { 884 | let owned_fd = OwnedFd::new_from_raw_fd(unsafe { 885 | open( 886 | mem::transmute(file_name.as_os_str().as_bytes().as_ptr()), 887 | O_DIRECT | O_RDWR, 888 | ) 889 | }); 890 | let fd = owned_fd.fd; 891 | 892 | let pool = futures_cpupool::CpuPool::new(5); 893 | let mut buffer = MemoryHandle::new(); 894 | fill_pattern(65u8, buffer.as_mut()); 895 | 896 | { 897 | let context = AioContext::new(&pool, 2).unwrap(); 898 | let write_future = context.write(fd, 16384, buffer).map_err(|err| { 899 | panic!("{:?}", err); 900 | }); 901 | 902 | let cpu_future = pool.spawn(write_future); 903 | let result = cpu_future.wait(); 904 | 905 | assert!(result.is_ok()); 906 | } 907 | } 908 | 909 | let mut file = fs::File::open(&file_name).unwrap(); 910 | file.seek(io::SeekFrom::Start(16384)).unwrap(); 911 | 912 | let mut read_buffer: [u8; 8192] = [0u8; 8192]; 913 | file.read(&mut read_buffer).unwrap(); 914 | 915 | assert!(validate_pattern(65u8, &read_buffer)); 916 | } 917 | 918 | #[test] 919 | fn write_block_sync_mt() { 920 | // At this point, this test merely verifies that data ends up being written to 921 | // a file in the presence of synchronization flags. What the test does not verify 922 | // as that the specific synchronization guarantees are being fulfilled. 923 | use io::{Read, Seek}; 924 | 925 | let file_name = temp_file_name(); 926 | create_temp_file(&file_name); 927 | 928 | { 929 | let owned_fd = OwnedFd::new_from_raw_fd(unsafe { 930 | open( 931 | mem::transmute(file_name.as_os_str().as_bytes().as_ptr()), 932 | O_DIRECT | O_RDWR, 933 | ) 934 | }); 935 | let fd = owned_fd.fd; 936 | 937 | let pool = futures_cpupool::CpuPool::new(5); 938 | let context = AioContext::new(&pool, 2).unwrap(); 939 | 940 | { 941 | let mut buffer = MemoryHandle::new(); 942 | fill_pattern(65u8, buffer.as_mut()); 943 | let write_future = context.write(fd, 16384, buffer).map_err(|err| { 944 | panic!("{:?}", err); 945 | }); 946 | 947 | let cpu_future = pool.spawn(write_future); 948 | let result = cpu_future.wait(); 949 | 950 | assert!(result.is_ok()); 951 | } 952 | 953 | { 954 | let mut buffer = MemoryHandle::new(); 955 | fill_pattern(66u8, buffer.as_mut()); 956 | let write_future = context.write(fd, 32768, buffer).map_err(|err| { 957 | panic!("{:?}", err); 958 | }); 959 | 960 | let cpu_future = pool.spawn(write_future); 961 | let result = cpu_future.wait(); 962 | 963 | assert!(result.is_ok()); 964 | } 965 | 966 | { 967 | let mut buffer = MemoryHandle::new(); 968 | fill_pattern(67u8, buffer.as_mut()); 969 | let write_future = context.write(fd, 49152, buffer).map_err(|err| { 970 | panic!("{:?}", err); 971 | }); 972 | 973 | let cpu_future = pool.spawn(write_future); 974 | let result = cpu_future.wait(); 975 | 976 | assert!(result.is_ok()); 977 | } 978 | } 979 | 980 | let mut file = fs::File::open(&file_name).unwrap(); 981 | let mut read_buffer: [u8; 8192] = [0u8; 8192]; 982 | 983 | file.seek(io::SeekFrom::Start(16384)).unwrap(); 984 | file.read(&mut read_buffer).unwrap(); 985 | assert!(validate_pattern(65u8, &read_buffer)); 986 | 987 | file.seek(io::SeekFrom::Start(32768)).unwrap(); 988 | file.read(&mut read_buffer).unwrap(); 989 | assert!(validate_pattern(66u8, &read_buffer)); 990 | 991 | file.seek(io::SeekFrom::Start(49152)).unwrap(); 992 | file.read(&mut read_buffer).unwrap(); 993 | assert!(validate_pattern(67u8, &read_buffer)); 994 | } 995 | 996 | #[test] 997 | fn read_invalid_fd() { 998 | let fd = 2431; 999 | 1000 | let pool = futures_cpupool::CpuPool::new(5); 1001 | let buffer = MemoryHandle::new(); 1002 | 1003 | { 1004 | let context = AioContext::new(&pool, 10).unwrap(); 1005 | let read_future = context 1006 | .read(fd, 0, buffer) 1007 | .map(move |_| { 1008 | assert!(false); 1009 | }) 1010 | .map_err(|err| { 1011 | assert!(err.error.kind() == io::ErrorKind::Other); 1012 | err 1013 | }); 1014 | 1015 | let cpu_future = pool.spawn(read_future); 1016 | let result = cpu_future.wait(); 1017 | 1018 | assert!(result.is_err()); 1019 | } 1020 | } 1021 | 1022 | /* 1023 | For some reason, this test does not pass on Travis. Need to research why the out-of-range 1024 | file offset does not trip an invalid argument error. 1025 | 1026 | #[test] 1027 | fn invalid_offset() { 1028 | let file_name = temp_file_name(); 1029 | create_temp_file(&file_name); 1030 | 1031 | { 1032 | let owned_fd = OwnedFd::new_from_raw_fd(unsafe { 1033 | open( 1034 | mem::transmute(file_name.as_os_str().as_bytes().as_ptr()), 1035 | O_DIRECT | O_RDWR, 1036 | ) 1037 | }); 1038 | let fd = owned_fd.fd; 1039 | 1040 | let pool = futures_cpupool::CpuPool::new(5); 1041 | let buffer = MemoryHandle::new(); 1042 | 1043 | let context = AioContext::new(&pool, 10).unwrap(); 1044 | let read_future = context 1045 | .read(fd, 1000000, buffer) 1046 | .map(move |_| { 1047 | assert!(false); 1048 | }) 1049 | .map_err(|err| { 1050 | assert!(err.error.kind() == io::ErrorKind::Other); 1051 | err 1052 | }); 1053 | 1054 | let cpu_future = pool.spawn(read_future); 1055 | let result = cpu_future.wait(); 1056 | 1057 | assert!(result.is_err()); 1058 | } 1059 | 1060 | remove_file(&file_name); 1061 | } 1062 | */ 1063 | 1064 | #[test] 1065 | fn read_many_blocks_mt() { 1066 | let file_name = temp_file_name(); 1067 | create_temp_file(&file_name); 1068 | 1069 | { 1070 | let owned_fd = OwnedFd::new_from_raw_fd(unsafe { 1071 | open( 1072 | mem::transmute(file_name.as_os_str().as_bytes().as_ptr()), 1073 | O_DIRECT | O_RDWR, 1074 | ) 1075 | }); 1076 | let fd = owned_fd.fd; 1077 | 1078 | let pool = futures_cpupool::CpuPool::new(5); 1079 | 1080 | { 1081 | let num_slots = 7; 1082 | let context = AioContext::new(&pool, num_slots).unwrap(); 1083 | 1084 | // 50 waves of requests just going above the lmit 1085 | 1086 | // Waves start here 1087 | for _wave in 0..50 { 1088 | let mut futures = Vec::new(); 1089 | 1090 | // Each wave makes 100 I/O requests 1091 | for index in 0..100 { 1092 | let buffer = MemoryHandle::new(); 1093 | let read_future = context 1094 | .read(fd, (index * 8192) % FILE_SIZE, buffer) 1095 | .map(move |result_buffer| { 1096 | assert!(validate_block(result_buffer.as_ref())); 1097 | }) 1098 | .map_err(|err| { 1099 | panic!("{:?}", err); 1100 | }); 1101 | 1102 | futures.push(pool.spawn(read_future)); 1103 | } 1104 | 1105 | // wait for all 100 requests to complete 1106 | let result = futures::future::join_all(futures).wait(); 1107 | 1108 | assert!(result.is_ok()); 1109 | 1110 | // all slots have been returned 1111 | assert!(context.inner.have_capacity.current_capacity() == num_slots); 1112 | } 1113 | } 1114 | } 1115 | 1116 | remove_file(&file_name); 1117 | } 1118 | 1119 | // A test with a mixed read/write workload 1120 | #[test] 1121 | fn mixed_read_write() { 1122 | let file_name = temp_file_name(); 1123 | create_temp_file(&file_name); 1124 | 1125 | let owned_fd = OwnedFd::new_from_raw_fd(unsafe { 1126 | open( 1127 | mem::transmute(file_name.as_os_str().as_bytes().as_ptr()), 1128 | O_DIRECT | O_RDWR, 1129 | ) 1130 | }); 1131 | let fd = owned_fd.fd; 1132 | 1133 | let mut futures = Vec::new(); 1134 | 1135 | let pool = futures_cpupool::CpuPool::new(5); 1136 | let context = AioContext::new(&pool, 7).unwrap(); 1137 | 1138 | // First access sequence 1139 | let buffer1 = MemoryHandle::new(); 1140 | 1141 | let sequence1 = { 1142 | let context1 = context.clone(); 1143 | let context2 = context.clone(); 1144 | let context3 = context.clone(); 1145 | let context4 = context.clone(); 1146 | let context5 = context.clone(); 1147 | let context6 = context.clone(); 1148 | 1149 | context1 1150 | .read(fd, 8192, buffer1) 1151 | .map(|mut buffer| -> MemoryHandle { 1152 | assert!(validate_block(buffer.as_ref())); 1153 | fill_pattern(0u8, buffer.as_mut()); 1154 | buffer 1155 | }) 1156 | .and_then(move |buffer| context2.write(fd, 8192, buffer)) 1157 | .and_then(move |buffer| context3.read(fd, 0, buffer)) 1158 | .map(|mut buffer| -> MemoryHandle { 1159 | assert!(validate_block(buffer.as_ref())); 1160 | fill_pattern(1u8, buffer.as_mut()); 1161 | buffer 1162 | }) 1163 | .and_then(move |buffer| context4.write(fd, 0, buffer)) 1164 | .and_then(move |buffer| context5.read(fd, 8192, buffer)) 1165 | .map(|buffer| -> MemoryHandle { 1166 | assert!(validate_pattern(0u8, buffer.as_ref())); 1167 | buffer 1168 | }) 1169 | .and_then(move |buffer| context6.read(fd, 0, buffer)) 1170 | .map(|buffer| -> MemoryHandle { 1171 | assert!(validate_pattern(1u8, buffer.as_ref())); 1172 | buffer 1173 | }) 1174 | .map_err(|err| { 1175 | panic!("{:?}", err); 1176 | }) 1177 | }; 1178 | 1179 | // Second access sequence 1180 | 1181 | let buffer2 = MemoryHandle::new(); 1182 | 1183 | let sequence2 = { 1184 | let context1 = context.clone(); 1185 | let context2 = context.clone(); 1186 | let context3 = context.clone(); 1187 | let context4 = context.clone(); 1188 | let context5 = context.clone(); 1189 | let context6 = context.clone(); 1190 | 1191 | context1 1192 | .read(fd, 16384, buffer2) 1193 | .map(|mut buffer| -> MemoryHandle { 1194 | assert!(validate_block(buffer.as_ref())); 1195 | fill_pattern(2u8, buffer.as_mut()); 1196 | buffer 1197 | }) 1198 | .and_then(move |buffer| context2.write(fd, 16384, buffer)) 1199 | .and_then(move |buffer| context3.read(fd, 24576, buffer)) 1200 | .map(|mut buffer| -> MemoryHandle { 1201 | assert!(validate_block(buffer.as_ref())); 1202 | fill_pattern(3u8, buffer.as_mut()); 1203 | buffer 1204 | }) 1205 | .and_then(move |buffer| context4.write(fd, 24576, buffer)) 1206 | .and_then(move |buffer| context5.read(fd, 16384, buffer)) 1207 | .map(|buffer| -> MemoryHandle { 1208 | assert!(validate_pattern(2u8, buffer.as_ref())); 1209 | buffer 1210 | }) 1211 | .and_then(move |buffer| context6.read(fd, 24576, buffer)) 1212 | .map(|buffer| -> MemoryHandle { 1213 | assert!(validate_pattern(3u8, buffer.as_ref())); 1214 | buffer 1215 | }) 1216 | .map_err(|err| { 1217 | panic!("{:?}", err); 1218 | }) 1219 | }; 1220 | 1221 | // Third access sequence 1222 | 1223 | let buffer3 = MemoryHandle::new(); 1224 | 1225 | let sequence3 = { 1226 | let context1 = context.clone(); 1227 | let context2 = context.clone(); 1228 | let context3 = context.clone(); 1229 | let context4 = context.clone(); 1230 | let context5 = context.clone(); 1231 | let context6 = context.clone(); 1232 | 1233 | context1 1234 | .read(fd, 40960, buffer3) 1235 | .map(|mut buffer| -> MemoryHandle { 1236 | assert!(validate_block(buffer.as_ref())); 1237 | fill_pattern(5u8, buffer.as_mut()); 1238 | buffer 1239 | }) 1240 | .and_then(move |buffer| context2.write(fd, 40960, buffer)) 1241 | .and_then(move |buffer| context3.read(fd, 32768, buffer)) 1242 | .map(|mut buffer| -> MemoryHandle { 1243 | assert!(validate_block(buffer.as_ref())); 1244 | fill_pattern(6u8, buffer.as_mut()); 1245 | buffer 1246 | }) 1247 | .and_then(move |buffer| context4.write(fd, 32768, buffer)) 1248 | .and_then(move |buffer| context5.read(fd, 40960, buffer)) 1249 | .map(|buffer| -> MemoryHandle { 1250 | assert!(validate_pattern(5u8, buffer.as_ref())); 1251 | buffer 1252 | }) 1253 | .and_then(move |buffer| context6.read(fd, 32768, buffer)) 1254 | .map(|buffer| -> MemoryHandle { 1255 | assert!(validate_pattern(6u8, buffer.as_ref())); 1256 | buffer 1257 | }) 1258 | .map_err(|err| { 1259 | panic!("{:?}", err); 1260 | }) 1261 | }; 1262 | 1263 | // Launch the three futures 1264 | futures.push(pool.spawn(sequence1)); 1265 | futures.push(pool.spawn(sequence2)); 1266 | futures.push(pool.spawn(sequence3)); 1267 | 1268 | // Wair for completion 1269 | let result = futures::future::join_all(futures).wait(); 1270 | 1271 | assert!(result.is_ok()); 1272 | } 1273 | 1274 | // Fille the buffer with a pattern that has a dependency on the provided key. 1275 | fn fill_pattern(key: u8, buffer: &mut [u8]) { 1276 | // The pattern we generate is an alternation of the key value and an index value 1277 | // For this we ensure that the buffer has an even number of elements 1278 | assert!(buffer.len() % 2 == 0); 1279 | 1280 | for index in 0..buffer.len() / 2 { 1281 | buffer[index * 2] = key; 1282 | buffer[index * 2 + 1] = index as u8; 1283 | } 1284 | } 1285 | 1286 | // Validate that the buffer is filled with a pattern as generated by the provided key. 1287 | fn validate_pattern(key: u8, buffer: &[u8]) -> bool { 1288 | // The pattern we generate is an alternation of the key value and an index value 1289 | // For this we ensure that the buffer has an even number of elements 1290 | assert!(buffer.len() % 2 == 0); 1291 | 1292 | for index in 0..buffer.len() / 2 { 1293 | if (buffer[index * 2] != key) || (buffer[index * 2 + 1] != (index as u8)) { 1294 | return false; 1295 | } 1296 | } 1297 | 1298 | return true; 1299 | } 1300 | 1301 | fn validate_block(data: &[u8]) -> bool { 1302 | for index in 0..data.len() { 1303 | if data[index] != index as u8 { 1304 | return false; 1305 | } 1306 | } 1307 | 1308 | true 1309 | } 1310 | 1311 | struct OwnedFd { 1312 | fd: RawFd, 1313 | } 1314 | 1315 | impl OwnedFd { 1316 | fn new_from_raw_fd(fd: RawFd) -> OwnedFd { 1317 | OwnedFd { fd } 1318 | } 1319 | } 1320 | 1321 | impl Drop for OwnedFd { 1322 | fn drop(&mut self) { 1323 | let result = unsafe { close(self.fd) }; 1324 | assert!(result == 0); 1325 | } 1326 | } 1327 | } 1328 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | // =============================================================================================== 2 | // Copyright (c) 2018 Hans-Martin Will 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // =============================================================================================== 22 | 23 | use std::sync; 24 | use std::io; 25 | use std::collections; 26 | 27 | use futures; 28 | 29 | // ----------------------------------------------------------------------------------------------- 30 | // Semaphore that's workable with Futures 31 | // 32 | // Currently this is rather barebones; may consider expanding it into something library-grade 33 | // and then exposing it by itself. 34 | // ----------------------------------------------------------------------------------------------- 35 | 36 | #[derive(Debug)] 37 | struct SemaphoreInner { 38 | capacity: usize, 39 | waiters: collections::VecDeque, 40 | } 41 | 42 | #[derive(Debug)] 43 | pub struct Semaphore { 44 | inner: sync::RwLock, 45 | } 46 | 47 | impl Semaphore { 48 | pub fn new(initial: usize) -> Semaphore { 49 | Semaphore { 50 | inner: sync::RwLock::new(SemaphoreInner { 51 | capacity: initial, 52 | waiters: collections::VecDeque::new(), 53 | }), 54 | } 55 | } 56 | 57 | pub fn acquire(&self) -> SemaphoreHandle { 58 | let mut lock_result = self.inner.write(); 59 | 60 | match lock_result { 61 | Ok(ref mut guard) => { 62 | if guard.capacity > 0 { 63 | guard.capacity -= 1; 64 | SemaphoreHandle::Completed(futures::future::result(Ok(()))) 65 | } else { 66 | guard.waiters.push_back(futures::task::current()); 67 | SemaphoreHandle::Waiting 68 | } 69 | } 70 | Err(err) => panic!("Lock failure {:?}", err), 71 | } 72 | } 73 | 74 | pub fn release(&self) { 75 | let mut lock_result = self.inner.write(); 76 | 77 | match lock_result { 78 | Ok(ref mut guard) => { 79 | if !guard.waiters.is_empty() { 80 | guard.waiters.pop_front().unwrap().notify(); 81 | } else { 82 | guard.capacity += 1; 83 | } 84 | } 85 | Err(err) => panic!("Lock failure {:?}", err), 86 | } 87 | } 88 | 89 | // For testing code 90 | pub fn current_capacity(&self) -> usize { 91 | let mut lock_result = self.inner.read(); 92 | 93 | match lock_result { 94 | Ok(ref mut guard) => guard.capacity, 95 | Err(_) => panic!("Lock failure"), 96 | } 97 | } 98 | } 99 | 100 | pub enum SemaphoreHandle { 101 | Waiting, 102 | Completed(futures::future::FutureResult<(), io::Error>), 103 | } 104 | 105 | impl futures::Future for SemaphoreHandle { 106 | type Item = (); 107 | type Error = io::Error; 108 | 109 | fn poll(&mut self) -> Result, io::Error> { 110 | match self { 111 | &mut SemaphoreHandle::Completed(_) => Ok(futures::Async::Ready(())), 112 | &mut SemaphoreHandle::Waiting => { 113 | *self = SemaphoreHandle::Completed(futures::future::result(Ok(()))); 114 | Ok(futures::Async::NotReady) 115 | } 116 | } 117 | } 118 | } 119 | --------------------------------------------------------------------------------