mirror of https://github.com/smol-rs/polling
Initial commit
This commit is contained in:
commit
5b32dd8e82
|
@ -0,0 +1 @@
|
|||
github: stjepang
|
|
@ -0,0 +1,59 @@
|
|||
name: 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.39.0]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set current week of the year in environnement
|
||||
if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macOS')
|
||||
run: echo "::set-env name=CURRENT_WEEK::$(date +%V)"
|
||||
|
||||
- name: Set current week of the year in environnement
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
run: echo "::set-env name=CURRENT_WEEK::$(Get-Date -UFormat %V)"
|
||||
|
||||
- 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
|
||||
if: startsWith(matrix.rust, '1.39.0') == false
|
||||
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
|
||||
if: startsWith(matrix.rust, '1.39.0') == false
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
|
@ -0,0 +1,51 @@
|
|||
name: 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 docker
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt install docker
|
||||
|
||||
- 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: illumos
|
||||
# if: startsWith(matrix.os, 'ubuntu')
|
||||
# run: cross build --target x86_64-unknown-illumos
|
|
@ -0,0 +1,27 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set current week of the year in environnement
|
||||
run: echo "::set-env name=CURRENT_WEEK::$(date +%V)"
|
||||
|
||||
- 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
|
|
@ -0,0 +1,20 @@
|
|||
name: Security audit
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
security_audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set current week of the year in environnement
|
||||
run: echo "::set-env name=CURRENT_WEEK::$(date +%V)"
|
||||
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,3 @@
|
|||
# Version 0.1.0
|
||||
|
||||
- Initial version
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "polling"
|
||||
version = "0.1.0"
|
||||
authors = ["Stjepan Glavina <stjepang@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "Portable interface to epoll, kqueue, and wepoll"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
repository = "https://github.com/stjepang/polling"
|
||||
homepage = "https://github.com/stjepang/polling"
|
||||
documentation = "https://docs.rs/polling"
|
||||
keywords = ["mio", "epoll", "kqueue", "iocp", "wepoll"]
|
||||
categories = ["asynchronous", "network-programming", "os"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "0.1.10"
|
||||
libc = "0.2.74"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
# Patched version of wepoll that can be notified by PostQueuedCompletionStatus.
|
||||
wepoll-sys-stjepang = "1.0.6"
|
||||
winapi = { version = "0.3.9", features = ["ioapiset"] }
|
|
@ -0,0 +1,201 @@
|
|||
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.
|
|
@ -0,0 +1,23 @@
|
|||
Permission 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.
|
|
@ -0,0 +1,72 @@
|
|||
# polling
|
||||
|
||||
[![Build](https://github.com/stjepang/polling/workflows/Build%20and%20test/badge.svg)](
|
||||
https://github.com/stjepang/polling/actions)
|
||||
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](
|
||||
https://github.com/stjepang/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, and wepoll.
|
||||
|
||||
Supported platforms:
|
||||
- [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android, illumos
|
||||
- [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, FreeBSD, NetBSD, OpenBSD,
|
||||
DragonFly BSD
|
||||
- [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
|
||||
use polling::{Event, Poller};
|
||||
use std::net::TcpListener;
|
||||
|
||||
// Create a TCP listener and put the socket in non-blocking mode.
|
||||
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.insert(&socket)?;
|
||||
poller.interest(&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.interest(&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.
|
|
@ -0,0 +1,41 @@
|
|||
use std::io;
|
||||
use std::net::TcpListener;
|
||||
|
||||
use polling::{Event, Poller};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let l1 = TcpListener::bind("127.0.0.1:8001")?;
|
||||
l1.set_nonblocking(true)?;
|
||||
|
||||
let l2 = TcpListener::bind("127.0.0.1:8002")?;
|
||||
l2.set_nonblocking(true)?;
|
||||
|
||||
let poller = Poller::new()?;
|
||||
poller.insert(&l1)?;
|
||||
poller.insert(&l2)?;
|
||||
|
||||
poller.interest(&l1, Event::readable(1))?;
|
||||
poller.interest(&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.interest(&l1, Event::readable(1))?;
|
||||
}
|
||||
2 => {
|
||||
println!("Accept on l2");
|
||||
l2.accept()?;
|
||||
poller.interest(&l2, Event::readable(2))?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
//! Bindings to epoll (Linux, Android, illumos).
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
use std::usize;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
impl Poller {
|
||||
/// Creates a new poller.
|
||||
pub fn new() -> io::Result<Poller> {
|
||||
// According to libuv, `EPOLL_CLOEXEC` is not defined on Android API < 21.
|
||||
// But `EPOLL_CLOEXEC` is an alias for `O_CLOEXEC` on that platform, so we use it instead.
|
||||
#[cfg(target_os = "android")]
|
||||
const CLOEXEC: libc::c_int = libc::O_CLOEXEC;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
const CLOEXEC: libc::c_int = libc::EPOLL_CLOEXEC;
|
||||
|
||||
// Create an epoll instance.
|
||||
let epoll_fd = unsafe {
|
||||
// Check if the `epoll_create1` symbol is available on this platform.
|
||||
let ptr = libc::dlsym(
|
||||
libc::RTLD_DEFAULT,
|
||||
"epoll_create1\0".as_ptr() as *const libc::c_char,
|
||||
);
|
||||
|
||||
if ptr.is_null() {
|
||||
// If not, use `epoll_create` and manually set `CLOEXEC`.
|
||||
let fd = match libc::epoll_create(1024) {
|
||||
-1 => return Err(io::Error::last_os_error()),
|
||||
fd => fd,
|
||||
};
|
||||
let flags = libc::fcntl(fd, libc::F_GETFD);
|
||||
libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC);
|
||||
fd
|
||||
} else {
|
||||
// Use `epoll_create1` with `CLOEXEC`.
|
||||
let epoll_create1 = std::mem::transmute::<
|
||||
*mut libc::c_void,
|
||||
unsafe extern "C" fn(libc::c_int) -> libc::c_int,
|
||||
>(ptr);
|
||||
match epoll_create1(CLOEXEC) {
|
||||
-1 => return Err(io::Error::last_os_error()),
|
||||
fd => fd,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Set up eventfd.
|
||||
let event_fd = syscall!(eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK))?;
|
||||
let poller = Poller { epoll_fd, event_fd };
|
||||
poller.insert(event_fd)?;
|
||||
poller.interest(event_fd, NOTIFY_KEY, true, false)?;
|
||||
|
||||
Ok(poller)
|
||||
}
|
||||
|
||||
/// Inserts a file descriptor.
|
||||
pub fn insert(&self, fd: RawFd) -> io::Result<()> {
|
||||
// Put the file descriptor in non-blocking mode.
|
||||
let flags = syscall!(fcntl(fd, libc::F_GETFL))?;
|
||||
syscall!(fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK))?;
|
||||
|
||||
// Register the file descriptor in epoll.
|
||||
let mut ev = libc::epoll_event {
|
||||
events: 0,
|
||||
u64: 0u64,
|
||||
};
|
||||
syscall!(epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_ADD, fd, &mut ev))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets interest in a read/write event on a file descriptor and associates a key with it.
|
||||
pub fn interest(&self, fd: RawFd, key: usize, read: bool, write: bool) -> io::Result<()> {
|
||||
let mut flags = libc::EPOLLONESHOT;
|
||||
if read {
|
||||
flags |= read_flags();
|
||||
}
|
||||
if write {
|
||||
flags |= write_flags();
|
||||
}
|
||||
|
||||
let mut ev = libc::epoll_event {
|
||||
events: flags as _,
|
||||
u64: key as u64,
|
||||
};
|
||||
syscall!(epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_MOD, fd, &mut ev))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a file descriptor.
|
||||
pub fn remove(&self, fd: RawFd) -> io::Result<()> {
|
||||
syscall!(epoll_ctl(
|
||||
self.epoll_fd,
|
||||
libc::EPOLL_CTL_DEL,
|
||||
fd,
|
||||
ptr::null_mut()
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits for I/O events with an optional timeout.
|
||||
///
|
||||
/// Returns the number of processed I/O events.
|
||||
///
|
||||
/// If a notification occurs, the notification event will be included in the `events` list
|
||||
/// identifiable by key `usize::MAX`.
|
||||
pub fn wait(&self, events: &mut Events, timeout: Option<Duration>) -> io::Result<usize> {
|
||||
// Convert the timeout to milliseconds.
|
||||
let timeout_ms = timeout
|
||||
.map(|t| {
|
||||
if t == Duration::from_millis(0) {
|
||||
t
|
||||
} else {
|
||||
// Non-zero duration must be at least 1ms.
|
||||
t.max(Duration::from_millis(1))
|
||||
}
|
||||
})
|
||||
.and_then(|t| t.as_millis().try_into().ok())
|
||||
.unwrap_or(-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;
|
||||
|
||||
// Clear the notification (if received) and re-register interest in it.
|
||||
let mut buf = [0u8; 8];
|
||||
let _ = syscall!(read(
|
||||
self.event_fd,
|
||||
&mut buf[0] as *mut u8 as *mut libc::c_void,
|
||||
buf.len()
|
||||
));
|
||||
self.interest(self.event_fd, NOTIFY_KEY, true, false)?;
|
||||
|
||||
Ok(events.len)
|
||||
}
|
||||
|
||||
/// Sends a notification to wake up the current or next `wait()` call.
|
||||
pub fn notify(&self) -> io::Result<()> {
|
||||
let buf: [u8; 8] = 1u64.to_ne_bytes();
|
||||
let _ = syscall!(write(
|
||||
self.event_fd,
|
||||
&buf[0] as *const u8 as *const libc::c_void,
|
||||
buf.len()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Poller {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.remove(self.event_fd);
|
||||
let _ = syscall!(close(self.event_fd));
|
||||
let _ = syscall!(close(self.epoll_fd));
|
||||
}
|
||||
}
|
||||
|
||||
/// Key associated with the eventfd for producing notifications.
|
||||
const NOTIFY_KEY: usize = usize::MAX;
|
||||
|
||||
/// 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<Item = Event> + '_ {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
//! 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 std::usize;
|
||||
|
||||
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<Poller> {
|
||||
// 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.interest(
|
||||
poller.read_stream.as_raw_fd(),
|
||||
Event {
|
||||
key: NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(poller)
|
||||
}
|
||||
|
||||
/// Inserts a file descriptor.
|
||||
pub fn insert(&self, fd: RawFd) -> io::Result<()> {
|
||||
// Put the file descriptor in non-blocking mode.
|
||||
let flags = syscall!(fcntl(fd, libc::F_GETFL))?;
|
||||
syscall!(fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets interest in a read/write event on a file descriptor and associates a key with it.
|
||||
pub fn interest(&self, fd: RawFd, ev: Event) -> io::Result<()> {
|
||||
let mut read_flags = libc::EV_ONESHOT | libc::EV_RECEIPT;
|
||||
let mut write_flags = libc::EV_ONESHOT | libc::EV_RECEIPT;
|
||||
if ev.readable {
|
||||
read_flags |= libc::EV_ADD;
|
||||
} else {
|
||||
read_flags |= libc::EV_DELETE;
|
||||
}
|
||||
if ev.writable {
|
||||
write_flags |= libc::EV_ADD;
|
||||
} else {
|
||||
write_flags |= libc::EV_DELETE;
|
||||
}
|
||||
|
||||
// A list of changes for kqueue.
|
||||
let changelist = [
|
||||
libc::kevent {
|
||||
ident: fd as _,
|
||||
filter: libc::EVFILT_READ,
|
||||
flags: read_flags,
|
||||
fflags: 0,
|
||||
data: 0,
|
||||
udata: ev.key as _,
|
||||
},
|
||||
libc::kevent {
|
||||
ident: fd as _,
|
||||
filter: libc::EVFILT_WRITE,
|
||||
flags: write_flags,
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Removes a file descriptor.
|
||||
pub fn remove(&self, fd: RawFd) -> io::Result<()> {
|
||||
// A list of changes for kqueue.
|
||||
let changelist = [
|
||||
libc::kevent {
|
||||
ident: fd as _,
|
||||
filter: libc::EVFILT_READ,
|
||||
flags: libc::EV_DELETE | libc::EV_RECEIPT,
|
||||
fflags: 0,
|
||||
data: 0,
|
||||
udata: 0 as _,
|
||||
},
|
||||
libc::kevent {
|
||||
ident: fd as _,
|
||||
filter: libc::EVFILT_WRITE,
|
||||
flags: libc::EV_DELETE | libc::EV_RECEIPT,
|
||||
fflags: 0,
|
||||
data: 0,
|
||||
udata: 0 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 {
|
||||
if (ev.flags & libc::EV_ERROR) != 0 && ev.data != 0 && ev.data != libc::ENOENT as _ {
|
||||
return Err(io::Error::from_raw_os_error(ev.data as _));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits for I/O events with an optional timeout.
|
||||
///
|
||||
/// Returns the number of processed I/O events.
|
||||
///
|
||||
/// If a notification occurs, the notification event will be included in the `events` list
|
||||
/// identifiable by key `usize::MAX`.
|
||||
pub fn wait(&self, events: &mut Events, timeout: Option<Duration>) -> io::Result<usize> {
|
||||
// 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;
|
||||
|
||||
// Clear the notification (if received) and re-register interest in it.
|
||||
while (&self.read_stream).read(&mut [0; 64]).is_ok() {}
|
||||
self.interest(
|
||||
self.read_stream.as_raw_fd(),
|
||||
Event {
|
||||
key: NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(events.len)
|
||||
}
|
||||
|
||||
/// 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.remove(self.read_stream.as_raw_fd());
|
||||
let _ = syscall!(close(self.kqueue_fd));
|
||||
}
|
||||
}
|
||||
|
||||
/// Key associated with the pipe for producing notifications.
|
||||
const NOTIFY_KEY: usize = usize::MAX;
|
||||
|
||||
/// 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<Item = Event> + '_ {
|
||||
// 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),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,410 @@
|
|||
//! Portable interface to epoll, kqueue, and wepoll.
|
||||
//!
|
||||
//! Supported platforms:
|
||||
//! - [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android, illumos
|
||||
//! - [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, FreeBSD, NetBSD, OpenBSD,
|
||||
//! DragonFly BSD
|
||||
//! - [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
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use polling::{Event, Poller};
|
||||
//! use std::net::TcpListener;
|
||||
//!
|
||||
//! // Create a TCP listener and put the socket in non-blocking mode.
|
||||
//! 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.insert(&socket)?;
|
||||
//! poller.interest(&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.interest(&socket, Event::readable(key))?;
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! # std::io::Result::Ok(())
|
||||
//! ```
|
||||
|
||||
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
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", target_os = "illumos"))] {
|
||||
mod epoll;
|
||||
use epoll 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(target_os = "windows")] {
|
||||
mod wepoll;
|
||||
use wepoll as sys;
|
||||
} else {
|
||||
compile_error!("polling does not support this target OS");
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates that a file descriptor or socket can read or write without blocking.
|
||||
#[derive(Debug)]
|
||||
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: true,
|
||||
writable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits for I/O events.
|
||||
pub struct Poller {
|
||||
poller: sys::Poller,
|
||||
events: Mutex<sys::Events>,
|
||||
}
|
||||
|
||||
impl Poller {
|
||||
/// Creates a new poller.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use polling::Poller;
|
||||
///
|
||||
/// let poller = Poller::new()?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn new() -> io::Result<Poller> {
|
||||
let poller = sys::Poller::new()?;
|
||||
let events = Mutex::new(sys::Events::new());
|
||||
Ok(Poller { poller, events })
|
||||
}
|
||||
|
||||
/// Inserts a file descriptor or socket into the poller.
|
||||
///
|
||||
/// Before setting interest in readability or writability, the file descriptor or socket must
|
||||
/// be inserted into the poller.
|
||||
///
|
||||
/// Don't forget to [remove][`Poller::remove()`] it when it is no longer used!
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use polling::Poller;
|
||||
/// use std::net::TcpListener;
|
||||
///
|
||||
/// let poller = Poller::new()?;
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
///
|
||||
/// poller.insert(&socket)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn insert(&self, source: impl Source) -> io::Result<()> {
|
||||
self.poller.insert(source.raw())
|
||||
}
|
||||
|
||||
/// Enables or disables interest in a file descriptor or socket.
|
||||
///
|
||||
/// 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 }`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method returns an error in the following situations:
|
||||
///
|
||||
/// * If `source` was not [inserted][`Poller::interest()`] into the poller.
|
||||
/// * 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 poller = Poller::new()?;
|
||||
/// # let key = 7;
|
||||
/// # let source = std::net::TcpListener::bind("127.0.0.1:0")?;
|
||||
/// poller.interest(&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 poller = Poller::new()?;
|
||||
/// # let key = 7;
|
||||
/// # let source = std::net::TcpListener::bind("127.0.0.1:0")?;
|
||||
/// poller.interest(&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.interest(&source, Event::writable(key))?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
///
|
||||
/// To disable interest in all 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.interest(&source, Event::none(key))?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn interest(&self, source: impl Source, event: Event) -> io::Result<()> {
|
||||
if event.key == usize::MAX {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"the key cannot be `usize::MAX`",
|
||||
))
|
||||
} else {
|
||||
self.poller.interest(source.raw(), event)
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a file descriptor or socket from the poller.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use polling::Poller;
|
||||
/// use std::net::TcpListener;
|
||||
///
|
||||
/// let poller = Poller::new()?;
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
///
|
||||
/// poller.insert(&socket)?;
|
||||
/// poller.remove(&socket)?;
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn remove(&self, source: impl Source) -> io::Result<()> {
|
||||
self.poller.remove(source.raw())
|
||||
}
|
||||
|
||||
/// Waits for at least one I/O event and returns the number of new events.
|
||||
///
|
||||
/// New events will be appended to `events`.
|
||||
///
|
||||
/// This call will return with no new events if a notification is delivered by the [`notify()`]
|
||||
/// method, or the timeout is reached.
|
||||
///
|
||||
/// 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::Poller;
|
||||
/// use std::net::TcpListener;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let poller = Poller::new()?;
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
/// poller.insert(&socket)?;
|
||||
///
|
||||
/// 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<Event>, timeout: Option<Duration>) -> io::Result<usize> {
|
||||
if let Ok(mut lock) = self.events.try_lock() {
|
||||
let n = self.poller.wait(&mut lock, timeout)?;
|
||||
events.extend(lock.iter().filter(|ev| ev.key != usize::MAX));
|
||||
Ok(n)
|
||||
} else {
|
||||
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<()> {
|
||||
self.poller.notify()
|
||||
}
|
||||
}
|
||||
|
||||
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<T: AsRawFd> 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<T: AsRawSocket> Source for &T {
|
||||
fn raw(&self) -> RawSocket {
|
||||
self.as_raw_fd()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
//! Bindings to wepoll (Windows).
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::os::windows::io::RawSocket;
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
|
||||
use wepoll_sys_stjepang as we;
|
||||
use winapi::um::winsock2;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
unsafe impl Send for Poller {}
|
||||
unsafe impl Sync for Poller {}
|
||||
|
||||
impl Poller {
|
||||
/// Creates a new poller.
|
||||
pub fn new() -> io::Result<Poller> {
|
||||
let handle = unsafe { we::epoll_create1(0) };
|
||||
if handle.is_null() {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(Poller { handle })
|
||||
}
|
||||
|
||||
/// Inserts a socket.
|
||||
pub fn insert(&self, sock: RawSocket) -> io::Result<()> {
|
||||
// Put the socket in non-blocking mode.
|
||||
unsafe {
|
||||
let mut nonblocking = true as libc::c_ulong;
|
||||
let res = winsock2::ioctlsocket(
|
||||
sock as winsock2::SOCKET,
|
||||
winsock2::FIONBIO,
|
||||
&mut nonblocking,
|
||||
);
|
||||
if res != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
// Register the socket in wepoll.
|
||||
let mut ev = we::epoll_event {
|
||||
events: 0,
|
||||
data: we::epoll_data { u64: 0u64 },
|
||||
};
|
||||
wepoll!(epoll_ctl(
|
||||
self.handle,
|
||||
we::EPOLL_CTL_ADD as libc::c_int,
|
||||
sock as we::SOCKET,
|
||||
&mut ev,
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets interest in a read/write event on a socket and associates a key with it.
|
||||
pub fn interest(&self, sock: RawSocket, key: usize, read: bool, write: bool) -> io::Result<()> {
|
||||
let mut flags = we::EPOLLONESHOT;
|
||||
if read {
|
||||
flags |= READ_FLAGS;
|
||||
}
|
||||
if write {
|
||||
flags |= WRITE_FLAGS;
|
||||
}
|
||||
|
||||
let mut ev = we::epoll_event {
|
||||
events: flags as u32,
|
||||
data: we::epoll_data { u64: key as u64 },
|
||||
};
|
||||
wepoll!(epoll_ctl(
|
||||
self.handle,
|
||||
we::EPOLL_CTL_MOD as libc::c_int,
|
||||
sock as we::SOCKET,
|
||||
&mut ev,
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a socket.
|
||||
pub fn remove(&self, sock: RawSocket) -> io::Result<()> {
|
||||
wepoll!(epoll_ctl(
|
||||
self.handle,
|
||||
we::EPOLL_CTL_DEL as libc::c_int,
|
||||
sock as we::SOCKET,
|
||||
ptr::null_mut(),
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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<Duration>) -> io::Result<usize> {
|
||||
// Convert the timeout to milliseconds.
|
||||
let timeout_ms = match timeout {
|
||||
None => -1,
|
||||
Some(t) => {
|
||||
if t == Duration::from_millis(0) {
|
||||
0
|
||||
} else {
|
||||
// Non-zero duration must be at least 1ms.
|
||||
t.max(Duration::from_millis(1))
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.unwrap_or(libc::c_int::max_value())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for I/O events.
|
||||
events.len = wepoll!(epoll_wait(
|
||||
self.handle,
|
||||
events.list.as_mut_ptr(),
|
||||
events.list.len() as libc::c_int,
|
||||
timeout_ms,
|
||||
))? as usize;
|
||||
|
||||
Ok(events.len)
|
||||
}
|
||||
|
||||
/// Sends a notification to wake up the current or next `wait()` call.
|
||||
pub fn notify(&self) -> io::Result<()> {
|
||||
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
|
||||
// this crate depends on a patched version of wepoll, wepoll-sys-stjepang.
|
||||
winapi::um::ioapiset::PostQueuedCompletionStatus(
|
||||
self.handle as winapi::um::winnt::HANDLE,
|
||||
0,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Poller {
|
||||
fn drop(&mut self) {
|
||||
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<Item = Event> + '_ {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue