polling-2.2.0/.cargo_vcs_info.json0000644000000001360000000000100125010ustar { "git": { "sha1": "89ce9bafaa31c1d53a3fbbe13b2015638c781acf" }, "path_in_vcs": "" }polling-2.2.0/.github/workflows/build-and-test.yaml000064400000000000000000000033710072674642500204220ustar 00000000000000name: Build and test on: push: branches: - master pull_request: jobs: build_and_test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] rust: [nightly, beta, stable, 1.40.0] steps: - uses: actions/checkout@v2 - name: Install latest ${{ matrix.rust }} uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} profile: minimal override: true - name: Run basic cargo check uses: actions-rs/cargo@v1 with: command: check args: --all --bins --all-features - name: Run cargo check without default features uses: actions-rs/cargo@v1 with: command: check args: --no-default-features - name: Run cargo check uses: actions-rs/cargo@v1 with: command: check args: --all --benches --bins --examples --tests --all-features - name: Run cargo check (without dev-dependencies to catch missing feature flags) if: startsWith(matrix.rust, 'nightly') uses: actions-rs/cargo@v1 with: command: check args: -Z features=dev_dep - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test - name: Clone async-io run: git clone https://github.com/smol-rs/async-io.git - name: Add patch section run: echo '[patch.crates-io]' >> async-io/Cargo.toml - name: Patch polling run: echo 'polling = { path = ".." }' >> async-io/Cargo.toml - name: Test async-io run: cargo test --manifest-path=async-io/Cargo.toml if: matrix.rust != '1.40.0' polling-2.2.0/.github/workflows/cross.yaml000064400000000000000000000025520072674642500167370ustar 00000000000000name: Cross compile on: push: branches: - master pull_request: jobs: cross: name: Cross compile runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@master - name: Install nightly uses: actions-rs/toolchain@v1 with: toolchain: nightly override: true - name: Install cross run: cargo install cross - name: Android if: startsWith(matrix.os, 'ubuntu') run: cross test --target arm-linux-androideabi - name: NetBSD if: startsWith(matrix.os, 'ubuntu') run: cross build --target x86_64-unknown-netbsd - name: FreeBSD if: startsWith(matrix.os, 'ubuntu') run: cross build --target x86_64-unknown-freebsd - name: iOS if: startsWith(matrix.os, 'macos') run: cross build --target aarch64-apple-ios - name: Linux x32 if: startsWith(matrix.os, 'ubuntu') run: cross check --target x86_64-unknown-linux-gnux32 - name: Add fuchsia target if: startsWith(matrix.os, 'ubuntu') run: rustup target add x86_64-fuchsia - name: Fuchsia if: startsWith(matrix.os, 'ubuntu') run: cross build --target x86_64-fuchsia # - name: illumos # if: startsWith(matrix.os, 'ubuntu') # run: cross build --target x86_64-unknown-illumos polling-2.2.0/.github/workflows/lint.yaml000064400000000000000000000006750072674642500165600ustar 00000000000000name: Lint on: push: branches: - master pull_request: jobs: clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal components: clippy - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --all-features -- -W clippy::all polling-2.2.0/.github/workflows/security.yaml000064400000000000000000000004240072674642500174510ustar 00000000000000name: Security audit on: push: branches: - master pull_request: jobs: security_audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} polling-2.2.0/.gitignore000064400000000000000000000000230072674642500133040ustar 00000000000000/target Cargo.lock polling-2.2.0/CHANGELOG.md000064400000000000000000000032260072674642500131350ustar 00000000000000# Version 2.2.0 - Support VxWorks, Fuchsia and other Unix systems by using poll. (#26) # Version 2.1.0 - Switch from `wepoll-sys` to `wepoll-ffi`. # Version 2.0.3 - Update `cfg-if` dependency to 1. # Version 2.0.2 - Replace manual pointer conversion with `as_ptr()` and `as_mut_ptr()`. # Version 2.0.1 - Minor docs improvements. # Version 2.0.0 - Add `Event` argument to `Poller::insert()`. - Don't put fd/socket in non-blocking mode upon insertion. - Rename `insert()`/`interest()`/`remove()` to `add()`/`modify()`/`delete()`. - Replace `wepoll-sys-stjepang` with an `wepoll-sys`. # Version 1.1.0 - Add "std" cargo feature. # Version 1.0.3 - Remove `libc` dependency on Windows. # Version 1.0.2 - Bump MSRV to 1.40.0 - Replace the `epoll_create1` hack with a cleaner solution. - Pass timeout to `epoll_wait` to support systems without `timerfd`. # Version 1.0.1 - Fix a typo in the readme. # Version 1.0.0 - Stabilize. # Version 0.1.9 - Fix compilation on x86_64-unknown-linux-gnux32 # Version 0.1.8 - Replace `log::debug!` with `log::trace!`. # Version 0.1.7 - Specify oneshot mode in epoll/wepoll at insert. # Version 0.1.6 - Add logging. # Version 0.1.5 - Fix a bug where epoll would block when the timeout is set to zero. - More tests. # Version 0.1.4 - Optimize notifications. - Fix a bug in timeouts on Windows where it would trigger too early. - Support sub-nanosecond precision on Linux/Android. # Version 0.1.3 - Improve error handling around event ports fcntl # Version 0.1.2 - Add support for event ports (illumos and Solaris) # Version 0.1.1 - Improve documentation - Fix a bug in `Event::none()`. # Version 0.1.0 - Initial version polling-2.2.0/Cargo.lock0000644000000041330000000000100104550ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "cc" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "easy-parallel" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd4afd79212583ff429b913ad6605242ed7eec277e950b1438f300748f948f4" [[package]] name = "libc" version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] [[package]] name = "polling" version = "2.2.0" dependencies = [ "cfg-if", "easy-parallel", "libc", "log", "wepoll-ffi", "winapi", ] [[package]] name = "wepoll-ffi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" dependencies = [ "cc", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" polling-2.2.0/Cargo.toml0000644000000026260000000000100105050ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "polling" version = "2.2.0" authors = ["Stjepan Glavina "] description = "Portable interface to epoll, kqueue, event ports, and wepoll" homepage = "https://github.com/smol-rs/polling" documentation = "https://docs.rs/polling" keywords = ["mio", "epoll", "kqueue", "iocp", "wepoll"] categories = ["asynchronous", "network-programming", "os"] license = "Apache-2.0 OR MIT" repository = "https://github.com/smol-rs/polling" [dependencies.cfg-if] version = "1" [dependencies.log] version = "0.4.11" [dev-dependencies.easy-parallel] version = "3.1.0" [features] default = ["std"] std = [] [target."cfg(any(unix, target_os = \"fuchsia\", target_os = \"vxworks\"))".dependencies.libc] version = "0.2.77" [target."cfg(windows)".dependencies.wepoll-ffi] version = "0.1.2" features = ["null-overlapped-wakeups-patch"] [target."cfg(windows)".dependencies.winapi] version = "0.3.9" features = ["ioapiset", "winsock2"] polling-2.2.0/Cargo.toml.orig000064400000000000000000000017200072674642500142100ustar 00000000000000[package] name = "polling" # When publishing a new version: # - Update CHANGELOG.md # - Create "v2.x.y" git tag version = "2.2.0" authors = ["Stjepan Glavina "] edition = "2018" description = "Portable interface to epoll, kqueue, event ports, and wepoll" license = "Apache-2.0 OR MIT" repository = "https://github.com/smol-rs/polling" homepage = "https://github.com/smol-rs/polling" documentation = "https://docs.rs/polling" keywords = ["mio", "epoll", "kqueue", "iocp", "wepoll"] categories = ["asynchronous", "network-programming", "os"] [features] default = ["std"] std = [] [dependencies] cfg-if = "1" log = "0.4.11" [target.'cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))'.dependencies] libc = "0.2.77" [target.'cfg(windows)'.dependencies] wepoll-ffi = { version = "0.1.2", features = ["null-overlapped-wakeups-patch"] } winapi = { version = "0.3.9", features = ["ioapiset", "winsock2"] } [dev-dependencies] easy-parallel = "3.1.0" polling-2.2.0/LICENSE-APACHE000064400000000000000000000251370072674642500132550ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. polling-2.2.0/LICENSE-MIT000064400000000000000000000017770072674642500127710ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. polling-2.2.0/README.md000064400000000000000000000046270072674642500126110ustar 00000000000000# polling [![Build](https://github.com/smol-rs/polling/workflows/Build%20and%20test/badge.svg)]( https://github.com/smol-rs/polling/actions) [![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)]( https://github.com/smol-rs/polling) [![Cargo](https://img.shields.io/crates/v/polling.svg)]( https://crates.io/crates/polling) [![Documentation](https://docs.rs/polling/badge.svg)]( https://docs.rs/polling) Portable interface to epoll, kqueue, event ports, and wepoll. Supported platforms: - [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android - [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, FreeBSD, NetBSD, OpenBSD, DragonFly BSD - [event ports](https://illumos.org/man/port_create): illumos, Solaris - [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, other Unix systems - [wepoll](https://github.com/piscisaureus/wepoll): Windows Polling is done in oneshot mode, which means interest in I/O events needs to be reset after an event is delivered if we're interested in the next event of the same kind. Only one thread can be waiting for I/O events at a time. ## Examples ```rust,no_run use polling::{Event, Poller}; use std::net::TcpListener; // Create a TCP listener. let socket = TcpListener::bind("127.0.0.1:8000")?; socket.set_nonblocking(true)?; let key = 7; // Arbitrary key identifying the socket. // Create a poller and register interest in readability on the socket. let poller = Poller::new()?; poller.add(&socket, Event::readable(key))?; // The event loop. let mut events = Vec::new(); loop { // Wait for at least one I/O event. events.clear(); poller.wait(&mut events, None)?; for ev in &events { if ev.key == key { // Perform a non-blocking accept operation. socket.accept()?; // Set interest in the next readability event. poller.modify(&socket, Event::readable(key))?; } } } ``` ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. #### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. polling-2.2.0/examples/two-listeners.rs000064400000000000000000000017270072674642500163330ustar 00000000000000use std::io; use std::net::TcpListener; use polling::{Event, Poller}; fn main() -> io::Result<()> { let l1 = TcpListener::bind("127.0.0.1:8001")?; let l2 = TcpListener::bind("127.0.0.1:8002")?; l1.set_nonblocking(true)?; l2.set_nonblocking(true)?; let poller = Poller::new()?; poller.add(&l1, Event::readable(1))?; poller.add(&l2, Event::readable(2))?; let mut events = Vec::new(); loop { events.clear(); poller.wait(&mut events, None)?; for ev in &events { match ev.key { 1 => { println!("Accept on l1"); l1.accept()?; poller.modify(&l1, Event::readable(1))?; } 2 => { println!("Accept on l2"); l2.accept()?; poller.modify(&l2, Event::readable(2))?; } _ => unreachable!(), } } } } polling-2.2.0/src/epoll.rs000064400000000000000000000207770072674642500136060ustar 00000000000000//! Bindings to epoll (Linux, Android). use std::convert::TryInto; use std::io; use std::os::unix::io::RawFd; use std::ptr; use std::time::Duration; use crate::Event; /// Interface to epoll. #[derive(Debug)] pub struct Poller { /// File descriptor for the epoll instance. epoll_fd: RawFd, /// File descriptor for the eventfd that produces notifications. event_fd: RawFd, /// File descriptor for the timerfd that produces timeouts. timer_fd: Option, } impl Poller { /// Creates a new poller. pub fn new() -> io::Result { // Create an epoll instance. // // Use `epoll_create1` with `EPOLL_CLOEXEC`. let epoll_fd = syscall!(syscall( libc::SYS_epoll_create1, libc::EPOLL_CLOEXEC as libc::c_int )) .map(|fd| fd as libc::c_int) .or_else(|e| { match e.raw_os_error() { Some(libc::ENOSYS) => { // If `epoll_create1` is not implemented, use `epoll_create` // and manually set `FD_CLOEXEC`. let fd = syscall!(epoll_create(1024))?; if let Ok(flags) = syscall!(fcntl(fd, libc::F_GETFD)) { let _ = syscall!(fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC)); } Ok(fd) } _ => Err(e), } })?; // Set up eventfd and timerfd. let event_fd = syscall!(eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK))?; let timer_fd = syscall!(syscall( libc::SYS_timerfd_create, libc::CLOCK_MONOTONIC as libc::c_int, (libc::TFD_CLOEXEC | libc::TFD_NONBLOCK) as libc::c_int, )) .map(|fd| fd as libc::c_int) .ok(); let poller = Poller { epoll_fd, event_fd, timer_fd, }; if let Some(timer_fd) = timer_fd { poller.add(timer_fd, Event::none(crate::NOTIFY_KEY))?; } poller.add( event_fd, Event { key: crate::NOTIFY_KEY, readable: true, writable: false, }, )?; log::trace!( "new: epoll_fd={}, event_fd={}, timer_fd={:?}", epoll_fd, event_fd, timer_fd ); Ok(poller) } /// Adds a new file descriptor. pub fn add(&self, fd: RawFd, ev: Event) -> io::Result<()> { log::trace!("add: epoll_fd={}, fd={}, ev={:?}", self.epoll_fd, fd, ev); self.ctl(libc::EPOLL_CTL_ADD, fd, Some(ev)) } /// Modifies an existing file descriptor. pub fn modify(&self, fd: RawFd, ev: Event) -> io::Result<()> { log::trace!("modify: epoll_fd={}, fd={}, ev={:?}", self.epoll_fd, fd, ev); self.ctl(libc::EPOLL_CTL_MOD, fd, Some(ev)) } /// Deletes a file descriptor. pub fn delete(&self, fd: RawFd) -> io::Result<()> { log::trace!("remove: epoll_fd={}, fd={}", self.epoll_fd, fd); self.ctl(libc::EPOLL_CTL_DEL, fd, None) } /// Waits for I/O events with an optional timeout. pub fn wait(&self, events: &mut Events, timeout: Option) -> io::Result<()> { log::trace!("wait: epoll_fd={}, timeout={:?}", self.epoll_fd, timeout); if let Some(timer_fd) = self.timer_fd { // Configure the timeout using timerfd. let new_val = libc::itimerspec { it_interval: TS_ZERO, it_value: match timeout { None => TS_ZERO, Some(t) => libc::timespec { tv_sec: t.as_secs() as libc::time_t, tv_nsec: (t.subsec_nanos() as libc::c_long).into(), }, }, }; syscall!(syscall( libc::SYS_timerfd_settime, timer_fd as libc::c_int, 0 as libc::c_int, &new_val as *const libc::itimerspec, ptr::null_mut() as *mut libc::itimerspec ))?; // Set interest in timerfd. self.modify( timer_fd, Event { key: crate::NOTIFY_KEY, readable: true, writable: false, }, )?; } // Timeout in milliseconds for epoll. let timeout_ms = match (self.timer_fd, timeout) { (_, Some(t)) if t == Duration::from_secs(0) => 0, (None, Some(t)) => { // Round up to a whole millisecond. let mut ms = t.as_millis().try_into().unwrap_or(std::i32::MAX); if Duration::from_millis(ms as u64) < t { ms = ms.saturating_add(1); } ms } _ => -1, }; // Wait for I/O events. let res = syscall!(epoll_wait( self.epoll_fd, events.list.as_mut_ptr() as *mut libc::epoll_event, events.list.len() as libc::c_int, timeout_ms as libc::c_int, ))?; events.len = res as usize; log::trace!("new events: epoll_fd={}, res={}", self.epoll_fd, res); // Clear the notification (if received) and re-register interest in it. let mut buf = [0u8; 8]; let _ = syscall!(read( self.event_fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len() )); self.modify( self.event_fd, Event { key: crate::NOTIFY_KEY, readable: true, writable: false, }, )?; Ok(()) } /// Sends a notification to wake up the current or next `wait()` call. pub fn notify(&self) -> io::Result<()> { log::trace!( "notify: epoll_fd={}, event_fd={}", self.epoll_fd, self.event_fd ); let buf: [u8; 8] = 1u64.to_ne_bytes(); let _ = syscall!(write( self.event_fd, buf.as_ptr() as *const libc::c_void, buf.len() )); Ok(()) } /// Passes arguments to `epoll_ctl`. fn ctl(&self, op: libc::c_int, fd: RawFd, ev: Option) -> io::Result<()> { let mut ev = ev.map(|ev| { let mut flags = libc::EPOLLONESHOT; if ev.readable { flags |= read_flags(); } if ev.writable { flags |= write_flags(); } libc::epoll_event { events: flags as _, u64: ev.key as u64, } }); syscall!(epoll_ctl( self.epoll_fd, op, fd, ev.as_mut() .map(|ev| ev as *mut libc::epoll_event) .unwrap_or(ptr::null_mut()), ))?; Ok(()) } } impl Drop for Poller { fn drop(&mut self) { log::trace!( "drop: epoll_fd={}, event_fd={}, timer_fd={:?}", self.epoll_fd, self.event_fd, self.timer_fd ); if let Some(timer_fd) = self.timer_fd { let _ = self.delete(timer_fd); let _ = syscall!(close(timer_fd)); } let _ = self.delete(self.event_fd); let _ = syscall!(close(self.event_fd)); let _ = syscall!(close(self.epoll_fd)); } } /// `timespec` value that equals zero. const TS_ZERO: libc::timespec = libc::timespec { tv_sec: 0, tv_nsec: 0, }; /// Epoll flags for all possible readability events. fn read_flags() -> libc::c_int { libc::EPOLLIN | libc::EPOLLRDHUP | libc::EPOLLHUP | libc::EPOLLERR | libc::EPOLLPRI } /// Epoll flags for all possible writability events. fn write_flags() -> libc::c_int { libc::EPOLLOUT | libc::EPOLLHUP | libc::EPOLLERR } /// A list of reported I/O events. pub struct Events { list: Box<[libc::epoll_event]>, len: usize, } unsafe impl Send for Events {} impl Events { /// Creates an empty list. pub fn new() -> Events { let ev = libc::epoll_event { events: 0, u64: 0 }; let list = vec![ev; 1000].into_boxed_slice(); let len = 0; Events { list, len } } /// Iterates over I/O events. pub fn iter(&self) -> impl Iterator + '_ { self.list[..self.len].iter().map(|ev| Event { key: ev.u64 as usize, readable: (ev.events as libc::c_int & read_flags()) != 0, writable: (ev.events as libc::c_int & write_flags()) != 0, }) } } polling-2.2.0/src/kqueue.rs000064400000000000000000000154000072674642500137550ustar 00000000000000//! Bindings to kqueue (macOS, iOS, FreeBSD, NetBSD, OpenBSD, DragonFly BSD). use std::io::{self, Read, Write}; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::UnixStream; use std::ptr; use std::time::Duration; use crate::Event; /// Interface to kqueue. #[derive(Debug)] pub struct Poller { /// File descriptor for the kqueue instance. kqueue_fd: RawFd, /// Read side of a pipe for consuming notifications. read_stream: UnixStream, /// Write side of a pipe for producing notifications. write_stream: UnixStream, } impl Poller { /// Creates a new poller. pub fn new() -> io::Result { // Create a kqueue instance. let kqueue_fd = syscall!(kqueue())?; syscall!(fcntl(kqueue_fd, libc::F_SETFD, libc::FD_CLOEXEC))?; // Set up the notification pipe. let (read_stream, write_stream) = UnixStream::pair()?; read_stream.set_nonblocking(true)?; write_stream.set_nonblocking(true)?; let poller = Poller { kqueue_fd, read_stream, write_stream, }; poller.add( poller.read_stream.as_raw_fd(), Event { key: crate::NOTIFY_KEY, readable: true, writable: false, }, )?; log::trace!( "new: kqueue_fd={}, read_stream={:?}", kqueue_fd, poller.read_stream ); Ok(poller) } /// Adds a new file descriptor. pub fn add(&self, fd: RawFd, ev: Event) -> io::Result<()> { // File descriptors don't need to be added explicitly, so just modify the interest. self.modify(fd, ev) } /// Modifies an existing file descriptor. pub fn modify(&self, fd: RawFd, ev: Event) -> io::Result<()> { if fd != self.read_stream.as_raw_fd() { log::trace!("add: kqueue_fd={}, fd={}, ev={:?}", self.kqueue_fd, fd, ev); } let read_flags = if ev.readable { libc::EV_ADD | libc::EV_ONESHOT } else { libc::EV_DELETE }; let write_flags = if ev.writable { libc::EV_ADD | libc::EV_ONESHOT } else { libc::EV_DELETE }; // A list of changes for kqueue. let changelist = [ libc::kevent { ident: fd as _, filter: libc::EVFILT_READ, flags: read_flags | libc::EV_RECEIPT, fflags: 0, data: 0, udata: ev.key as _, }, libc::kevent { ident: fd as _, filter: libc::EVFILT_WRITE, flags: write_flags | libc::EV_RECEIPT, fflags: 0, data: 0, udata: ev.key as _, }, ]; // Apply changes. let mut eventlist = changelist; syscall!(kevent( self.kqueue_fd, changelist.as_ptr() as *const libc::kevent, changelist.len() as _, eventlist.as_mut_ptr() as *mut libc::kevent, eventlist.len() as _, ptr::null(), ))?; // Check for errors. for ev in &eventlist { // Explanation for ignoring EPIPE: https://github.com/tokio-rs/mio/issues/582 if (ev.flags & libc::EV_ERROR) != 0 && ev.data != 0 && ev.data != libc::ENOENT as _ && ev.data != libc::EPIPE as _ { return Err(io::Error::from_raw_os_error(ev.data as _)); } } Ok(()) } /// Deletes a file descriptor. pub fn delete(&self, fd: RawFd) -> io::Result<()> { // Simply delete interest in the file descriptor. self.modify(fd, Event::none(0)) } /// Waits for I/O events with an optional timeout. pub fn wait(&self, events: &mut Events, timeout: Option) -> io::Result<()> { log::trace!("wait: kqueue_fd={}, timeout={:?}", self.kqueue_fd, timeout); // Convert the `Duration` to `libc::timespec`. let timeout = timeout.map(|t| libc::timespec { tv_sec: t.as_secs() as libc::time_t, tv_nsec: t.subsec_nanos() as libc::c_long, }); // Wait for I/O events. let changelist = []; let eventlist = &mut events.list; let res = syscall!(kevent( self.kqueue_fd, changelist.as_ptr() as *const libc::kevent, changelist.len() as _, eventlist.as_mut_ptr() as *mut libc::kevent, eventlist.len() as _, match &timeout { None => ptr::null(), Some(t) => t, } ))?; events.len = res as usize; log::trace!("new events: kqueue_fd={}, res={}", self.kqueue_fd, res); // Clear the notification (if received) and re-register interest in it. while (&self.read_stream).read(&mut [0; 64]).is_ok() {} self.modify( self.read_stream.as_raw_fd(), Event { key: crate::NOTIFY_KEY, readable: true, writable: false, }, )?; Ok(()) } /// Sends a notification to wake up the current or next `wait()` call. pub fn notify(&self) -> io::Result<()> { log::trace!("notify: kqueue_fd={}", self.kqueue_fd); let _ = (&self.write_stream).write(&[1]); Ok(()) } } impl Drop for Poller { fn drop(&mut self) { log::trace!("drop: kqueue_fd={}", self.kqueue_fd); let _ = self.delete(self.read_stream.as_raw_fd()); let _ = syscall!(close(self.kqueue_fd)); } } /// A list of reported I/O events. pub struct Events { list: Box<[libc::kevent]>, len: usize, } unsafe impl Send for Events {} impl Events { /// Creates an empty list. pub fn new() -> Events { let ev = libc::kevent { ident: 0 as _, filter: 0, flags: 0, fflags: 0, data: 0, udata: 0 as _, }; let list = vec![ev; 1000].into_boxed_slice(); let len = 0; Events { list, len } } /// Iterates over I/O events. pub fn iter(&self) -> impl Iterator + '_ { // On some platforms, closing the read end of a pipe wakes up writers, but the // event is reported as EVFILT_READ with the EV_EOF flag. // // https://github.com/golang/go/commit/23aad448b1e3f7c3b4ba2af90120bde91ac865b4 self.list[..self.len].iter().map(|ev| Event { key: ev.udata as usize, readable: ev.filter == libc::EVFILT_READ, writable: ev.filter == libc::EVFILT_WRITE || (ev.filter == libc::EVFILT_READ && (ev.flags & libc::EV_EOF) != 0), }) } } polling-2.2.0/src/lib.rs000064400000000000000000000360100072674642500132240ustar 00000000000000//! Portable interface to epoll, kqueue, event ports, and wepoll. //! //! Supported platforms: //! - [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android //! - [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, FreeBSD, NetBSD, OpenBSD, //! DragonFly BSD //! - [event ports](https://illumos.org/man/port_create): illumos, Solaris //! - [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, other Unix systems //! - [wepoll](https://github.com/piscisaureus/wepoll): Windows //! //! Polling is done in oneshot mode, which means interest in I/O events needs to be re-enabled //! after an event is delivered if we're interested in the next event of the same kind. //! //! Only one thread can be waiting for I/O events at a time. //! //! # Examples //! //! ```no_run //! use polling::{Event, Poller}; //! use std::net::TcpListener; //! //! // Create a TCP listener. //! let socket = TcpListener::bind("127.0.0.1:8000")?; //! socket.set_nonblocking(true)?; //! let key = 7; // Arbitrary key identifying the socket. //! //! // Create a poller and register interest in readability on the socket. //! let poller = Poller::new()?; //! poller.add(&socket, Event::readable(key))?; //! //! // The event loop. //! let mut events = Vec::new(); //! loop { //! // Wait for at least one I/O event. //! events.clear(); //! poller.wait(&mut events, None)?; //! //! for ev in &events { //! if ev.key == key { //! // Perform a non-blocking accept operation. //! socket.accept()?; //! // Set interest in the next readability event. //! poller.modify(&socket, Event::readable(key))?; //! } //! } //! } //! # std::io::Result::Ok(()) //! ``` #![cfg(feature = "std")] #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] use std::fmt; use std::io; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; use std::time::Duration; use std::usize; use cfg_if::cfg_if; /// Calls a libc function and results in `io::Result`. #[cfg(unix)] macro_rules! syscall { ($fn:ident $args:tt) => {{ let res = unsafe { libc::$fn $args }; if res == -1 { Err(std::io::Error::last_os_error()) } else { Ok(res) } }}; } cfg_if! { if #[cfg(any(target_os = "linux", target_os = "android"))] { mod epoll; use epoll as sys; } else if #[cfg(any( target_os = "illumos", target_os = "solaris", ))] { mod port; use port as sys; } else if #[cfg(any( target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", ))] { mod kqueue; use kqueue as sys; } else if #[cfg(any( target_os = "vxworks", target_os = "fuchsia", unix, ))] { mod poll; use poll as sys; } else if #[cfg(target_os = "windows")] { mod wepoll; use wepoll as sys; } else { compile_error!("polling does not support this target OS"); } } /// Key associated with notifications. const NOTIFY_KEY: usize = std::usize::MAX; /// Indicates that a file descriptor or socket can read or write without blocking. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Event { /// Key identifying the file descriptor or socket. pub key: usize, /// Can it do a read operation without blocking? pub readable: bool, /// Can it do a write operation without blocking? pub writable: bool, } impl Event { /// All kinds of events (readable and writable). /// /// Equivalent to: `Event { key, readable: true, writable: true }` pub fn all(key: usize) -> Event { Event { key, readable: true, writable: true, } } /// Only the readable event. /// /// Equivalent to: `Event { key, readable: true, writable: false }` pub fn readable(key: usize) -> Event { Event { key, readable: true, writable: false, } } /// Only the writable event. /// /// Equivalent to: `Event { key, readable: false, writable: true }` pub fn writable(key: usize) -> Event { Event { key, readable: false, writable: true, } } /// No events. /// /// Equivalent to: `Event { key, readable: false, writable: false }` pub fn none(key: usize) -> Event { Event { key, readable: false, writable: false, } } } /// Waits for I/O events. pub struct Poller { poller: sys::Poller, events: Mutex, notified: AtomicBool, } impl Poller { /// Creates a new poller. /// /// # Examples /// /// ``` /// use polling::Poller; /// /// let poller = Poller::new()?; /// # std::io::Result::Ok(()) /// ``` pub fn new() -> io::Result { Ok(Poller { poller: sys::Poller::new()?, events: Mutex::new(sys::Events::new()), notified: AtomicBool::new(false), }) } /// Adds a file descriptor or socket to the poller. /// /// A file descriptor or socket is considered readable or writable when a read or write /// operation on it would not block. This doesn't mean the read or write operation will /// succeed, it only means the operation will return immediately. /// /// If interest is set in both readability and writability, the two kinds of events might be /// delivered either separately or together. /// /// For example, interest in `Event { key: 7, readable: true, writable: true }` might result in /// a single [`Event`] of the same form, or in two separate [`Event`]s: /// - `Event { key: 7, readable: true, writable: false }` /// - `Event { key: 7, readable: false, writable: true }` /// /// Note that interest in I/O events needs to be re-enabled using /// [`modify()`][`Poller::modify()`] again after an event is delivered if we're interested in /// the next event of the same kind. /// /// Don't forget to [`delete()`][`Poller::delete()`] the file descriptor or socket when it is /// no longer used! /// /// # Errors /// /// This method returns an error in the following situations: /// /// * If `key` equals `usize::MAX` because that key is reserved for internal use. /// * If an error is returned by the syscall. /// /// # Examples /// /// Set interest in all events: /// /// ```no_run /// use polling::{Event, Poller}; /// /// let source = std::net::TcpListener::bind("127.0.0.1:0")?; /// source.set_nonblocking(true)?; /// let key = 7; /// /// let poller = Poller::new()?; /// poller.add(&source, Event::all(key))?; /// # std::io::Result::Ok(()) /// ``` pub fn add(&self, source: impl Source, interest: Event) -> io::Result<()> { if interest.key == NOTIFY_KEY { return Err(io::Error::new( io::ErrorKind::InvalidInput, "the key is not allowed to be `usize::MAX`", )); } self.poller.add(source.raw(), interest) } /// Modifies the interest in a file descriptor or socket. /// /// This method has the same behavior as [`add()`][`Poller::add()`] except it modifies the /// interest of a previously added file descriptor or socket. /// /// To use this method with a file descriptor or socket, you must first add it using /// [`add()`][`Poller::add()`]. /// /// Note that interest in I/O events needs to be re-enabled using /// [`modify()`][`Poller::modify()`] again after an event is delivered if we're interested in /// the next event of the same kind. /// /// # Errors /// /// This method returns an error in the following situations: /// /// * If `key` equals `usize::MAX` because that key is reserved for internal use. /// * If an error is returned by the syscall. /// /// # Examples /// /// To enable interest in all events: /// /// ```no_run /// # use polling::{Event, Poller}; /// # let source = std::net::TcpListener::bind("127.0.0.1:0")?; /// # let key = 7; /// # let poller = Poller::new()?; /// # poller.add(&source, Event::none(key))?; /// poller.modify(&source, Event::all(key))?; /// # std::io::Result::Ok(()) /// ``` /// /// To enable interest in readable events and disable interest in writable events: /// /// ```no_run /// # use polling::{Event, Poller}; /// # let source = std::net::TcpListener::bind("127.0.0.1:0")?; /// # let key = 7; /// # let poller = Poller::new()?; /// # poller.add(&source, Event::none(key))?; /// poller.modify(&source, Event::readable(key))?; /// # std::io::Result::Ok(()) /// ``` /// /// To disable interest in readable events and enable interest in writable events: /// /// ```no_run /// # use polling::{Event, Poller}; /// # let poller = Poller::new()?; /// # let key = 7; /// # let source = std::net::TcpListener::bind("127.0.0.1:0")?; /// # poller.add(&source, Event::none(key))?; /// poller.modify(&source, Event::writable(key))?; /// # std::io::Result::Ok(()) /// ``` /// /// To disable interest in all events: /// /// ```no_run /// # use polling::{Event, Poller}; /// # let source = std::net::TcpListener::bind("127.0.0.1:0")?; /// # let key = 7; /// # let poller = Poller::new()?; /// # poller.add(&source, Event::none(key))?; /// poller.modify(&source, Event::none(key))?; /// # std::io::Result::Ok(()) /// ``` pub fn modify(&self, source: impl Source, interest: Event) -> io::Result<()> { if interest.key == NOTIFY_KEY { return Err(io::Error::new( io::ErrorKind::InvalidInput, "the key is not allowed to be `usize::MAX`", )); } self.poller.modify(source.raw(), interest) } /// Removes a file descriptor or socket from the poller. /// /// Unlike [`add()`][`Poller::add()`], this method only removes the file descriptor or /// socket from the poller without putting it back into blocking mode. /// /// # Examples /// /// ``` /// use polling::{Event, Poller}; /// use std::net::TcpListener; /// /// let socket = TcpListener::bind("127.0.0.1:0")?; /// socket.set_nonblocking(true)?; /// let key = 7; /// /// let poller = Poller::new()?; /// poller.add(&socket, Event::all(key))?; /// poller.delete(&socket)?; /// # std::io::Result::Ok(()) /// ``` pub fn delete(&self, source: impl Source) -> io::Result<()> { self.poller.delete(source.raw()) } /// Waits for at least one I/O event and returns the number of new events. /// /// New events will be appended to `events`. If necessary, make sure to clear the [`Vec`] /// before calling [`wait()`][`Poller::wait()`]! /// /// This method will return with no new events if a notification is delivered by the /// [`notify()`] method, or the timeout is reached. Sometimes it may even return with no events /// spuriously. /// /// Only one thread can wait on I/O. If another thread is already in [`wait()`], concurrent /// calls to this method will return immediately with no new events. /// /// If the operating system is ready to deliver a large number of events at once, this method /// may decide to deliver them in smaller batches. /// /// [`notify()`]: `Poller::notify()` /// [`wait()`]: `Poller::wait()` /// /// # Examples /// /// ``` /// use polling::{Event, Poller}; /// use std::net::TcpListener; /// use std::time::Duration; /// /// let socket = TcpListener::bind("127.0.0.1:0")?; /// socket.set_nonblocking(true)?; /// let key = 7; /// /// let poller = Poller::new()?; /// poller.add(&socket, Event::all(key))?; /// /// let mut events = Vec::new(); /// let n = poller.wait(&mut events, Some(Duration::from_secs(1)))?; /// # std::io::Result::Ok(()) /// ``` pub fn wait(&self, events: &mut Vec, timeout: Option) -> io::Result { log::trace!("Poller::wait(_, {:?})", timeout); if let Ok(mut lock) = self.events.try_lock() { // Wait for I/O events. self.poller.wait(&mut lock, timeout)?; // Clear the notification, if any. self.notified.swap(false, Ordering::SeqCst); // Collect events. let len = events.len(); events.extend(lock.iter().filter(|ev| ev.key != usize::MAX)); Ok(events.len() - len) } else { log::trace!("wait: skipping because another thread is already waiting on I/O"); Ok(0) } } /// Wakes up the current or the following invocation of [`wait()`]. /// /// If no thread is calling [`wait()`] right now, this method will cause the following call /// to wake up immediately. /// /// [`wait()`]: `Poller::wait()` /// /// # Examples /// /// ``` /// use polling::Poller; /// /// let poller = Poller::new()?; /// /// // Notify the poller. /// poller.notify()?; /// /// let mut events = Vec::new(); /// poller.wait(&mut events, None)?; // wakes up immediately /// assert!(events.is_empty()); /// # std::io::Result::Ok(()) /// ``` pub fn notify(&self) -> io::Result<()> { log::trace!("Poller::notify()"); if self .notified .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .is_ok() { self.poller.notify()?; } Ok(()) } } impl fmt::Debug for Poller { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.poller.fmt(f) } } cfg_if! { if #[cfg(unix)] { use std::os::unix::io::{AsRawFd, RawFd}; /// A [`RawFd`] or a reference to a type implementing [`AsRawFd`]. pub trait Source { /// Returns the [`RawFd`] for this I/O object. fn raw(&self) -> RawFd; } impl Source for RawFd { fn raw(&self) -> RawFd { *self } } impl Source for &T { fn raw(&self) -> RawFd { self.as_raw_fd() } } } else if #[cfg(windows)] { use std::os::windows::io::{AsRawSocket, RawSocket}; /// A [`RawSocket`] or a reference to a type implementing [`AsRawSocket`]. pub trait Source { /// Returns the [`RawSocket`] for this I/O object. fn raw(&self) -> RawSocket; } impl Source for RawSocket { fn raw(&self) -> RawSocket { *self } } impl Source for &T { fn raw(&self) -> RawSocket { self.as_raw_socket() } } } } polling-2.2.0/src/poll.rs000064400000000000000000000311160072674642500134260ustar 00000000000000//! Bindings to poll (VxWorks, Fuchsia, other Unix systems). use std::collections::HashMap; use std::convert::TryInto; use std::fmt::{self, Debug, Formatter}; use std::io; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Condvar, Mutex}; use std::time::{Duration, Instant}; // std::os::unix doesn't exist on Fuchsia use libc::c_int as RawFd; use crate::Event; /// Interface to poll. #[derive(Debug)] pub struct Poller { /// File descriptors to poll. fds: Mutex, /// The file descriptor of the read half of the notify pipe. This is also stored as the first /// file descriptor in `fds.poll_fds`. notify_read: RawFd, /// The file descriptor of the write half of the notify pipe. /// /// Data is written to this to wake up the current instance of `wait`, which can occur when the /// user notifies it (in which case `notified` would have been set) or when an operation needs /// to occur (in which case `waiting_operations` would have been incremented). notify_write: RawFd, /// The number of operations (`add`, `modify` or `delete`) that are currently waiting on the /// mutex to become free. When this is nonzero, `wait` must be suspended until it reaches zero /// again. waiting_operations: AtomicUsize, /// Whether `wait` has been notified by the user. notified: AtomicBool, /// The condition variable that gets notified when `waiting_operations` reaches zero or /// `notified` becomes true. /// /// This is used with the `fds` mutex. operations_complete: Condvar, } /// The file descriptors to poll in a `Poller`. #[derive(Debug)] struct Fds { /// The list of `pollfds` taken by poll. /// /// The first file descriptor is always present and is used to notify the poller. It is also /// stored in `notify_read`. poll_fds: Vec, /// The map of each file descriptor to data associated with it. This does not include the file /// descriptors `notify_read` or `notify_write`. fd_data: HashMap, } /// Transparent wrapper around `libc::pollfd`, used to support `Debug` derives without adding the /// `extra_traits` feature of `libc`. #[repr(transparent)] struct PollFd(libc::pollfd); impl Debug for PollFd { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("pollfd") .field("fd", &self.0.fd) .field("events", &self.0.events) .field("revents", &self.0.revents) .finish() } } /// Data associated with a file descriptor in a poller. #[derive(Debug)] struct FdData { /// The index into `poll_fds` this file descriptor is. poll_fds_index: usize, /// The key of the `Event` associated with this file descriptor. key: usize, } impl Poller { /// Creates a new poller. pub fn new() -> io::Result { // Create the notification pipe. let mut notify_pipe = [0; 2]; syscall!(pipe(notify_pipe.as_mut_ptr()))?; // Put the reading side into non-blocking mode. let notify_read_flags = syscall!(fcntl(notify_pipe[0], libc::F_GETFL))?; syscall!(fcntl( notify_pipe[0], libc::F_SETFL, notify_read_flags | libc::O_NONBLOCK ))?; log::trace!( "new: notify_read={}, notify_write={}", notify_pipe[0], notify_pipe[1] ); Ok(Self { fds: Mutex::new(Fds { poll_fds: vec![PollFd(libc::pollfd { fd: notify_pipe[0], events: libc::POLLRDNORM, revents: 0, })], fd_data: HashMap::new(), }), notify_read: notify_pipe[0], notify_write: notify_pipe[1], waiting_operations: AtomicUsize::new(0), operations_complete: Condvar::new(), notified: AtomicBool::new(false), }) } /// Adds a new file descriptor. pub fn add(&self, fd: RawFd, ev: Event) -> io::Result<()> { if fd == self.notify_read || fd == self.notify_write { return Err(io::Error::from(io::ErrorKind::InvalidInput)); } log::trace!( "add: notify_read={}, fd={}, ev={:?}", self.notify_read, fd, ev ); self.modify_fds(|fds| { if fds.fd_data.contains_key(&fd) { return Err(io::Error::from(io::ErrorKind::AlreadyExists)); } let poll_fds_index = fds.poll_fds.len(); fds.fd_data.insert( fd, FdData { poll_fds_index, key: ev.key, }, ); fds.poll_fds.push(PollFd(libc::pollfd { fd, events: poll_events(ev), revents: 0, })); Ok(()) }) } /// Modifies an existing file descriptor. pub fn modify(&self, fd: RawFd, ev: Event) -> io::Result<()> { log::trace!( "modify: notify_read={}, fd={}, ev={:?}", self.notify_read, fd, ev ); self.modify_fds(|fds| { let data = fds.fd_data.get_mut(&fd).ok_or(io::ErrorKind::NotFound)?; data.key = ev.key; let poll_fds_index = data.poll_fds_index; fds.poll_fds[poll_fds_index].0.events = poll_events(ev); Ok(()) }) } /// Deletes a file descriptor. pub fn delete(&self, fd: RawFd) -> io::Result<()> { log::trace!("delete: notify_read={}, fd={}", self.notify_read, fd); self.modify_fds(|fds| { let data = fds.fd_data.remove(&fd).ok_or(io::ErrorKind::NotFound)?; fds.poll_fds.swap_remove(data.poll_fds_index); if let Some(swapped_pollfd) = fds.poll_fds.get(data.poll_fds_index) { fds.fd_data .get_mut(&swapped_pollfd.0.fd) .unwrap() .poll_fds_index = data.poll_fds_index; } Ok(()) }) } /// Waits for I/O events with an optional timeout. pub fn wait(&self, events: &mut Events, timeout: Option) -> io::Result<()> { log::trace!( "wait: notify_read={}, timeout={:?}", self.notify_read, timeout ); let deadline = timeout.map(|t| Instant::now() + t); events.inner.clear(); let mut fds = self.fds.lock().unwrap(); loop { // Complete all current operations. loop { if self.notified.swap(false, Ordering::SeqCst) { // `notify` will have sent a notification in case we were polling. We weren't, // so remove it. return self.pop_notification(); } else if self.waiting_operations.load(Ordering::SeqCst) == 0 { break; } fds = self.operations_complete.wait(fds).unwrap(); } // Perform the poll. let num_events = poll(&mut fds.poll_fds, deadline)?; let notified = fds.poll_fds[0].0.revents != 0; let num_fd_events = if notified { num_events - 1 } else { num_events }; log::trace!( "new events: notify_read={}, num={}", self.notify_read, num_events ); // Read all notifications. if notified { while syscall!(read(self.notify_read, &mut [0; 64] as *mut _ as *mut _, 64)).is_ok() { } } // If the only event that occurred during polling was notification and it wasn't to // exit, another thread is trying to perform an operation on the fds. Continue the // loop. if !self.notified.swap(false, Ordering::SeqCst) && num_fd_events == 0 && notified { continue; } // Store the events if there were any. if num_fd_events > 0 { let fds = &mut *fds; events.inner.reserve(num_fd_events); for fd_data in fds.fd_data.values_mut() { let PollFd(poll_fd) = &mut fds.poll_fds[fd_data.poll_fds_index]; if poll_fd.revents != 0 { // Store event events.inner.push(Event { key: fd_data.key, readable: poll_fd.revents & READ_REVENTS != 0, writable: poll_fd.revents & WRITE_REVENTS != 0, }); // Remove interest poll_fd.events = 0; if events.inner.len() == num_fd_events { break; } } } } break; } Ok(()) } /// Sends a notification to wake up the current or next `wait()` call. pub fn notify(&self) -> io::Result<()> { log::trace!("notify: notify_read={}", self.notify_read); if !self.notified.swap(true, Ordering::SeqCst) { self.notify_inner()?; self.operations_complete.notify_one(); } Ok(()) } /// Perform a modification on `fds`, interrupting the current caller of `wait` if it's running. fn modify_fds(&self, f: impl FnOnce(&mut Fds) -> io::Result<()>) -> io::Result<()> { self.waiting_operations.fetch_add(1, Ordering::SeqCst); // Wake up the current caller of `wait` if there is one. let sent_notification = self.notify_inner().is_ok(); let mut fds = self.fds.lock().unwrap(); // If there was no caller of `wait` our notification was not removed from the pipe. if sent_notification { let _ = self.pop_notification(); } let res = f(&mut *fds); if self.waiting_operations.fetch_sub(1, Ordering::SeqCst) == 1 { self.operations_complete.notify_one(); } res } /// Wake the current thread that is calling `wait`. fn notify_inner(&self) -> io::Result<()> { syscall!(write(self.notify_write, &0_u8 as *const _ as *const _, 1))?; Ok(()) } /// Remove a notification created by `notify_inner`. fn pop_notification(&self) -> io::Result<()> { syscall!(read(self.notify_read, &mut [0; 1] as *mut _ as *mut _, 1))?; Ok(()) } } impl Drop for Poller { fn drop(&mut self) { log::trace!("drop: notify_read={}", self.notify_read); let _ = syscall!(close(self.notify_read)); let _ = syscall!(close(self.notify_write)); } } /// Get the input poll events for the given event. fn poll_events(ev: Event) -> libc::c_short { (if ev.readable { libc::POLLIN | libc::POLLPRI } else { 0 }) | (if ev.writable { libc::POLLOUT | libc::POLLWRBAND } else { 0 }) } /// Returned poll events for reading. const READ_REVENTS: libc::c_short = libc::POLLIN | libc::POLLPRI | libc::POLLHUP | libc::POLLERR; /// Returned poll events for writing. const WRITE_REVENTS: libc::c_short = libc::POLLOUT | libc::POLLWRBAND | libc::POLLHUP | libc::POLLERR; /// A list of reported I/O events. pub struct Events { inner: Vec, } impl Events { /// Creates an empty list. pub fn new() -> Events { Self { inner: Vec::new() } } /// Iterates over I/O events. pub fn iter(&self) -> impl Iterator + '_ { self.inner.iter().copied() } } /// Helper function to call poll. fn poll(fds: &mut [PollFd], deadline: Option) -> io::Result { loop { // Convert the timeout to milliseconds. let timeout_ms = deadline .map(|deadline| { let timeout = deadline.saturating_duration_since(Instant::now()); // Round up to a whole millisecond. let mut ms = timeout.as_millis().try_into().unwrap_or(std::u64::MAX); if Duration::from_millis(ms) < timeout { ms = ms.saturating_add(1); } ms.try_into().unwrap_or(std::i32::MAX) }) .unwrap_or(-1); match syscall!(poll( fds.as_mut_ptr() as *mut libc::pollfd, fds.len() as libc::nfds_t, timeout_ms, )) { Ok(num_events) => break Ok(num_events as usize), // poll returns EAGAIN if we can retry it. Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => continue, Err(e) => return Err(e), } } } polling-2.2.0/src/port.rs000064400000000000000000000127240072674642500134500ustar 00000000000000//! Bindings to event port (illumos, Solaris). use std::io::{self, Read, Write}; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::UnixStream; use std::ptr; use std::time::Duration; use crate::Event; /// Interface to event ports. #[derive(Debug)] pub struct Poller { /// File descriptor for the port instance. port_fd: RawFd, /// Read side of a pipe for consuming notifications. read_stream: UnixStream, /// Write side of a pipe for producing notifications. write_stream: UnixStream, } impl Poller { /// Creates a new poller. pub fn new() -> io::Result { let port_fd = syscall!(port_create())?; let flags = syscall!(fcntl(port_fd, libc::F_GETFD))?; syscall!(fcntl(port_fd, libc::F_SETFD, flags | libc::FD_CLOEXEC))?; // Set up the notification pipe. let (read_stream, write_stream) = UnixStream::pair()?; read_stream.set_nonblocking(true)?; write_stream.set_nonblocking(true)?; let poller = Poller { port_fd, read_stream, write_stream, }; poller.add( poller.read_stream.as_raw_fd(), Event { key: crate::NOTIFY_KEY, readable: true, writable: false, }, )?; Ok(poller) } /// Adds a file descriptor. pub fn add(&self, fd: RawFd, ev: Event) -> io::Result<()> { // File descriptors don't need to be added explicitly, so just modify the interest. self.modify(fd, ev) } /// Modifies an existing file descriptor. pub fn modify(&self, fd: RawFd, ev: Event) -> io::Result<()> { let mut flags = 0; if ev.readable { flags |= libc::POLLIN; } if ev.writable { flags |= libc::POLLOUT; } syscall!(port_associate( self.port_fd, libc::PORT_SOURCE_FD, fd as _, flags as _, ev.key as _, ))?; Ok(()) } /// Deletes a file descriptor. pub fn delete(&self, fd: RawFd) -> io::Result<()> { if let Err(e) = syscall!(port_dissociate( self.port_fd, libc::PORT_SOURCE_FD, fd as usize, )) { match e.raw_os_error().unwrap() { libc::ENOENT => return Ok(()), _ => return Err(e), } } Ok(()) } /// Waits for I/O events with an optional timeout. pub fn wait(&self, events: &mut Events, timeout: Option) -> io::Result<()> { let mut timeout = timeout.map(|t| libc::timespec { tv_sec: t.as_secs() as libc::time_t, tv_nsec: t.subsec_nanos() as libc::c_long, }); let mut nget: u32 = 1; // Wait for I/O events. let res = syscall!(port_getn( self.port_fd, events.list.as_mut_ptr() as *mut libc::port_event, events.list.len() as libc::c_uint, &mut nget as _, match &mut timeout { None => ptr::null_mut(), Some(t) => t, } )); // Event ports sets the return value to -1 and returns ETIME on timer expire. The number of // returned events is stored in nget, but in our case it should always be 0 since we set // nget to 1 initially. let nevents = match res { Err(e) => match e.raw_os_error().unwrap() { libc::ETIME => 0, _ => return Err(e), }, Ok(_) => nget as usize, }; events.len = nevents; // Clear the notification (if received) and re-register interest in it. while (&self.read_stream).read(&mut [0; 64]).is_ok() {} self.modify( self.read_stream.as_raw_fd(), Event { key: crate::NOTIFY_KEY, readable: true, writable: false, }, )?; Ok(()) } /// Sends a notification to wake up the current or next `wait()` call. pub fn notify(&self) -> io::Result<()> { let _ = (&self.write_stream).write(&[1]); Ok(()) } } impl Drop for Poller { fn drop(&mut self) { let _ = self.delete(self.read_stream.as_raw_fd()); let _ = syscall!(close(self.port_fd)); } } /// Poll flags for all possible readability events. fn read_flags() -> libc::c_short { libc::POLLIN | libc::POLLHUP | libc::POLLERR | libc::POLLPRI } /// Poll flags for all possible writability events. fn write_flags() -> libc::c_short { libc::POLLOUT | libc::POLLHUP | libc::POLLERR } /// A list of reported I/O events. pub struct Events { list: Box<[libc::port_event]>, len: usize, } unsafe impl Send for Events {} impl Events { /// Creates an empty list. pub fn new() -> Events { let ev = libc::port_event { portev_events: 0, portev_source: 0, portev_pad: 0, portev_object: 0, portev_user: 0 as _, }; let list = vec![ev; 1000].into_boxed_slice(); let len = 0; Events { list, len } } /// Iterates over I/O events. pub fn iter(&self) -> impl Iterator + '_ { self.list[..self.len].iter().map(|ev| Event { key: ev.portev_user as _, readable: (ev.portev_events & read_flags() as libc::c_int) != 0, writable: (ev.portev_events & write_flags() as libc::c_int) != 0, }) } } polling-2.2.0/src/wepoll.rs000064400000000000000000000146410072674642500137660ustar 00000000000000//! Bindings to wepoll (Windows). use std::convert::TryInto; use std::io; use std::os::windows::io::RawSocket; use std::ptr; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; use wepoll_ffi as we; use winapi::ctypes; use crate::Event; /// Calls a wepoll function and results in `io::Result`. macro_rules! wepoll { ($fn:ident $args:tt) => {{ let res = unsafe { we::$fn $args }; if res == -1 { Err(std::io::Error::last_os_error()) } else { Ok(res) } }}; } /// Interface to wepoll. #[derive(Debug)] pub struct Poller { handle: we::HANDLE, notified: AtomicBool, } unsafe impl Send for Poller {} unsafe impl Sync for Poller {} impl Poller { /// Creates a new poller. pub fn new() -> io::Result { let handle = unsafe { we::epoll_create1(0) }; if handle.is_null() { return Err(io::Error::last_os_error()); } let notified = AtomicBool::new(false); log::trace!("new: handle={:?}", handle); Ok(Poller { handle, notified }) } /// Adds a socket. pub fn add(&self, sock: RawSocket, ev: Event) -> io::Result<()> { log::trace!("add: handle={:?}, sock={}, ev={:?}", self.handle, sock, ev); self.ctl(we::EPOLL_CTL_ADD, sock, Some(ev)) } /// Modifies a socket. pub fn modify(&self, sock: RawSocket, ev: Event) -> io::Result<()> { log::trace!( "modify: handle={:?}, sock={}, ev={:?}", self.handle, sock, ev ); self.ctl(we::EPOLL_CTL_MOD, sock, Some(ev)) } /// Deletes a socket. pub fn delete(&self, sock: RawSocket) -> io::Result<()> { log::trace!("remove: handle={:?}, sock={}", self.handle, sock); self.ctl(we::EPOLL_CTL_DEL, sock, None) } /// Waits for I/O events with an optional timeout. /// /// Returns the number of processed I/O events. /// /// If a notification occurs, this method will return but the notification event will not be /// included in the `events` list nor contribute to the returned count. pub fn wait(&self, events: &mut Events, timeout: Option) -> io::Result<()> { log::trace!("wait: handle={:?}, timeout={:?}", self.handle, timeout); let deadline = timeout.map(|t| Instant::now() + t); loop { // Convert the timeout to milliseconds. let timeout_ms = match deadline.map(|d| d.saturating_duration_since(Instant::now())) { None => -1, Some(t) => { // Round up to a whole millisecond. let mut ms = t.as_millis().try_into().unwrap_or(std::u64::MAX); if Duration::from_millis(ms) < t { ms = ms.saturating_add(1); } ms.try_into().unwrap_or(std::i32::MAX) } }; // Wait for I/O events. events.len = wepoll!(epoll_wait( self.handle, events.list.as_mut_ptr(), events.list.len() as ctypes::c_int, timeout_ms, ))? as usize; log::trace!("new events: handle={:?}, len={}", self.handle, events.len); // Break if there was a notification or at least one event, or if deadline is reached. if self.notified.swap(false, Ordering::SeqCst) || events.len > 0 || timeout_ms == 0 { break; } } Ok(()) } /// Sends a notification to wake up the current or next `wait()` call. pub fn notify(&self) -> io::Result<()> { log::trace!("notify: handle={:?}", self.handle); if self .notified .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .is_ok() { unsafe { // This call errors if a notification has already been posted, but that's okay - we // can just ignore the error. // // The original wepoll does not support notifications triggered this way, which is // why wepoll-sys includes a small patch to support them. winapi::um::ioapiset::PostQueuedCompletionStatus( self.handle as winapi::um::winnt::HANDLE, 0, 0, ptr::null_mut(), ); } } Ok(()) } /// Passes arguments to `epoll_ctl`. fn ctl(&self, op: u32, sock: RawSocket, ev: Option) -> io::Result<()> { let mut ev = ev.map(|ev| { let mut flags = we::EPOLLONESHOT; if ev.readable { flags |= READ_FLAGS; } if ev.writable { flags |= WRITE_FLAGS; } we::epoll_event { events: flags as u32, data: we::epoll_data { u64_: ev.key as u64 }, } }); wepoll!(epoll_ctl( self.handle, op as ctypes::c_int, sock as we::SOCKET, ev.as_mut() .map(|ev| ev as *mut we::epoll_event) .unwrap_or(ptr::null_mut()), ))?; Ok(()) } } impl Drop for Poller { fn drop(&mut self) { log::trace!("drop: handle={:?}", self.handle); unsafe { we::epoll_close(self.handle); } } } /// Wepoll flags for all possible readability events. const READ_FLAGS: u32 = we::EPOLLIN | we::EPOLLRDHUP | we::EPOLLHUP | we::EPOLLERR | we::EPOLLPRI; /// Wepoll flags for all possible writability events. const WRITE_FLAGS: u32 = we::EPOLLOUT | we::EPOLLHUP | we::EPOLLERR; /// A list of reported I/O events. pub struct Events { list: Box<[we::epoll_event]>, len: usize, } unsafe impl Send for Events {} impl Events { /// Creates an empty list. pub fn new() -> Events { let ev = we::epoll_event { events: 0, data: we::epoll_data { u64_: 0 }, }; Events { list: vec![ev; 1000].into_boxed_slice(), len: 0, } } /// Iterates over I/O events. pub fn iter(&self) -> impl Iterator + '_ { self.list[..self.len].iter().map(|ev| Event { key: unsafe { ev.data.u64_ } as usize, readable: (ev.events & READ_FLAGS) != 0, writable: (ev.events & WRITE_FLAGS) != 0, }) } } polling-2.2.0/tests/concurrent_modification.rs000064400000000000000000000031560072674642500177450ustar 00000000000000use std::time::Duration; use std::net::{TcpListener, TcpStream}; use std::thread; use std::io::{self, Write}; use easy_parallel::Parallel; use polling::{Poller, Event}; #[test] fn concurrent_add() -> io::Result<()> { let (reader, mut writer) = tcp_pair()?; let poller = Poller::new()?; let mut events = Vec::new(); Parallel::new() .add(|| { poller.wait(&mut events, None)?; Ok(()) }) .add(|| { thread::sleep(Duration::from_millis(100)); poller.add(&reader, Event::readable(0))?; writer.write_all(&[1])?; Ok(()) }) .run() .into_iter() .collect::>()?; assert_eq!(events, [Event::readable(0)]); Ok(()) } #[test] fn concurrent_modify() -> io::Result<()> { let (reader, mut writer) = tcp_pair()?; let poller = Poller::new()?; poller.add(&reader, Event::none(0))?; let mut events = Vec::new(); Parallel::new() .add(|| { poller.wait(&mut events, None)?; Ok(()) }) .add(|| { thread::sleep(Duration::from_millis(100)); poller.modify(&reader, Event::readable(0))?; writer.write_all(&[1])?; Ok(()) }) .run() .into_iter() .collect::>()?; assert_eq!(events, [Event::readable(0)]); Ok(()) } fn tcp_pair() -> io::Result<(TcpStream, TcpStream)> { let listener = TcpListener::bind("127.0.0.1:0")?; let a = TcpStream::connect(listener.local_addr()?)?; let (b, _) = listener.accept()?; Ok((a, b)) } polling-2.2.0/tests/notify.rs000064400000000000000000000012710072674642500143420ustar 00000000000000use std::io; use std::thread; use std::time::Duration; use easy_parallel::Parallel; use polling::Poller; #[test] fn simple() -> io::Result<()> { let poller = Poller::new()?; let mut events = Vec::new(); for _ in 0..10 { poller.notify()?; poller.wait(&mut events, None)?; } Ok(()) } #[test] fn concurrent() -> io::Result<()> { let poller = Poller::new()?; let mut events = Vec::new(); for _ in 0..2 { Parallel::new() .add(|| { thread::sleep(Duration::from_secs(0)); poller.notify().unwrap(); }) .finish(|| poller.wait(&mut events, None).unwrap()); } Ok(()) } polling-2.2.0/tests/precision.rs000064400000000000000000000033730072674642500150320ustar 00000000000000use std::io; use std::time::{Duration, Instant}; use polling::Poller; #[test] fn below_ms() -> io::Result<()> { let poller = Poller::new()?; let mut events = Vec::new(); let dur = Duration::from_micros(100); let margin = Duration::from_micros(500); let mut lowest = Duration::from_secs(1000); for _ in 0..1_000 { let now = Instant::now(); let n = poller.wait(&mut events, Some(dur))?; let elapsed = now.elapsed(); assert_eq!(n, 0); assert!(elapsed >= dur); lowest = lowest.min(elapsed); } if cfg!(any( target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", )) { assert!(lowest < dur + margin); } Ok(()) } #[test] fn above_ms() -> io::Result<()> { let poller = Poller::new()?; let mut events = Vec::new(); let dur = Duration::from_micros(3_100); let margin = Duration::from_micros(500); let mut lowest = Duration::from_secs(1000); for _ in 0..1_000 { let now = Instant::now(); let n = poller.wait(&mut events, Some(dur))?; let elapsed = now.elapsed(); assert_eq!(n, 0); assert!(elapsed >= dur); lowest = lowest.min(elapsed); } if cfg!(any( target_os = "linux", target_os = "android", target_os = "illumos", target_os = "solaris", target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", )) { assert!(lowest < dur + margin); } Ok(()) } polling-2.2.0/tests/timeout.rs000064400000000000000000000011700072674642500145160ustar 00000000000000use std::io; use std::time::{Duration, Instant}; use polling::Poller; #[test] fn twice() -> io::Result<()> { let poller = Poller::new()?; let mut events = Vec::new(); for _ in 0..2 { let start = Instant::now(); poller.wait(&mut events, Some(Duration::from_secs(1)))?; let elapsed = start.elapsed(); assert!(elapsed >= Duration::from_secs(1)); } Ok(()) } #[test] fn non_blocking() -> io::Result<()> { let poller = Poller::new()?; let mut events = Vec::new(); for _ in 0..100 { poller.wait(&mut events, Some(Duration::from_secs(0)))?; } Ok(()) }