mirror of https://github.com/smol-rs/polling
Compare commits
199 Commits
Author | SHA1 | Date |
---|---|---|
John Nunley | cf2d60efca | |
John Nunley | 0b4afcaf0a | |
John Nunley | eb9d92a2e0 | |
Nikolay Arhipov | 9e46c8455c | |
John Nunley | 1c16a1e4af | |
irvingouj @ Devolutions | e25b3b4e4c | |
John Nunley | 50454d1cea | |
John Nunley | 634a77c264 | |
John Nunley | 4d64fdc572 | |
John Nunley | 77b4ed1156 | |
John Nunley | ac7fbcae31 | |
John Nunley | 24e3691794 | |
John Nunley | 62430fd56e | |
John Nunley | ae484a0a12 | |
irvingouj @ Devolutions | cf25dd85f8 | |
John Nunley | 6125508c93 | |
John Nunley | ea5a38a500 | |
John Nunley | 1f13664bbb | |
Taiki Endo | 0c794fce50 | |
Taiki Endo | 94c5ebf78b | |
Taiki Endo | e956a8ad64 | |
Taiki Endo | 078c478346 | |
John Nunley | b57a7c32a2 | |
dependabot[bot] | 08a316e1fc | |
John Nunley | 8087787ab2 | |
John Nunley | b9ab821df1 | |
Uli Schlachter | 0575cbd4bc | |
Taiki Endo | 37a1d4ecd2 | |
Taiki Endo | a559165acd | |
tison | ceb88a46c4 | |
John Nunley | d9a65fdd73 | |
Al Hoang | 99a32b7607 | |
John Nunley | 9e143a38e1 | |
John Nunley | 45ebe3b904 | |
David Hotham | 254577da8d | |
Taiki Endo | 8c99506375 | |
John Nunley | 256542375c | |
John Nunley | 90c661f5e1 | |
John Nunley | 7718565d11 | |
Alex Touchet | 792618094c | |
Taiki Endo | 9a3fe18981 | |
tison | d8595b56a5 | |
John Nunley | c7cc91a1f1 | |
John Nunley | 2c279b871c | |
John Nunley | a521cd2c29 | |
John Nunley | e42664d57e | |
John Nunley | c6a0890627 | |
ivmarkov | 6d13def8ab | |
ivmarkov | 26afefbbbc | |
John Nunley | 49152081d0 | |
ivmarkov | 53793382a7 | |
John Nunley | 6eb7679aa3 | |
John Nunley | c86c3894c1 | |
dependabot[bot] | ea5946c453 | |
Taiki Endo | d41c05447a | |
John Nunley | a8eb2dfc48 | |
John Nunley | 7a1fd31944 | |
John Nunley | e2129ea879 | |
John Nunley | 5df378f811 | |
Ivan Zvonimir Horvat | e161698e6c | |
Taiki Endo | 5e4410c937 | |
John Nunley | 8d8d2efcc2 | |
John Nunley | d3a171b88b | |
John Nunley | 75cff30584 | |
Taiki Endo | 1e966a0848 | |
John Nunley | 6857a165aa | |
John Nunley | 8f6d039b26 | |
Taiki Endo | 1e101c500f | |
John Nunley | 0f38ed35ea | |
John Nunley | e340458d3a | |
John Nunley | e10c7e8da1 | |
John Nunley | f48f2c1a5a | |
John Nunley | 24900fb662 | |
Taiki Endo | e85331c437 | |
Taiki Endo | 1e4467b1be | |
Taiki Endo | d443196f64 | |
John Nunley | a5aae98805 | |
dependabot[bot] | 914aa48d67 | |
Taiki Endo | 900b00c061 | |
Taiki Endo | ff86c54dfd | |
Taiki Endo | 9b67232ea4 | |
Taiki Endo | 3bc6310121 | |
John Nunley | 27f23a9384 | |
John Nunley | 729b5ee071 | |
John Nunley | dc4c5b4ec0 | |
John Nunley | 181acc67d0 | |
Taiki Endo | bc56d1fb38 | |
Taiki Endo | 1a3f61da89 | |
Taiki Endo | 5e9bedbae8 | |
Taiki Endo | 14e1488537 | |
Taiki Endo | 341ca612b8 | |
John Nunley | 97e6ecd0d0 | |
Taiki Endo | f0afd7788e | |
Alan Somers | cebf394ca6 | |
Wesley Wiser | 5343302970 | |
John Nunley | 00e7eefc4d | |
Taiki Endo | 0b45549097 | |
Taiki Endo | 1d54c93f9d | |
Taiki Endo | 6f459f89a9 | |
Taiki Endo | e5fe94732c | |
Taiki Endo | 76ee52a68c | |
John Nunley | 8982599ddf | |
Taiki Endo | ca52490c9d | |
Taiki Endo | bf6cbcc31c | |
Taiki Endo | de312d15b6 | |
Taiki Endo | e0daa5b327 | |
Taiki Endo | d32e79b5dd | |
Taiki Endo | b9cce6645c | |
John Nunley | 323473ec1a | |
Taiki Endo | 61c2179a38 | |
Taiki Endo | 989c515fa2 | |
Taiki Endo | 95c8207ac1 | |
Taiki Endo | 6b219eb0d7 | |
Taiki Endo | 055e2711ae | |
Taiki Endo | a12be08c97 | |
Taiki Endo | 191e91655e | |
Taiki Endo | dec94cb423 | |
Taiki Endo | 89ce9bafaa | |
Taiki Endo | 4ac15ff339 | |
Kestrer | e902924621 | |
Kestrer | 597b6aed86 | |
Kestrer | bbce346140 | |
Kestrer | 1930801c40 | |
Kestrer | ee13911717 | |
Kestrer | 9c257ccef6 | |
Zeeshan Ali | 952fccb2f5 | |
Zeeshan Ali | d13366703a | |
Philip Degarmo | 784f9c108c | |
Kestrer | 6d2ccc4210 | |
Kestrer | c174c9b7bb | |
Kestrer | e0c0032cc0 | |
Philip Degarmo | 88cce716c9 | |
Philip Degarmo | 2295ca07b8 | |
Taiki Endo | 1019f90247 | |
Taiki Endo | fba6877b3c | |
Taiki Endo | 38441655db | |
Taiki Endo | a11153d7b1 | |
Taiki Endo | 94adda1433 | |
Taiki Endo | d963b53f0f | |
Taiki Endo | 458d5c9101 | |
Koxiaet | a04a265f9b | |
Taiki Endo | 2e27830522 | |
Taiki Endo | 99d9e20ba0 | |
Taiki Endo | 5aa15b58b0 | |
Taiki Endo | 84a3453537 | |
Taiki Endo | 4e32cca7d3 | |
Taiki Endo | c009653b99 | |
Taiki Endo | 14c8d34655 | |
Taiki Endo | 995f1b1091 | |
Koxiaet | 20c1e19c46 | |
Koxiaet | dd15b4cd8a | |
Koxiaet | e0789a8ee0 | |
Koxiaet | 0f2f6ed15a | |
Koxiaet | 2fc0831d40 | |
Stjepan Glavina | 4df98de5ae | |
Stjepan Glavina | 30fcade0e4 | |
Mike Zeller | 6f778cddf1 | |
Stjepan Glavina | 9c7e8061c5 | |
Stjepan Glavina | 3a3020bb5c | |
Stjepan Glavina | f4ee76fb32 | |
Stjepan Glavina | 421bfc635d | |
Stjepan Glavina | 112da8b4e5 | |
Stjepan Glavina | 7d1a4e3fe3 | |
Stjepan Glavina | e8023dcb58 | |
Stjepan Glavina | 5bd5cd226c | |
Stjepan Glavina | 0bc0553217 | |
Stjepan Glavina | b562ab84ea | |
Stjepan Glavina | b25847f0ed | |
Stjepan Glavina | 89ef82d11d | |
Stjepan Glavina | a3c748bcd0 | |
Stjepan Glavina | 8b656241d5 | |
Stjepan Glavina | d4667889b4 | |
Stjepan Glavina | 0a299cf060 | |
Stjepan Glavina | 35add590ce | |
Yorick Peterse | 29791dec13 | |
Yorick Peterse | ba05307af1 | |
Yorick Peterse | 4e5c3ce836 | |
Stjepan Glavina | 5309d571b2 | |
Stjepan Glavina | 272bb11eaf | |
Stjepan Glavina | 8c7ce8fb5b | |
Stjepan Glavina | 8a77220000 | |
Jayce Fayne | 2ecb5564e8 | |
Stjepan Glavina | 8e35897e53 | |
Stjepan Glavina | c2e3322b1e | |
oblique | f7f67e8745 | |
oblique | 62103c1ef0 | |
Stjepan Glavina | 4fc3040bff | |
Stjepan Glavina | a954600d6a | |
Stjepan Glavina | 46634fc32a | |
Stjepan Glavina | d8c32a0140 | |
Stjepan Glavina | 7738ca0b25 | |
Stjepan Glavina | 1992605564 | |
Stjepan Glavina | b05877c3ec | |
Stjepan Glavina | 8c2f5e4169 | |
Stjepan Glavina | 6d4724ba35 | |
Stjepan Glavina | a8e3673939 | |
Dan Gohman | d22ed8fd7b | |
Stjepan Glavina | f03bb7acf0 | |
Stjepan Glavina | bc3dc1ec6e |
|
@ -0,0 +1,61 @@
|
|||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master')
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master'
|
||||
env:
|
||||
CARGO_INCREMENTAL: '0'
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: 'true'
|
||||
CARGO_NET_RETRY: '10'
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: 'sparse'
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_BACKTRACE: '1'
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
RUSTFLAGS: -D warnings
|
||||
RUSTUP_MAX_RETRIES: '10'
|
||||
|
||||
freebsd_task:
|
||||
name: test ($TARGET)
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-2
|
||||
matrix:
|
||||
- env:
|
||||
TARGET: x86_64-unknown-freebsd
|
||||
- env:
|
||||
TARGET: i686-unknown-freebsd
|
||||
setup_script:
|
||||
- pkg install -y git
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable --target $TARGET
|
||||
test_script:
|
||||
# https://github.com/cirruslabs/cirrus-ci-docs/issues/483
|
||||
- sudo sysctl net.inet.tcp.blackhole=0
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --target $TARGET
|
||||
|
||||
netbsd_task:
|
||||
name: test ($TARGET)
|
||||
compute_engine_instance:
|
||||
image_project: pg-ci-images
|
||||
# https://github.com/anarazel/pg-vm-images/blob/main/packer/netbsd.pkrvars.hcl
|
||||
image: family/pg-ci-netbsd-vanilla-9-3
|
||||
platform: netbsd
|
||||
env:
|
||||
TARGET: x86_64-unknown-netbsd
|
||||
setup_script:
|
||||
- pkgin -y install git
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test
|
||||
|
||||
openbsd_task:
|
||||
name: test ($TARGET)
|
||||
compute_engine_instance:
|
||||
image_project: pg-ci-images
|
||||
# https://github.com/anarazel/pg-vm-images/blob/main/packer/openbsd.pkrvars.hcl
|
||||
image: family/pg-ci-openbsd-vanilla-7-2
|
||||
platform: openbsd
|
||||
env:
|
||||
TARGET: x86_64-unknown-openbsd
|
||||
setup_script:
|
||||
# OpenBSD is tier 3 target, so install rust from package manager instead of rustup
|
||||
- pkg_add git rust
|
||||
test_script:
|
||||
- cargo test
|
|
@ -1 +0,0 @@
|
|||
github: stjepang
|
|
@ -0,0 +1,9 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: ''
|
||||
labels: []
|
|
@ -1,59 +0,0 @@
|
|||
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,187 @@
|
|||
name: CI
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '0 2 * * 0'
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_BACKTRACE: 1
|
||||
RUSTFLAGS: -D warnings
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
rust: [nightly, beta, stable]
|
||||
include:
|
||||
- os: windows-latest
|
||||
rust: nightly-x86_64-pc-windows-gnu
|
||||
- os: windows-latest
|
||||
rust: nightly-i686-pc-windows-msvc
|
||||
- os: windows-latest
|
||||
rust: nightly-i686-pc-windows-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
# --no-self-update is necessary because the windows environment cannot self-update rustup.exe.
|
||||
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
- run: cargo build --all --all-features --all-targets
|
||||
- run: cargo test
|
||||
- run: cargo test
|
||||
env:
|
||||
# Note: This cfg is intended to make it easy for polling developers to test
|
||||
# the backend that uses poll, and is not a public API.
|
||||
RUSTFLAGS: ${{ env.RUSTFLAGS }} --cfg polling_test_poll_backend
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
- run: cargo test
|
||||
env:
|
||||
# Note: This cfg is intended to make it easy for polling developers to test
|
||||
# the backend that uses pipes, and is not a public API.
|
||||
RUSTFLAGS: ${{ env.RUSTFLAGS }} --cfg polling_test_epoll_pipe
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
- run: cargo hack build --feature-powerset --no-dev-deps
|
||||
# TODO: broken due to https://github.com/rust-lang/rust/pull/119026.
|
||||
# - name: Check selected Tier 3 targets
|
||||
# if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
# run: cargo check -Z build-std --target=riscv32imc-esp-espidf
|
||||
- name: Clone async-io
|
||||
run: git clone https://github.com/smol-rs/async-io.git
|
||||
# The async-io Cargo.toml already has a patch section at the bottom, so we
|
||||
# can just add this.
|
||||
- name: Patch polling
|
||||
run: echo 'polling = { path = ".." }' >> async-io/Cargo.toml
|
||||
- name: Test async-io
|
||||
run: cargo test --manifest-path=async-io/Cargo.toml
|
||||
|
||||
cross:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
rust: [nightly, stable]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
|
||||
- name: Install cross
|
||||
uses: taiki-e/install-action@cross
|
||||
- name: Add rust-src
|
||||
if: startsWith(matrix.rust, 'nightly')
|
||||
run: rustup component add rust-src
|
||||
# We don't test BSDs, since we already test them in Cirrus.
|
||||
- name: Android
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: cross test --target arm-linux-androideabi
|
||||
- name: iOS
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
run: |
|
||||
rustup target add aarch64-apple-ios
|
||||
cross build --target aarch64-apple-ios
|
||||
- name: Linux x32
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
rustup target add x86_64-unknown-linux-gnux32
|
||||
cross check --target x86_64-unknown-linux-gnux32
|
||||
- name: Fuchsia
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
rustup target add x86_64-unknown-fuchsia
|
||||
cargo build --target x86_64-unknown-fuchsia
|
||||
- name: illumos
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
rustup target add x86_64-unknown-illumos
|
||||
cargo build --target x86_64-unknown-illumos
|
||||
- name: Redox
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
rustup target add x86_64-unknown-redox
|
||||
cargo check --target x86_64-unknown-redox
|
||||
- name: HermitOS
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: cargo check -Z build-std --target x86_64-unknown-hermit
|
||||
- name: Check haiku
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: cargo check -Z build-std --target x86_64-unknown-haiku
|
||||
- name: Check vita
|
||||
if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest'
|
||||
run: cargo check -Z build-std --target armv7-sony-vita-newlibeabihf
|
||||
|
||||
wine:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: x86_64-pc-windows-gnu
|
||||
- run: cargo test --target x86_64-pc-windows-gnu
|
||||
|
||||
msrv:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
- run: cargo hack build --no-dev-deps --rust-version
|
||||
- run: cargo hack build --no-dev-deps --rust-version --target x86_64-unknown-freebsd
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
- run: cargo hack build --no-dev-deps --rust-version --target x86_64-unknown-netbsd
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- run: cargo clippy --all-features --all-targets
|
||||
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- run: cargo fmt --all --check
|
||||
|
||||
security_audit:
|
||||
permissions:
|
||||
checks: write
|
||||
contents: read
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# https://github.com/rustsec/audit-check/issues/2
|
||||
- uses: rustsec/audit-check@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,51 +0,0 @@
|
|||
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
|
|
@ -1,27 +0,0 @@
|
|||
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,22 @@
|
|||
name: Release
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.*
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
if: github.repository_owner == 'smol-rs'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: taiki-e/create-gh-release-action@v1
|
||||
with:
|
||||
changelog: CHANGELOG.md
|
||||
branch: master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,20 +0,0 @@
|
|||
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 }}
|
162
CHANGELOG.md
162
CHANGELOG.md
|
@ -1,3 +1,165 @@
|
|||
# Version 3.7.0
|
||||
|
||||
- Add support for the PS Vita as a platform. (#160)
|
||||
|
||||
# Version 3.6.0
|
||||
|
||||
- Add an `is_err` method to `Event` to tell when an error has occurred. (#189)
|
||||
- Deprecate the `is_connect_failed` function. (#189)
|
||||
- Add support for HermitOS to `polling`. (#194)
|
||||
|
||||
# Version 3.5.0
|
||||
|
||||
- Use the `epoll` backend when RedoxOS is enabled. (#190)
|
||||
|
||||
# Version 3.4.0
|
||||
|
||||
- Add the ability to identify whether socket connection has failed. (#185)
|
||||
- On BSD, add the ability to wait on a process by its PID. Previously, it was
|
||||
only possible to wait on a process by a `Child` object. (#180)
|
||||
- On ESP-IDF, annotate `eventfd` initialization failures with a message
|
||||
indicating the source of those failures. (#186)
|
||||
|
||||
# Version 3.3.2
|
||||
|
||||
- When AFD fails to initialize, the resulting error now references
|
||||
the underlying system error. (#174)
|
||||
|
||||
# Version 3.3.1
|
||||
|
||||
- Bump `windows-sys` to v0.52.0. (#169)
|
||||
|
||||
# Version 3.3.0
|
||||
|
||||
- Automatically restarts polling when `ErrorKind::Interrupted` is returned, rather than relying on the user to handle it. (#164)
|
||||
- Fix bad link in documentation for `Poller::wait()`. (#163)
|
||||
|
||||
# Version 3.2.0
|
||||
|
||||
- The `kqueue` backend previously allowed the following operations that other backends forbid. Now these operations result in an error: (#153)
|
||||
- Inserting a source that was already inserted.
|
||||
- Modifying/deleting a source that was not already inserted.
|
||||
- Add support for Haiku OS. (#154)
|
||||
|
||||
# Version 3.1.0
|
||||
|
||||
- Add an `Event::new()` constructor to simplify creating `Event`s. (#149)
|
||||
|
||||
# Version 3.0.0
|
||||
|
||||
- Replace `libc` in all backends with the `rustix` crate (#108).
|
||||
- Use `tracing` instead of `log` for logging (#119).
|
||||
- **Breaking:** Rework the API to use I/O safety. Note that this makes several previously safe functions unsafe. (#123)
|
||||
- Add support for the ESP-IDF platform. (#128)
|
||||
- **Breaking:** Make `Event` partially opaque, and create a new `Events` struct for holding events. (#133)
|
||||
- Add support for running `polling` in Linux containers without `eventfd` available. (#134)
|
||||
- Specify the behavior when registered in multiple `Poller`s. (#136)
|
||||
- **Breaking:** Use `c_int` from the standard library in `polling::os::kqueue` instead of defining our own. (#143)
|
||||
- **Breaking:** Remove the useless `std` feature. (#147)
|
||||
|
||||
# Version 2.8.0
|
||||
|
||||
- Add functionality for posting events to the IOCP. (#101)
|
||||
|
||||
# Version 2.7.0
|
||||
|
||||
- Add edge/oneshot combination mode. (#96)
|
||||
- Update windows-sys requirement from 0.45 to 0.48. (#103)
|
||||
|
||||
# Version 2.6.0
|
||||
|
||||
- Add level and edge triggered modes to the poller (#59)
|
||||
- Support tvOS and watchOS (#60)
|
||||
- Prevent large timeouts from causing panics on certain backends (#71)
|
||||
- For certain BSDs, use `EVFILT_USER` to wake up the poller instead of a pipe (#73)
|
||||
- For Solaris/illumos, use `port_send` to wake up the poller instead of a pipe (#74)
|
||||
- Update `windows_sys` from 0.42 to 0.45 (#80)
|
||||
- Expose other `kqueue` filter types (#83)
|
||||
- Replace the Windows backend with a hand-written version, rather than bringing in a C dependency (#88)
|
||||
|
||||
# Version 2.5.2
|
||||
|
||||
- Update use of `libc::timespec` to prepare for future libc version (#55)
|
||||
- Update use of `libc::kevent` to prepare for future libc version (#56)
|
||||
- Add error message for Wepoll (#54)
|
||||
|
||||
# Version 2.5.1
|
||||
|
||||
- Fix the build error with MSRV on Windows
|
||||
|
||||
# Version 2.5.0
|
||||
|
||||
- Switch from `winapi` to `windows-sys` (#47)
|
||||
|
||||
# Version 2.4.0
|
||||
|
||||
- Fix the build error on illumos and Solaris (#43)
|
||||
- Bump MSRV to 1.47 (#40)
|
||||
- Optimize `Poller` internal representation (#40)
|
||||
|
||||
# Version 2.3.0
|
||||
|
||||
- Implement `AsRawFd` for `Poller` on most Unix systems (#39)
|
||||
- Implement `AsRawHandle` for `Poller` on Windows (#39)
|
||||
- Implement I/O safety traits on Rust 1.63+ (#39)
|
||||
|
||||
# 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.
|
||||
|
|
69
Cargo.toml
69
Cargo.toml
|
@ -1,27 +1,60 @@
|
|||
[package]
|
||||
name = "polling"
|
||||
version = "0.1.7"
|
||||
authors = ["Stjepan Glavina <stjepang@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "Portable interface to epoll, kqueue, event ports, and wepoll"
|
||||
# When publishing a new version:
|
||||
# - Update CHANGELOG.md
|
||||
# - Create "v3.x.y" git tag
|
||||
version = "3.7.0"
|
||||
authors = ["Stjepan Glavina <stjepang@gmail.com>", "John Nunley <dev@notgull.net>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
description = "Portable interface to epoll, kqueue, event ports, and IOCP"
|
||||
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"]
|
||||
repository = "https://github.com/smol-rs/polling"
|
||||
keywords = ["mio", "epoll", "kqueue", "iocp"]
|
||||
categories = ["asynchronous", "network-programming", "os"]
|
||||
readme = "README.md"
|
||||
exclude = ["/.*"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "0.1.10"
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
cfg-if = "1"
|
||||
tracing = { version = "0.1.37", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
easy-parallel = "3.1.0"
|
||||
[target.'cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))'.dependencies.rustix]
|
||||
version = "0.38.31"
|
||||
features = ["event", "fs", "pipe", "process", "std", "time"]
|
||||
default-features = false
|
||||
|
||||
[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", "winsock2"] }
|
||||
concurrent-queue = "2.2.0"
|
||||
pin-project-lite = "0.2.9"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.52"
|
||||
features = [
|
||||
"Wdk_Foundation",
|
||||
"Wdk_Storage_FileSystem",
|
||||
"Win32_Foundation",
|
||||
"Win32_Networking_WinSock",
|
||||
"Win32_Security",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_IO",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_WindowsProgramming",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "hermit")'.dependencies.hermit-abi]
|
||||
version = "0.3.9"
|
||||
|
||||
[dev-dependencies]
|
||||
easy-parallel = "3.1.0"
|
||||
fastrand = "2.0.0"
|
||||
socket2 = "0.5.5"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os="vita")))'.dev-dependencies]
|
||||
signal-hook = "0.3.17"
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[target.arm-linux-androideabi]
|
||||
# Workaround https://github.com/cross-rs/cross/issues/1128 / https://github.com/rust-lang/rust/issues/103673
|
||||
image = "ghcr.io/cross-rs/arm-linux-androideabi:edge"
|
30
README.md
30
README.md
|
@ -1,22 +1,23 @@
|
|||
# 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)
|
||||
[![Build](https://github.com/smol-rs/polling/actions/workflows/ci.yml/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.
|
||||
Portable interface to epoll, kqueue, event ports, and IOCP.
|
||||
|
||||
Supported platforms:
|
||||
- [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android
|
||||
- [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, FreeBSD, NetBSD, OpenBSD,
|
||||
- [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android, RedoxOS
|
||||
- [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, tvOS, watchOS, FreeBSD, NetBSD, OpenBSD,
|
||||
DragonFly BSD
|
||||
- [event ports](https://illumos.org/man/port_create): illumos, Solaris
|
||||
- [wepoll](https://github.com/piscisaureus/wepoll): Windows
|
||||
- [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, HermitOS, other Unix systems
|
||||
- [IOCP](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports): Windows, Wine (version 7.13+)
|
||||
|
||||
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.
|
||||
|
@ -31,12 +32,12 @@ use std::net::TcpListener;
|
|||
|
||||
// Create a TCP listener.
|
||||
let socket = TcpListener::bind("127.0.0.1:8000")?;
|
||||
let key = 7; // arbitrary key identifying the socket
|
||||
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))?;
|
||||
poller.add(&socket, Event::readable(key))?;
|
||||
|
||||
// The event loop.
|
||||
let mut events = Vec::new();
|
||||
|
@ -50,19 +51,18 @@ loop {
|
|||
// Perform a non-blocking accept operation.
|
||||
socket.accept()?;
|
||||
// Set interest in the next readability event.
|
||||
poller.interest(&socket, Event::readable(key))?;
|
||||
poller.modify(&socket, Event::readable(key))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::io::Result::Ok(())
|
||||
```
|
||||
|
||||
## 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)
|
||||
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/license/mit/)
|
||||
|
||||
at your option.
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
use std::{io, net};
|
||||
|
||||
use polling::Event;
|
||||
use socket2::Type;
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let socket = socket2::Socket::new(socket2::Domain::IPV4, Type::STREAM, None)?;
|
||||
let poller = polling::Poller::new()?;
|
||||
unsafe {
|
||||
poller.add(&socket, Event::new(0, true, true))?;
|
||||
}
|
||||
let addr = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 8080);
|
||||
socket.set_nonblocking(true)?;
|
||||
let _ = socket.connect(&addr.into());
|
||||
|
||||
let mut events = polling::Events::new();
|
||||
|
||||
events.clear();
|
||||
poller.wait(&mut events, None)?;
|
||||
|
||||
let event = events.iter().next();
|
||||
let event = match event {
|
||||
Some(event) => event,
|
||||
None => {
|
||||
println!("no event");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
println!("event: {:?}", event);
|
||||
if event.is_err().unwrap_or(false) {
|
||||
println!("connect failed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,35 +1,40 @@
|
|||
use std::io;
|
||||
use std::net::TcpListener;
|
||||
|
||||
use polling::{Event, Poller};
|
||||
use polling::{Event, Events, 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.insert(&l1)?;
|
||||
poller.insert(&l2)?;
|
||||
unsafe {
|
||||
poller.add(&l1, Event::readable(1))?;
|
||||
poller.add(&l2, Event::readable(2))?;
|
||||
}
|
||||
|
||||
poller.interest(&l1, Event::readable(1))?;
|
||||
poller.interest(&l2, Event::readable(2))?;
|
||||
println!("You can connect to the server using `nc`:");
|
||||
println!(" $ nc 127.0.0.1 8001");
|
||||
println!(" $ nc 127.0.0.1 8002");
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut events = Events::new();
|
||||
loop {
|
||||
events.clear();
|
||||
poller.wait(&mut events, None)?;
|
||||
|
||||
for ev in &events {
|
||||
for ev in events.iter() {
|
||||
match ev.key {
|
||||
1 => {
|
||||
println!("Accept on l1");
|
||||
l1.accept()?;
|
||||
poller.interest(&l1, Event::readable(1))?;
|
||||
poller.modify(&l1, Event::readable(1))?;
|
||||
}
|
||||
2 => {
|
||||
println!("Accept on l2");
|
||||
l2.accept()?;
|
||||
poller.interest(&l2, Event::readable(2))?;
|
||||
poller.modify(&l2, Event::readable(2))?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
#[cfg(all(
|
||||
any(
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly",
|
||||
),
|
||||
not(polling_test_poll_backend),
|
||||
))]
|
||||
mod example {
|
||||
use polling::os::kqueue::{PollerKqueueExt, Signal};
|
||||
use polling::{Events, PollMode, Poller};
|
||||
|
||||
pub(super) fn main2() {
|
||||
// Create a poller.
|
||||
let poller = Poller::new().unwrap();
|
||||
|
||||
// Register SIGINT in the poller.
|
||||
let sigint = Signal(rustix::process::Signal::Int as _);
|
||||
poller.add_filter(sigint, 1, PollMode::Oneshot).unwrap();
|
||||
|
||||
let mut events = Events::new();
|
||||
|
||||
println!("Press Ctrl+C to exit...");
|
||||
|
||||
// Wait for events.
|
||||
poller.wait(&mut events, None).unwrap();
|
||||
|
||||
// Process events.
|
||||
let ev = events.iter().next().unwrap();
|
||||
match ev.key {
|
||||
1 => {
|
||||
println!("SIGINT received");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly",
|
||||
),
|
||||
not(polling_test_poll_backend),
|
||||
))]
|
||||
fn main() {
|
||||
example::main2();
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
any(
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly",
|
||||
),
|
||||
not(polling_test_poll_backend),
|
||||
)))]
|
||||
fn main() {
|
||||
eprintln!("This example is only supported on kqueue-based platforms.");
|
||||
}
|
599
src/epoll.rs
599
src/epoll.rs
|
@ -1,289 +1,498 @@
|
|||
//! Bindings to epoll (Linux, Android).
|
||||
|
||||
use std::io;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::ptr;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::Event;
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
use rustix::event::{eventfd, EventfdFlags};
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
use rustix::time::{
|
||||
timerfd_create, timerfd_settime, Itimerspec, TimerfdClockId, TimerfdFlags, TimerfdTimerFlags,
|
||||
Timespec,
|
||||
};
|
||||
|
||||
use rustix::event::epoll;
|
||||
use rustix::fd::OwnedFd;
|
||||
use rustix::fs::{fcntl_getfl, fcntl_setfl, OFlags};
|
||||
use rustix::io::{fcntl_getfd, fcntl_setfd, read, write, FdFlags};
|
||||
use rustix::pipe::{pipe, pipe_with, PipeFlags};
|
||||
|
||||
use crate::{Event, PollMode};
|
||||
|
||||
/// 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,
|
||||
epoll_fd: OwnedFd,
|
||||
|
||||
/// Notifier used to wake up epoll.
|
||||
notifier: Notifier,
|
||||
|
||||
/// File descriptor for the timerfd that produces timeouts.
|
||||
timer_fd: RawFd,
|
||||
///
|
||||
/// Redox does not support timerfd.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
timer_fd: Option<OwnedFd>,
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
//
|
||||
// Use `epoll_create1` with `EPOLL_CLOEXEC`.
|
||||
let epoll_fd = epoll::create(epoll::CreateFlags::CLOEXEC)?;
|
||||
|
||||
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 notifier and timerfd.
|
||||
let notifier = Notifier::new()?;
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
let timer_fd = timerfd_create(
|
||||
TimerfdClockId::Monotonic,
|
||||
TimerfdFlags::CLOEXEC | TimerfdFlags::NONBLOCK,
|
||||
)
|
||||
.ok();
|
||||
|
||||
// Set up eventfd and timerfd.
|
||||
let event_fd = syscall!(eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK))?;
|
||||
let timer_fd = syscall!(timerfd_create(
|
||||
libc::CLOCK_MONOTONIC,
|
||||
libc::TFD_CLOEXEC | libc::TFD_NONBLOCK,
|
||||
))?;
|
||||
let poller = Poller {
|
||||
epoll_fd,
|
||||
event_fd,
|
||||
notifier,
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
timer_fd,
|
||||
};
|
||||
poller.insert(event_fd)?;
|
||||
poller.insert(timer_fd)?;
|
||||
poller.interest(
|
||||
event_fd,
|
||||
Event {
|
||||
key: crate::NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
log::debug!(
|
||||
"new: epoll_fd={}, event_fd={}, timer_fd={}",
|
||||
epoll_fd,
|
||||
event_fd,
|
||||
timer_fd
|
||||
unsafe {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
if let Some(ref timer_fd) = poller.timer_fd {
|
||||
poller.add(
|
||||
timer_fd.as_raw_fd(),
|
||||
Event::none(crate::NOTIFY_KEY),
|
||||
PollMode::Oneshot,
|
||||
)?;
|
||||
}
|
||||
|
||||
poller.add(
|
||||
poller.notifier.as_fd().as_raw_fd(),
|
||||
Event::readable(crate::NOTIFY_KEY),
|
||||
PollMode::Oneshot,
|
||||
)?;
|
||||
}
|
||||
|
||||
tracing::trace!(
|
||||
epoll_fd = ?poller.epoll_fd.as_raw_fd(),
|
||||
notifier = ?poller.notifier,
|
||||
"new",
|
||||
);
|
||||
Ok(poller)
|
||||
}
|
||||
|
||||
/// Inserts a file descriptor.
|
||||
pub fn insert(&self, fd: RawFd) -> io::Result<()> {
|
||||
log::debug!("insert: epoll_fd={}, fd={}", self.epoll_fd, fd);
|
||||
|
||||
// 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: libc::EPOLLONESHOT as _,
|
||||
u64: crate::NOTIFY_KEY as u64,
|
||||
};
|
||||
syscall!(epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_ADD, fd, &mut ev))?;
|
||||
|
||||
Ok(())
|
||||
/// Whether this poller supports level-triggered events.
|
||||
pub fn supports_level(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
log::debug!(
|
||||
"interest: epoll_fd={}, fd={}, ev={:?}",
|
||||
self.epoll_fd,
|
||||
fd,
|
||||
ev
|
||||
/// Whether the poller supports edge-triggered events.
|
||||
pub fn supports_edge(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Adds a new file descriptor.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `fd` must be a valid file descriptor. The usual condition of remaining registered in
|
||||
/// the `Poller` doesn't apply to `epoll`.
|
||||
pub unsafe fn add(&self, fd: RawFd, ev: Event, mode: PollMode) -> io::Result<()> {
|
||||
let span = tracing::trace_span!(
|
||||
"add",
|
||||
epoll_fd = ?self.epoll_fd.as_raw_fd(),
|
||||
?fd,
|
||||
?ev,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
let mut flags = libc::EPOLLONESHOT;
|
||||
if ev.readable {
|
||||
flags |= read_flags();
|
||||
}
|
||||
if ev.writable {
|
||||
flags |= write_flags();
|
||||
}
|
||||
|
||||
let mut ev = libc::epoll_event {
|
||||
events: flags as _,
|
||||
u64: ev.key as u64,
|
||||
};
|
||||
syscall!(epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_MOD, fd, &mut ev))?;
|
||||
epoll::add(
|
||||
&self.epoll_fd,
|
||||
unsafe { rustix::fd::BorrowedFd::borrow_raw(fd) },
|
||||
epoll::EventData::new_u64(ev.key as u64),
|
||||
epoll_flags(&ev, mode) | ev.extra.flags,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a file descriptor.
|
||||
pub fn remove(&self, fd: RawFd) -> io::Result<()> {
|
||||
log::debug!("remove: epoll_fd={}, fd={}", self.epoll_fd, fd);
|
||||
/// Modifies an existing file descriptor.
|
||||
pub fn modify(&self, fd: BorrowedFd<'_>, ev: Event, mode: PollMode) -> io::Result<()> {
|
||||
let span = tracing::trace_span!(
|
||||
"modify",
|
||||
epoll_fd = ?self.epoll_fd.as_raw_fd(),
|
||||
?fd,
|
||||
?ev,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
syscall!(epoll_ctl(
|
||||
self.epoll_fd,
|
||||
libc::EPOLL_CTL_DEL,
|
||||
epoll::modify(
|
||||
&self.epoll_fd,
|
||||
fd,
|
||||
ptr::null_mut()
|
||||
))?;
|
||||
epoll::EventData::new_u64(ev.key as u64),
|
||||
epoll_flags(&ev, mode) | ev.extra.flags,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes a file descriptor.
|
||||
pub fn delete(&self, fd: BorrowedFd<'_>) -> io::Result<()> {
|
||||
let span = tracing::trace_span!(
|
||||
"delete",
|
||||
epoll_fd = ?self.epoll_fd.as_raw_fd(),
|
||||
?fd,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
epoll::delete(&self.epoll_fd, fd)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits for I/O events with an optional timeout.
|
||||
#[allow(clippy::needless_update)]
|
||||
pub fn wait(&self, events: &mut Events, timeout: Option<Duration>) -> io::Result<()> {
|
||||
log::debug!("wait: epoll_fd={}, timeout={:?}", self.epoll_fd, timeout);
|
||||
let span = tracing::trace_span!(
|
||||
"wait",
|
||||
epoll_fd = ?self.epoll_fd.as_raw_fd(),
|
||||
?timeout,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
// 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,
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
if let Some(ref timer_fd) = self.timer_fd {
|
||||
// Configure the timeout using timerfd.
|
||||
let new_val = Itimerspec {
|
||||
it_interval: TS_ZERO,
|
||||
it_value: match timeout {
|
||||
None => TS_ZERO,
|
||||
Some(t) => {
|
||||
let mut ts = TS_ZERO;
|
||||
ts.tv_sec = t.as_secs() as _;
|
||||
ts.tv_nsec = t.subsec_nanos() as _;
|
||||
ts
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
syscall!(timerfd_settime(self.timer_fd, 0, &new_val, ptr::null_mut()))?;
|
||||
..unsafe { std::mem::zeroed() }
|
||||
};
|
||||
|
||||
// Set interest in timerfd.
|
||||
self.interest(
|
||||
self.timer_fd,
|
||||
Event {
|
||||
key: crate::NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
)?;
|
||||
timerfd_settime(timer_fd, TimerfdTimerFlags::empty(), &new_val)?;
|
||||
|
||||
// Set interest in timerfd.
|
||||
self.modify(
|
||||
timer_fd.as_fd(),
|
||||
Event::readable(crate::NOTIFY_KEY),
|
||||
PollMode::Oneshot,
|
||||
)?;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
let timer_fd = &self.timer_fd;
|
||||
#[cfg(target_os = "redox")]
|
||||
let timer_fd: Option<core::convert::Infallible> = None;
|
||||
|
||||
// Timeout in milliseconds for epoll.
|
||||
let timeout_ms = if timeout == Some(Duration::from_secs(0)) {
|
||||
// This is a non-blocking call - use zero as the timeout.
|
||||
0
|
||||
} else {
|
||||
// This is a blocking call - rely on timerfd to trigger the timeout.
|
||||
-1
|
||||
let timeout_ms = match (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,
|
||||
))?;
|
||||
events.len = res as usize;
|
||||
log::trace!("new events: epoll_fd={}, res={}", self.epoll_fd, res);
|
||||
epoll::wait(&self.epoll_fd, &mut events.list, timeout_ms)?;
|
||||
tracing::trace!(
|
||||
epoll_fd = ?self.epoll_fd.as_raw_fd(),
|
||||
res = ?events.list.len(),
|
||||
"new events",
|
||||
);
|
||||
|
||||
// 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,
|
||||
Event {
|
||||
key: crate::NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
self.notifier.clear();
|
||||
self.modify(
|
||||
self.notifier.as_fd(),
|
||||
Event::readable(crate::NOTIFY_KEY),
|
||||
PollMode::Oneshot,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a notification to wake up the current or next `wait()` call.
|
||||
pub fn notify(&self) -> io::Result<()> {
|
||||
log::debug!(
|
||||
"notify: epoll_fd={}, event_fd={}",
|
||||
self.epoll_fd,
|
||||
self.event_fd
|
||||
let span = tracing::trace_span!(
|
||||
"notify",
|
||||
epoll_fd = ?self.epoll_fd.as_raw_fd(),
|
||||
notifier = ?self.notifier,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
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()
|
||||
));
|
||||
self.notifier.notify();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Poller {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.epoll_fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for Poller {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.epoll_fd.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Poller {
|
||||
fn drop(&mut self) {
|
||||
log::debug!(
|
||||
"drop: epoll_fd={}, event_fd={}, timer_fd={}",
|
||||
self.epoll_fd,
|
||||
self.event_fd,
|
||||
self.timer_fd
|
||||
let span = tracing::trace_span!(
|
||||
"drop",
|
||||
epoll_fd = ?self.epoll_fd.as_raw_fd(),
|
||||
notifier = ?self.notifier,
|
||||
);
|
||||
let _ = self.remove(self.event_fd);
|
||||
let _ = self.remove(self.timer_fd);
|
||||
let _ = syscall!(close(self.event_fd));
|
||||
let _ = syscall!(close(self.timer_fd));
|
||||
let _ = syscall!(close(self.epoll_fd));
|
||||
let _enter = span.enter();
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
if let Some(timer_fd) = self.timer_fd.take() {
|
||||
let _ = self.delete(timer_fd.as_fd());
|
||||
}
|
||||
let _ = self.delete(self.notifier.as_fd());
|
||||
}
|
||||
}
|
||||
|
||||
/// `timespec` value that equals zero.
|
||||
const TS_ZERO: libc::timespec = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
const TS_ZERO: Timespec = unsafe { std::mem::transmute([0u8; std::mem::size_of::<Timespec>()]) };
|
||||
|
||||
/// Get the EPOLL flags for the interest.
|
||||
fn epoll_flags(interest: &Event, mode: PollMode) -> epoll::EventFlags {
|
||||
let mut flags = match mode {
|
||||
PollMode::Oneshot => epoll::EventFlags::ONESHOT,
|
||||
PollMode::Level => epoll::EventFlags::empty(),
|
||||
PollMode::Edge => epoll::EventFlags::ET,
|
||||
PollMode::EdgeOneshot => epoll::EventFlags::ET | epoll::EventFlags::ONESHOT,
|
||||
};
|
||||
if interest.readable {
|
||||
flags |= read_flags();
|
||||
}
|
||||
if interest.writable {
|
||||
flags |= write_flags();
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
/// Epoll flags for all possible readability events.
|
||||
fn read_flags() -> libc::c_int {
|
||||
libc::EPOLLIN | libc::EPOLLRDHUP | libc::EPOLLHUP | libc::EPOLLERR | libc::EPOLLPRI
|
||||
fn read_flags() -> epoll::EventFlags {
|
||||
use epoll::EventFlags as Epoll;
|
||||
Epoll::IN | Epoll::HUP | Epoll::ERR | Epoll::PRI
|
||||
}
|
||||
|
||||
/// Epoll flags for all possible writability events.
|
||||
fn write_flags() -> libc::c_int {
|
||||
libc::EPOLLOUT | libc::EPOLLHUP | libc::EPOLLERR
|
||||
fn write_flags() -> epoll::EventFlags {
|
||||
use epoll::EventFlags as Epoll;
|
||||
Epoll::OUT | Epoll::HUP | Epoll::ERR
|
||||
}
|
||||
|
||||
/// A list of reported I/O events.
|
||||
pub struct Events {
|
||||
list: Box<[libc::epoll_event]>,
|
||||
len: usize,
|
||||
list: epoll::EventVec,
|
||||
}
|
||||
|
||||
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 }
|
||||
pub fn with_capacity(cap: usize) -> Events {
|
||||
Events {
|
||||
list: epoll::EventVec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
self.list.iter().map(|ev| {
|
||||
let flags = ev.flags;
|
||||
Event {
|
||||
key: ev.data.u64() as usize,
|
||||
readable: flags.intersects(read_flags()),
|
||||
writable: flags.intersects(write_flags()),
|
||||
extra: EventExtra { flags },
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear the list.
|
||||
pub fn clear(&mut self) {
|
||||
self.list.clear();
|
||||
}
|
||||
|
||||
/// Get the capacity of the list.
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.list.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra information about this event.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct EventExtra {
|
||||
flags: epoll::EventFlags,
|
||||
}
|
||||
|
||||
impl EventExtra {
|
||||
/// Create an empty version of the data.
|
||||
#[inline]
|
||||
pub const fn empty() -> EventExtra {
|
||||
EventExtra {
|
||||
flags: epoll::EventFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the interrupt flag to this event.
|
||||
#[inline]
|
||||
pub fn set_hup(&mut self, active: bool) {
|
||||
self.flags.set(epoll::EventFlags::HUP, active);
|
||||
}
|
||||
|
||||
/// Add the priority flag to this event.
|
||||
#[inline]
|
||||
pub fn set_pri(&mut self, active: bool) {
|
||||
self.flags.set(epoll::EventFlags::PRI, active);
|
||||
}
|
||||
|
||||
/// Tell if the interrupt flag is set.
|
||||
#[inline]
|
||||
pub fn is_hup(&self) -> bool {
|
||||
self.flags.contains(epoll::EventFlags::HUP)
|
||||
}
|
||||
|
||||
/// Tell if the priority flag is set.
|
||||
#[inline]
|
||||
pub fn is_pri(&self) -> bool {
|
||||
self.flags.contains(epoll::EventFlags::PRI)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
Some(
|
||||
self.flags.contains(epoll::EventFlags::ERR)
|
||||
&& self.flags.contains(epoll::EventFlags::HUP),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(epoll::EventFlags::ERR))
|
||||
}
|
||||
}
|
||||
|
||||
/// The notifier for Linux.
|
||||
///
|
||||
/// Certain container runtimes do not expose eventfd to the client, as it relies on the host and
|
||||
/// can be used to "escape" the container under certain conditions. Gramine is the prime example,
|
||||
/// see [here](gramine). In this case, fall back to using a pipe.
|
||||
///
|
||||
/// [gramine]: https://gramine.readthedocs.io/en/stable/manifest-syntax.html#allowing-eventfd
|
||||
#[derive(Debug)]
|
||||
enum Notifier {
|
||||
/// The primary notifier, using eventfd.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
EventFd(OwnedFd),
|
||||
|
||||
/// The fallback notifier, using a pipe.
|
||||
Pipe {
|
||||
/// The read end of the pipe.
|
||||
read_pipe: OwnedFd,
|
||||
|
||||
/// The write end of the pipe.
|
||||
write_pipe: OwnedFd,
|
||||
},
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
/// Create a new notifier.
|
||||
fn new() -> io::Result<Self> {
|
||||
// Skip eventfd for testing if necessary.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
{
|
||||
if !cfg!(polling_test_epoll_pipe) {
|
||||
// Try to create an eventfd.
|
||||
match eventfd(0, EventfdFlags::CLOEXEC | EventfdFlags::NONBLOCK) {
|
||||
Ok(fd) => {
|
||||
tracing::trace!("created eventfd for notifier");
|
||||
return Ok(Notifier::EventFd(fd));
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"eventfd() failed with error ({}), falling back to pipe",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (read, write) = pipe_with(PipeFlags::CLOEXEC).or_else(|_| {
|
||||
let (read, write) = pipe()?;
|
||||
fcntl_setfd(&read, fcntl_getfd(&read)? | FdFlags::CLOEXEC)?;
|
||||
fcntl_setfd(&write, fcntl_getfd(&write)? | FdFlags::CLOEXEC)?;
|
||||
io::Result::Ok((read, write))
|
||||
})?;
|
||||
|
||||
fcntl_setfl(&read, fcntl_getfl(&read)? | OFlags::NONBLOCK)?;
|
||||
Ok(Notifier::Pipe {
|
||||
read_pipe: read,
|
||||
write_pipe: write,
|
||||
})
|
||||
}
|
||||
|
||||
/// The file descriptor to register in the poller.
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
match self {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
Notifier::EventFd(fd) => fd.as_fd(),
|
||||
Notifier::Pipe {
|
||||
read_pipe: read, ..
|
||||
} => read.as_fd(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify the poller.
|
||||
fn notify(&self) {
|
||||
match self {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
Self::EventFd(fd) => {
|
||||
let buf: [u8; 8] = 1u64.to_ne_bytes();
|
||||
let _ = write(fd, &buf);
|
||||
}
|
||||
|
||||
Self::Pipe { write_pipe, .. } => {
|
||||
write(write_pipe, &[0; 1]).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the notification.
|
||||
fn clear(&self) {
|
||||
match self {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
Self::EventFd(fd) => {
|
||||
let mut buf = [0u8; 8];
|
||||
let _ = read(fd, &mut buf);
|
||||
}
|
||||
|
||||
Self::Pipe { read_pipe, .. } => while read(read_pipe, &mut [0u8; 1024]).is_ok() {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,688 @@
|
|||
//! Safe wrapper around \Device\Afd
|
||||
|
||||
use super::port::{Completion, CompletionHandle};
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::marker::{PhantomData, PhantomPinned};
|
||||
use std::mem::{self, size_of, transmute, MaybeUninit};
|
||||
use std::ops;
|
||||
use std::os::windows::prelude::{AsRawHandle, RawHandle, RawSocket};
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Once;
|
||||
|
||||
use windows_sys::Wdk::Foundation::OBJECT_ATTRIBUTES;
|
||||
use windows_sys::Wdk::Storage::FileSystem::FILE_OPEN;
|
||||
use windows_sys::Win32::Foundation::{
|
||||
CloseHandle, HANDLE, HMODULE, NTSTATUS, STATUS_NOT_FOUND, STATUS_PENDING, STATUS_SUCCESS,
|
||||
UNICODE_STRING,
|
||||
};
|
||||
use windows_sys::Win32::Networking::WinSock::{
|
||||
WSAIoctl, SIO_BASE_HANDLE, SIO_BSP_HANDLE_POLL, SOCKET_ERROR,
|
||||
};
|
||||
use windows_sys::Win32::Storage::FileSystem::{FILE_SHARE_READ, FILE_SHARE_WRITE, SYNCHRONIZE};
|
||||
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress};
|
||||
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
|
||||
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
pub(super) struct AfdPollInfo {
|
||||
/// The timeout for this poll.
|
||||
timeout: i64,
|
||||
|
||||
/// The number of handles being polled.
|
||||
handle_count: u32,
|
||||
|
||||
/// Whether or not this poll is exclusive for this handle.
|
||||
exclusive: u32,
|
||||
|
||||
/// The handles to poll.
|
||||
handles: [AfdPollHandleInfo; 1],
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
struct AfdPollHandleInfo {
|
||||
/// The handle to poll.
|
||||
handle: HANDLE,
|
||||
|
||||
/// The events to poll for.
|
||||
events: AfdPollMask,
|
||||
|
||||
/// The status of the poll.
|
||||
status: NTSTATUS,
|
||||
}
|
||||
|
||||
impl AfdPollInfo {
|
||||
pub(super) fn handle_count(&self) -> u32 {
|
||||
self.handle_count
|
||||
}
|
||||
|
||||
pub(super) fn events(&self) -> AfdPollMask {
|
||||
self.handles[0].events
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub(super) struct AfdPollMask(u32);
|
||||
|
||||
impl AfdPollMask {
|
||||
pub(crate) const RECEIVE: AfdPollMask = AfdPollMask(0x001);
|
||||
pub(crate) const RECEIVE_EXPEDITED: AfdPollMask = AfdPollMask(0x002);
|
||||
pub(crate) const SEND: AfdPollMask = AfdPollMask(0x004);
|
||||
pub(crate) const DISCONNECT: AfdPollMask = AfdPollMask(0x008);
|
||||
pub(crate) const ABORT: AfdPollMask = AfdPollMask(0x010);
|
||||
pub(crate) const LOCAL_CLOSE: AfdPollMask = AfdPollMask(0x020);
|
||||
pub(crate) const ACCEPT: AfdPollMask = AfdPollMask(0x080);
|
||||
pub(crate) const CONNECT_FAIL: AfdPollMask = AfdPollMask(0x100);
|
||||
|
||||
/// Creates an empty mask.
|
||||
pub(crate) const fn empty() -> AfdPollMask {
|
||||
AfdPollMask(0)
|
||||
}
|
||||
|
||||
/// Checks if this mask contains the other mask.
|
||||
pub(crate) fn intersects(self, other: AfdPollMask) -> bool {
|
||||
(self.0 & other.0) != 0
|
||||
}
|
||||
|
||||
/// Sets a flag.
|
||||
pub(crate) fn set(&mut self, other: AfdPollMask, value: bool) {
|
||||
if value {
|
||||
*self |= other;
|
||||
} else {
|
||||
self.0 &= !other.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for AfdPollMask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
const FLAGS: &[(&str, AfdPollMask)] = &[
|
||||
("RECEIVE", AfdPollMask::RECEIVE),
|
||||
("RECEIVE_EXPEDITED", AfdPollMask::RECEIVE_EXPEDITED),
|
||||
("SEND", AfdPollMask::SEND),
|
||||
("DISCONNECT", AfdPollMask::DISCONNECT),
|
||||
("ABORT", AfdPollMask::ABORT),
|
||||
("LOCAL_CLOSE", AfdPollMask::LOCAL_CLOSE),
|
||||
("ACCEPT", AfdPollMask::ACCEPT),
|
||||
("CONNECT_FAIL", AfdPollMask::CONNECT_FAIL),
|
||||
];
|
||||
|
||||
let mut first = true;
|
||||
for (name, value) in FLAGS {
|
||||
if self.intersects(*value) {
|
||||
if !first {
|
||||
write!(f, " | ")?;
|
||||
}
|
||||
|
||||
first = false;
|
||||
write!(f, "{}", name)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOr for AfdPollMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
AfdPollMask(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOrAssign for AfdPollMask {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.0 |= rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitAnd for AfdPollMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self {
|
||||
AfdPollMask(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitAndAssign for AfdPollMask {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
self.0 &= rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait HasAfdInfo {
|
||||
fn afd_info(self: Pin<&Self>) -> Pin<&UnsafeCell<AfdPollInfo>>;
|
||||
}
|
||||
|
||||
macro_rules! define_ntdll_import {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
fn $name:ident($($arg:ident: $arg_ty:ty),*) -> $ret:ty;
|
||||
)*
|
||||
) => {
|
||||
/// Imported functions from ntdll.dll.
|
||||
#[allow(non_snake_case)]
|
||||
pub(super) struct NtdllImports {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
$name: unsafe extern "system" fn($($arg_ty),*) -> $ret,
|
||||
)*
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl NtdllImports {
|
||||
unsafe fn load(ntdll: HMODULE) -> io::Result<Self> {
|
||||
$(
|
||||
let $name = {
|
||||
const NAME: &str = concat!(stringify!($name), "\0");
|
||||
let addr = GetProcAddress(ntdll, NAME.as_ptr() as *const _);
|
||||
|
||||
let addr = match addr {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
tracing::error!("Failed to load ntdll function {}", NAME);
|
||||
return Err(io::Error::last_os_error());
|
||||
},
|
||||
};
|
||||
|
||||
transmute::<_, unsafe extern "system" fn($($arg_ty),*) -> $ret>(addr)
|
||||
};
|
||||
)*
|
||||
|
||||
Ok(Self {
|
||||
$(
|
||||
$name,
|
||||
)*
|
||||
})
|
||||
}
|
||||
|
||||
$(
|
||||
$(#[$attr])*
|
||||
unsafe fn $name(&self, $($arg: $arg_ty),*) -> $ret {
|
||||
(self.$name)($($arg),*)
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_ntdll_import! {
|
||||
/// Cancels an ongoing I/O operation.
|
||||
fn NtCancelIoFileEx(
|
||||
FileHandle: HANDLE,
|
||||
IoRequestToCancel: *mut IO_STATUS_BLOCK,
|
||||
IoStatusBlock: *mut IO_STATUS_BLOCK
|
||||
) -> NTSTATUS;
|
||||
|
||||
/// Opens or creates a file handle.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn NtCreateFile(
|
||||
FileHandle: *mut HANDLE,
|
||||
DesiredAccess: u32,
|
||||
ObjectAttributes: *mut OBJECT_ATTRIBUTES,
|
||||
IoStatusBlock: *mut IO_STATUS_BLOCK,
|
||||
AllocationSize: *mut i64,
|
||||
FileAttributes: u32,
|
||||
ShareAccess: u32,
|
||||
CreateDisposition: u32,
|
||||
CreateOptions: u32,
|
||||
EaBuffer: *mut (),
|
||||
EaLength: u32
|
||||
) -> NTSTATUS;
|
||||
|
||||
/// Runs an I/O control on a file handle.
|
||||
///
|
||||
/// Practically equivalent to `ioctl`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn NtDeviceIoControlFile(
|
||||
FileHandle: HANDLE,
|
||||
Event: HANDLE,
|
||||
ApcRoutine: *mut (),
|
||||
ApcContext: *mut (),
|
||||
IoStatusBlock: *mut IO_STATUS_BLOCK,
|
||||
IoControlCode: u32,
|
||||
InputBuffer: *mut (),
|
||||
InputBufferLength: u32,
|
||||
OutputBuffer: *mut (),
|
||||
OutputBufferLength: u32
|
||||
) -> NTSTATUS;
|
||||
|
||||
/// Converts `NTSTATUS` to a DOS error code.
|
||||
fn RtlNtStatusToDosError(
|
||||
Status: NTSTATUS
|
||||
) -> u32;
|
||||
}
|
||||
|
||||
impl NtdllImports {
|
||||
fn get() -> io::Result<&'static Self> {
|
||||
macro_rules! s {
|
||||
($e:expr) => {{
|
||||
$e as u16
|
||||
}};
|
||||
}
|
||||
|
||||
// ntdll.dll
|
||||
static NTDLL_NAME: &[u16] = &[
|
||||
s!('n'),
|
||||
s!('t'),
|
||||
s!('d'),
|
||||
s!('l'),
|
||||
s!('l'),
|
||||
s!('.'),
|
||||
s!('d'),
|
||||
s!('l'),
|
||||
s!('l'),
|
||||
s!('\0'),
|
||||
];
|
||||
static NTDLL_IMPORTS: OnceCell<io::Result<NtdllImports>> = OnceCell::new();
|
||||
|
||||
NTDLL_IMPORTS
|
||||
.get_or_init(|| unsafe {
|
||||
let ntdll = GetModuleHandleW(NTDLL_NAME.as_ptr() as *const _);
|
||||
|
||||
if ntdll == 0 {
|
||||
tracing::error!("Failed to load ntdll.dll");
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
NtdllImports::load(ntdll)
|
||||
})
|
||||
.as_ref()
|
||||
.map_err(|e| io::Error::from(e.kind()))
|
||||
}
|
||||
|
||||
pub(super) fn force_load() -> io::Result<()> {
|
||||
Self::get()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The handle to the AFD device.
|
||||
pub(super) struct Afd<T> {
|
||||
/// The handle to the AFD device.
|
||||
handle: HANDLE,
|
||||
|
||||
/// We own `T`.
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Afd<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
struct WriteAsHex(HANDLE);
|
||||
|
||||
impl fmt::Debug for WriteAsHex {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:010x}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
f.debug_struct("Afd")
|
||||
.field("handle", &WriteAsHex(self.handle))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Afd<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CloseHandle(self.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRawHandle for Afd<T> {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.handle as _
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CompletionHandle> Afd<T>
|
||||
where
|
||||
T::Completion: AsIoStatusBlock + HasAfdInfo,
|
||||
{
|
||||
/// Create a new AFD device.
|
||||
pub(super) fn new() -> io::Result<Self> {
|
||||
macro_rules! s {
|
||||
($e:expr) => {
|
||||
($e) as u16
|
||||
};
|
||||
}
|
||||
|
||||
/// \Device\Afd\Smol
|
||||
const AFD_NAME: &[u16] = &[
|
||||
s!('\\'),
|
||||
s!('D'),
|
||||
s!('e'),
|
||||
s!('v'),
|
||||
s!('i'),
|
||||
s!('c'),
|
||||
s!('e'),
|
||||
s!('\\'),
|
||||
s!('A'),
|
||||
s!('f'),
|
||||
s!('d'),
|
||||
s!('\\'),
|
||||
s!('S'),
|
||||
s!('m'),
|
||||
s!('o'),
|
||||
s!('l'),
|
||||
s!('\0'),
|
||||
];
|
||||
|
||||
// Set up device attributes.
|
||||
let mut device_name = UNICODE_STRING {
|
||||
Length: mem::size_of_val(AFD_NAME) as u16,
|
||||
MaximumLength: mem::size_of_val(AFD_NAME) as u16,
|
||||
Buffer: AFD_NAME.as_ptr() as *mut _,
|
||||
};
|
||||
let mut device_attributes = OBJECT_ATTRIBUTES {
|
||||
Length: size_of::<OBJECT_ATTRIBUTES>() as u32,
|
||||
RootDirectory: 0,
|
||||
ObjectName: &mut device_name,
|
||||
Attributes: 0,
|
||||
SecurityDescriptor: ptr::null_mut(),
|
||||
SecurityQualityOfService: ptr::null_mut(),
|
||||
};
|
||||
|
||||
let mut handle = MaybeUninit::<HANDLE>::uninit();
|
||||
let mut iosb = MaybeUninit::<IO_STATUS_BLOCK>::zeroed();
|
||||
let ntdll = NtdllImports::get()?;
|
||||
|
||||
let result = unsafe {
|
||||
ntdll.NtCreateFile(
|
||||
handle.as_mut_ptr(),
|
||||
SYNCHRONIZE,
|
||||
&mut device_attributes,
|
||||
iosb.as_mut_ptr(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
FILE_OPEN,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if result != STATUS_SUCCESS {
|
||||
let real_code = unsafe { ntdll.RtlNtStatusToDosError(result) };
|
||||
|
||||
return Err(io::Error::from_raw_os_error(real_code as i32));
|
||||
}
|
||||
|
||||
let handle = unsafe { handle.assume_init() };
|
||||
|
||||
Ok(Self {
|
||||
handle,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Begin polling with the provided handle.
|
||||
pub(super) fn poll(
|
||||
&self,
|
||||
packet: T,
|
||||
base_socket: RawSocket,
|
||||
afd_events: AfdPollMask,
|
||||
) -> io::Result<()> {
|
||||
const IOCTL_AFD_POLL: u32 = 0x00012024;
|
||||
|
||||
// Lock the packet.
|
||||
if !packet.get().try_lock() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"packet is already in use",
|
||||
));
|
||||
}
|
||||
|
||||
// Set up the AFD poll info.
|
||||
let poll_info = unsafe {
|
||||
let poll_info = Pin::into_inner_unchecked(packet.get().afd_info()).get();
|
||||
|
||||
// Initialize the AFD poll info.
|
||||
(*poll_info).exclusive = false.into();
|
||||
(*poll_info).handle_count = 1;
|
||||
(*poll_info).timeout = std::i64::MAX;
|
||||
(*poll_info).handles[0].handle = base_socket as HANDLE;
|
||||
(*poll_info).handles[0].status = 0;
|
||||
(*poll_info).handles[0].events = afd_events;
|
||||
|
||||
poll_info
|
||||
};
|
||||
|
||||
let iosb = T::into_ptr(packet).cast::<IO_STATUS_BLOCK>();
|
||||
// Set Status to pending
|
||||
unsafe {
|
||||
(*iosb).Anonymous.Status = STATUS_PENDING;
|
||||
}
|
||||
|
||||
let ntdll = NtdllImports::get()?;
|
||||
let result = unsafe {
|
||||
ntdll.NtDeviceIoControlFile(
|
||||
self.handle,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
iosb.cast(),
|
||||
iosb.cast(),
|
||||
IOCTL_AFD_POLL,
|
||||
poll_info.cast(),
|
||||
size_of::<AfdPollInfo>() as u32,
|
||||
poll_info.cast(),
|
||||
size_of::<AfdPollInfo>() as u32,
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
STATUS_SUCCESS => Ok(()),
|
||||
STATUS_PENDING => Err(io::ErrorKind::WouldBlock.into()),
|
||||
status => {
|
||||
let real_code = unsafe { ntdll.RtlNtStatusToDosError(status) };
|
||||
|
||||
Err(io::Error::from_raw_os_error(real_code as i32))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel an ongoing poll operation.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The poll operation must currently be in progress for this AFD.
|
||||
pub(super) unsafe fn cancel(&self, packet: &T) -> io::Result<()> {
|
||||
let ntdll = NtdllImports::get()?;
|
||||
|
||||
let result = {
|
||||
// First, check if the packet is still in use.
|
||||
let iosb = packet.as_ptr().cast::<IO_STATUS_BLOCK>();
|
||||
|
||||
if (*iosb).Anonymous.Status != STATUS_PENDING {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Cancel the packet.
|
||||
let mut cancel_iosb = MaybeUninit::<IO_STATUS_BLOCK>::zeroed();
|
||||
|
||||
ntdll.NtCancelIoFileEx(self.handle, iosb, cancel_iosb.as_mut_ptr())
|
||||
};
|
||||
|
||||
if result == STATUS_SUCCESS || result == STATUS_NOT_FOUND {
|
||||
Ok(())
|
||||
} else {
|
||||
let real_code = ntdll.RtlNtStatusToDosError(result);
|
||||
|
||||
Err(io::Error::from_raw_os_error(real_code as i32))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A one-time initialization cell.
|
||||
struct OnceCell<T> {
|
||||
/// The value.
|
||||
value: UnsafeCell<MaybeUninit<T>>,
|
||||
|
||||
/// The one-time initialization.
|
||||
once: Once,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send + Sync> Send for OnceCell<T> {}
|
||||
unsafe impl<T: Send + Sync> Sync for OnceCell<T> {}
|
||||
|
||||
impl<T> OnceCell<T> {
|
||||
/// Creates a new `OnceCell`.
|
||||
pub const fn new() -> Self {
|
||||
OnceCell {
|
||||
value: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
once: Once::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the value or initializes it.
|
||||
pub fn get_or_init<F>(&self, f: F) -> &T
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
self.once.call_once(|| unsafe {
|
||||
let value = f();
|
||||
*self.value.get() = MaybeUninit::new(value);
|
||||
});
|
||||
|
||||
unsafe { &*self.value.get().cast() }
|
||||
}
|
||||
}
|
||||
|
||||
pin_project_lite::pin_project! {
|
||||
/// An I/O status block paired with some auxillary data.
|
||||
#[repr(C)]
|
||||
pub(super) struct IoStatusBlock<T> {
|
||||
// The I/O status block.
|
||||
iosb: UnsafeCell<IO_STATUS_BLOCK>,
|
||||
|
||||
// Whether or not the block is in use.
|
||||
in_use: AtomicBool,
|
||||
|
||||
// The auxillary data.
|
||||
#[pin]
|
||||
data: T,
|
||||
|
||||
// This block is not allowed to move.
|
||||
#[pin]
|
||||
_marker: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for IoStatusBlock<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("IoStatusBlock")
|
||||
.field("iosb", &"..")
|
||||
.field("in_use", &self.in_use)
|
||||
.field("data", &self.data)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for IoStatusBlock<T> {}
|
||||
unsafe impl<T: Sync> Sync for IoStatusBlock<T> {}
|
||||
|
||||
impl<T> From<T> for IoStatusBlock<T> {
|
||||
fn from(data: T) -> Self {
|
||||
Self {
|
||||
iosb: UnsafeCell::new(unsafe { std::mem::zeroed() }),
|
||||
in_use: AtomicBool::new(false),
|
||||
data,
|
||||
_marker: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IoStatusBlock<T> {
|
||||
pub(super) fn iosb(self: Pin<&Self>) -> &UnsafeCell<IO_STATUS_BLOCK> {
|
||||
self.project_ref().iosb
|
||||
}
|
||||
|
||||
pub(super) fn data(self: Pin<&Self>) -> Pin<&T> {
|
||||
self.project_ref().data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasAfdInfo> HasAfdInfo for IoStatusBlock<T> {
|
||||
fn afd_info(self: Pin<&Self>) -> Pin<&UnsafeCell<AfdPollInfo>> {
|
||||
self.project_ref().data.afd_info()
|
||||
}
|
||||
}
|
||||
|
||||
/// Can be transmuted to an I/O status block.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A pointer to `T` must be able to be converted to a pointer to `IO_STATUS_BLOCK`
|
||||
/// without any issues.
|
||||
pub(super) unsafe trait AsIoStatusBlock {}
|
||||
|
||||
unsafe impl<T> AsIoStatusBlock for IoStatusBlock<T> {}
|
||||
unsafe impl<T> Completion for IoStatusBlock<T> {
|
||||
fn try_lock(self: Pin<&Self>) -> bool {
|
||||
!self.in_use.swap(true, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
unsafe fn unlock(self: Pin<&Self>) {
|
||||
self.in_use.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the base socket associated with a socket.
|
||||
pub(super) fn base_socket(sock: RawSocket) -> io::Result<RawSocket> {
|
||||
// First, try the SIO_BASE_HANDLE ioctl.
|
||||
let result = unsafe { try_socket_ioctl(sock, SIO_BASE_HANDLE) };
|
||||
|
||||
match result {
|
||||
Ok(sock) => return Ok(sock),
|
||||
Err(e) if e.kind() == io::ErrorKind::InvalidInput => return Err(e),
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
// Some poorly coded LSPs may not handle SIO_BASE_HANDLE properly, but in some cases may
|
||||
// handle SIO_BSP_HANDLE_POLL better. Try that.
|
||||
let result = unsafe { try_socket_ioctl(sock, SIO_BSP_HANDLE_POLL)? };
|
||||
if result == sock {
|
||||
return Err(io::Error::from(io::ErrorKind::InvalidInput));
|
||||
}
|
||||
|
||||
// Try `SIO_BASE_HANDLE` again, in case the LSP fixed itself.
|
||||
unsafe { try_socket_ioctl(result, SIO_BASE_HANDLE) }
|
||||
}
|
||||
|
||||
/// Run an IOCTL on a socket and return a socket.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `ioctl` parameter must be a valid I/O control that returns a valid socket.
|
||||
unsafe fn try_socket_ioctl(sock: RawSocket, ioctl: u32) -> io::Result<RawSocket> {
|
||||
let mut out = MaybeUninit::<RawSocket>::uninit();
|
||||
let mut bytes = 0u32;
|
||||
|
||||
let result = WSAIoctl(
|
||||
sock as _,
|
||||
ioctl,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
out.as_mut_ptr().cast(),
|
||||
size_of::<RawSocket>() as u32,
|
||||
&mut bytes,
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
);
|
||||
|
||||
if result == SOCKET_ERROR {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(out.assume_init())
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,298 @@
|
|||
//! A safe wrapper around the Windows I/O API.
|
||||
|
||||
use super::dur2timeout;
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::Deref;
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
|
||||
use windows_sys::Win32::Storage::FileSystem::SetFileCompletionNotificationModes;
|
||||
use windows_sys::Win32::System::Threading::INFINITE;
|
||||
use windows_sys::Win32::System::WindowsProgramming::FILE_SKIP_SET_EVENT_ON_HANDLE;
|
||||
use windows_sys::Win32::System::IO::{
|
||||
CreateIoCompletionPort, GetQueuedCompletionStatusEx, PostQueuedCompletionStatus, OVERLAPPED,
|
||||
OVERLAPPED_ENTRY,
|
||||
};
|
||||
|
||||
/// A completion block which can be used with I/O completion ports.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This must be a valid completion block.
|
||||
pub(super) unsafe trait Completion {
|
||||
/// Signal to the completion block that we are about to start an operation.
|
||||
fn try_lock(self: Pin<&Self>) -> bool;
|
||||
|
||||
/// Unlock the completion block.
|
||||
unsafe fn unlock(self: Pin<&Self>);
|
||||
}
|
||||
|
||||
/// The pointer to a completion block.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This must be a valid completion block.
|
||||
pub(super) unsafe trait CompletionHandle: Deref + Sized {
|
||||
/// Type of the completion block.
|
||||
type Completion: Completion;
|
||||
|
||||
/// Get a pointer to the completion block.
|
||||
///
|
||||
/// The pointer is pinned since the underlying object should not be moved
|
||||
/// after creation. This prevents it from being invalidated while it's
|
||||
/// used in an overlapped operation.
|
||||
fn get(&self) -> Pin<&Self::Completion>;
|
||||
|
||||
/// Convert this block into a pointer that can be passed as `*mut OVERLAPPED`.
|
||||
fn into_ptr(this: Self) -> *mut OVERLAPPED;
|
||||
|
||||
/// Convert a pointer that was passed as `*mut OVERLAPPED` into a pointer to this block.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This must be a valid pointer to a completion block.
|
||||
unsafe fn from_ptr(ptr: *mut OVERLAPPED) -> Self;
|
||||
|
||||
/// Convert to a pointer without losing ownership.
|
||||
fn as_ptr(&self) -> *mut OVERLAPPED;
|
||||
}
|
||||
|
||||
unsafe impl<'a, T: Completion> CompletionHandle for Pin<&'a T> {
|
||||
type Completion = T;
|
||||
|
||||
fn get(&self) -> Pin<&Self::Completion> {
|
||||
*self
|
||||
}
|
||||
|
||||
fn into_ptr(this: Self) -> *mut OVERLAPPED {
|
||||
unsafe { Pin::into_inner_unchecked(this) as *const T as *mut OVERLAPPED }
|
||||
}
|
||||
|
||||
unsafe fn from_ptr(ptr: *mut OVERLAPPED) -> Self {
|
||||
Pin::new_unchecked(&*(ptr as *const T))
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *mut OVERLAPPED {
|
||||
self.get_ref() as *const T as *mut OVERLAPPED
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Completion> CompletionHandle for Pin<Arc<T>> {
|
||||
type Completion = T;
|
||||
|
||||
fn get(&self) -> Pin<&Self::Completion> {
|
||||
self.as_ref()
|
||||
}
|
||||
|
||||
fn into_ptr(this: Self) -> *mut OVERLAPPED {
|
||||
unsafe { Arc::into_raw(Pin::into_inner_unchecked(this)) as *const T as *mut OVERLAPPED }
|
||||
}
|
||||
|
||||
unsafe fn from_ptr(ptr: *mut OVERLAPPED) -> Self {
|
||||
Pin::new_unchecked(Arc::from_raw(ptr as *const T))
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *mut OVERLAPPED {
|
||||
self.as_ref().get_ref() as *const T as *mut OVERLAPPED
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to the I/O completion port.
|
||||
pub(super) struct IoCompletionPort<T> {
|
||||
/// The underlying handle.
|
||||
handle: HANDLE,
|
||||
|
||||
/// We own the status block.
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Drop for IoCompletionPort<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CloseHandle(self.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRawHandle for IoCompletionPort<T> {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.handle as _
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for IoCompletionPort<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
struct WriteAsHex(HANDLE);
|
||||
|
||||
impl fmt::Debug for WriteAsHex {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:010x}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
f.debug_struct("IoCompletionPort")
|
||||
.field("handle", &WriteAsHex(self.handle))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CompletionHandle> IoCompletionPort<T> {
|
||||
/// Create a new I/O completion port.
|
||||
pub(super) fn new(threads: usize) -> io::Result<Self> {
|
||||
let handle = unsafe {
|
||||
CreateIoCompletionPort(
|
||||
INVALID_HANDLE_VALUE,
|
||||
0,
|
||||
0,
|
||||
threads.try_into().expect("too many threads"),
|
||||
)
|
||||
};
|
||||
|
||||
if handle == 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(Self {
|
||||
handle,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a handle with this I/O completion port.
|
||||
pub(super) fn register(
|
||||
&self,
|
||||
handle: &impl AsRawHandle, // TODO change to AsHandle
|
||||
skip_set_event_on_handle: bool,
|
||||
) -> io::Result<()> {
|
||||
let handle = handle.as_raw_handle();
|
||||
|
||||
let result =
|
||||
unsafe { CreateIoCompletionPort(handle as _, self.handle, handle as usize, 0) };
|
||||
|
||||
if result == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
if skip_set_event_on_handle {
|
||||
// Set the skip event on handle.
|
||||
let result = unsafe {
|
||||
SetFileCompletionNotificationModes(handle as _, FILE_SKIP_SET_EVENT_ON_HANDLE as _)
|
||||
};
|
||||
|
||||
if result == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Post a completion packet to this port.
|
||||
pub(super) fn post(&self, bytes_transferred: usize, id: usize, packet: T) -> io::Result<()> {
|
||||
let result = unsafe {
|
||||
PostQueuedCompletionStatus(
|
||||
self.handle,
|
||||
bytes_transferred
|
||||
.try_into()
|
||||
.expect("too many bytes transferred"),
|
||||
id,
|
||||
T::into_ptr(packet),
|
||||
)
|
||||
};
|
||||
|
||||
if result == 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for completion packets to arrive.
|
||||
pub(super) fn wait(
|
||||
&self,
|
||||
packets: &mut Vec<OverlappedEntry<T>>,
|
||||
timeout: Option<Duration>,
|
||||
) -> io::Result<usize> {
|
||||
// Drop the current packets.
|
||||
packets.clear();
|
||||
|
||||
let mut count = MaybeUninit::<u32>::uninit();
|
||||
let timeout = timeout.map_or(INFINITE, dur2timeout);
|
||||
|
||||
let result = unsafe {
|
||||
GetQueuedCompletionStatusEx(
|
||||
self.handle,
|
||||
packets.as_mut_ptr() as _,
|
||||
packets.capacity().try_into().expect("too many packets"),
|
||||
count.as_mut_ptr(),
|
||||
timeout,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if result == 0 {
|
||||
let io_error = io::Error::last_os_error();
|
||||
if io_error.kind() == io::ErrorKind::TimedOut {
|
||||
Ok(0)
|
||||
} else {
|
||||
Err(io_error)
|
||||
}
|
||||
} else {
|
||||
let count = unsafe { count.assume_init() };
|
||||
unsafe {
|
||||
packets.set_len(count as _);
|
||||
}
|
||||
Ok(count as _)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An `OVERLAPPED_ENTRY` resulting from an I/O completion port.
|
||||
#[repr(transparent)]
|
||||
pub(super) struct OverlappedEntry<T: CompletionHandle> {
|
||||
/// The underlying entry.
|
||||
entry: OVERLAPPED_ENTRY,
|
||||
|
||||
/// We own the status block.
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: CompletionHandle> fmt::Debug for OverlappedEntry<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("OverlappedEntry { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CompletionHandle> OverlappedEntry<T> {
|
||||
/// Convert into the completion packet.
|
||||
pub(super) fn into_packet(self) -> T {
|
||||
let packet = unsafe { self.packet() };
|
||||
std::mem::forget(self);
|
||||
packet
|
||||
}
|
||||
|
||||
/// Get the packet reference that this entry refers to.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should only be called once, since it moves
|
||||
/// out the `T` from the `OVERLAPPED_ENTRY`.
|
||||
unsafe fn packet(&self) -> T {
|
||||
let packet = T::from_ptr(self.entry.lpOverlapped);
|
||||
packet.get().unlock();
|
||||
packet
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CompletionHandle> Drop for OverlappedEntry<T> {
|
||||
fn drop(&mut self) {
|
||||
drop(unsafe { self.packet() });
|
||||
}
|
||||
}
|
659
src/kqueue.rs
659
src/kqueue.rs
|
@ -1,265 +1,306 @@
|
|||
//! Bindings to kqueue (macOS, iOS, FreeBSD, NetBSD, OpenBSD, DragonFly BSD).
|
||||
//! Bindings to kqueue (macOS, iOS, tvOS, watchOS, 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::collections::HashSet;
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::Event;
|
||||
use rustix::event::kqueue;
|
||||
use rustix::io::{fcntl_setfd, Errno, FdFlags};
|
||||
|
||||
use crate::{Event, PollMode};
|
||||
|
||||
/// 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,
|
||||
kqueue_fd: OwnedFd,
|
||||
|
||||
/// List of sources currently registered in this poller.
|
||||
///
|
||||
/// This is used to make sure the same source is not registered twice.
|
||||
sources: RwLock<HashSet<SourceId>>,
|
||||
|
||||
/// Notification pipe for waking up the poller.
|
||||
///
|
||||
/// On platforms that support `EVFILT_USER`, this uses that to wake up the poller. Otherwise, it
|
||||
/// uses a pipe.
|
||||
notify: notify::Notify,
|
||||
}
|
||||
|
||||
/// Identifier for a source.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SourceId {
|
||||
/// Registered file descriptor.
|
||||
Fd(RawFd),
|
||||
|
||||
/// Signal.
|
||||
Signal(std::os::raw::c_int),
|
||||
|
||||
/// Process ID.
|
||||
Pid(rustix::process::Pid),
|
||||
|
||||
/// Timer ID.
|
||||
Timer(usize),
|
||||
}
|
||||
|
||||
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))?;
|
||||
let kqueue_fd = kqueue::kqueue()?;
|
||||
fcntl_setfd(&kqueue_fd, FdFlags::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,
|
||||
sources: RwLock::new(HashSet::new()),
|
||||
notify: notify::Notify::new()?,
|
||||
};
|
||||
poller.interest(
|
||||
poller.read_stream.as_raw_fd(),
|
||||
Event {
|
||||
key: crate::NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
log::debug!(
|
||||
"new: kqueue_fd={}, read_stream={:?}",
|
||||
kqueue_fd,
|
||||
poller.read_stream
|
||||
// Register the notification pipe.
|
||||
poller.notify.register(&poller)?;
|
||||
|
||||
tracing::trace!(
|
||||
kqueue_fd = ?poller.kqueue_fd.as_raw_fd(),
|
||||
"new"
|
||||
);
|
||||
Ok(poller)
|
||||
}
|
||||
|
||||
/// Inserts a file descriptor.
|
||||
pub fn insert(&self, fd: RawFd) -> io::Result<()> {
|
||||
if fd != self.read_stream.as_raw_fd() {
|
||||
log::debug!("insert: fd={}", fd);
|
||||
}
|
||||
|
||||
// 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(())
|
||||
/// Whether this poller supports level-triggered events.
|
||||
pub fn supports_level(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
if fd != self.read_stream.as_raw_fd() {
|
||||
log::debug!(
|
||||
"interest: kqueue_fd={}, fd={}, ev={:?}",
|
||||
self.kqueue_fd,
|
||||
fd,
|
||||
ev
|
||||
/// Whether this poller supports edge-triggered events.
|
||||
pub fn supports_edge(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Adds a new file descriptor.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The file descriptor must be valid and it must last until it is deleted.
|
||||
pub unsafe fn add(&self, fd: RawFd, ev: Event, mode: PollMode) -> io::Result<()> {
|
||||
self.add_source(SourceId::Fd(fd))?;
|
||||
|
||||
// File descriptors don't need to be added explicitly, so just modify the interest.
|
||||
self.modify(BorrowedFd::borrow_raw(fd), ev, mode)
|
||||
}
|
||||
|
||||
/// Modifies an existing file descriptor.
|
||||
pub fn modify(&self, fd: BorrowedFd<'_>, ev: Event, mode: PollMode) -> io::Result<()> {
|
||||
let span = if !self.notify.has_fd(fd) {
|
||||
let span = tracing::trace_span!(
|
||||
"add",
|
||||
kqueue_fd = ?self.kqueue_fd.as_raw_fd(),
|
||||
?fd,
|
||||
?ev,
|
||||
);
|
||||
}
|
||||
Some(span)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let _enter = span.as_ref().map(|s| s.enter());
|
||||
|
||||
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;
|
||||
self.has_source(SourceId::Fd(fd.as_raw_fd()))?;
|
||||
|
||||
let mode_flags = mode_to_flags(mode);
|
||||
|
||||
let read_flags = if ev.readable {
|
||||
kqueue::EventFlags::ADD | mode_flags
|
||||
} else {
|
||||
read_flags |= libc::EV_DELETE;
|
||||
}
|
||||
if ev.writable {
|
||||
write_flags |= libc::EV_ADD;
|
||||
kqueue::EventFlags::DELETE
|
||||
};
|
||||
let write_flags = if ev.writable {
|
||||
kqueue::EventFlags::ADD | mode_flags
|
||||
} else {
|
||||
write_flags |= libc::EV_DELETE;
|
||||
}
|
||||
kqueue::EventFlags::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 _,
|
||||
},
|
||||
kqueue::Event::new(
|
||||
kqueue::EventFilter::Read(fd.as_raw_fd()),
|
||||
read_flags | kqueue::EventFlags::RECEIPT,
|
||||
ev.key as _,
|
||||
),
|
||||
kqueue::Event::new(
|
||||
kqueue::EventFilter::Write(fd.as_raw_fd()),
|
||||
write_flags | kqueue::EventFlags::RECEIPT,
|
||||
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(),
|
||||
))?;
|
||||
self.submit_changes(changelist)
|
||||
}
|
||||
|
||||
/// Submit one or more changes to the kernel queue and check to see if they succeeded.
|
||||
pub(crate) fn submit_changes<A>(&self, changelist: A) -> io::Result<()>
|
||||
where
|
||||
A: Copy + AsRef<[kqueue::Event]> + AsMut<[kqueue::Event]>,
|
||||
{
|
||||
let mut eventlist = Vec::with_capacity(changelist.as_ref().len());
|
||||
|
||||
// Apply changes.
|
||||
{
|
||||
let changelist = changelist.as_ref();
|
||||
|
||||
unsafe {
|
||||
kqueue::kevent(&self.kqueue_fd, changelist, &mut eventlist, None)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors.
|
||||
for ev in &eventlist {
|
||||
for &ev in &eventlist {
|
||||
let data = ev.data();
|
||||
|
||||
// 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 _
|
||||
if (ev.flags().contains(kqueue::EventFlags::ERROR))
|
||||
&& data != 0
|
||||
&& data != Errno::NOENT.raw_os_error() as _
|
||||
&& data != Errno::PIPE.raw_os_error() as _
|
||||
{
|
||||
return Err(io::Error::from_raw_os_error(ev.data as _));
|
||||
return Err(io::Error::from_raw_os_error(data as _));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a file descriptor.
|
||||
pub fn remove(&self, fd: RawFd) -> io::Result<()> {
|
||||
if fd != self.read_stream.as_raw_fd() {
|
||||
log::debug!("remove: kqueue_fd={}, fd={}", self.kqueue_fd, fd);
|
||||
/// Add a source to the sources set.
|
||||
#[inline]
|
||||
pub(crate) fn add_source(&self, source: SourceId) -> io::Result<()> {
|
||||
if self
|
||||
.sources
|
||||
.write()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.insert(source)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::from(io::ErrorKind::AlreadyExists))
|
||||
}
|
||||
}
|
||||
|
||||
// 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 _));
|
||||
}
|
||||
/// Tell if a source is currently inside the set.
|
||||
#[inline]
|
||||
pub(crate) fn has_source(&self, source: SourceId) -> io::Result<()> {
|
||||
if self
|
||||
.sources
|
||||
.read()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.contains(&source)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::from(io::ErrorKind::NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/// Remove a source from the sources set.
|
||||
#[inline]
|
||||
pub(crate) fn remove_source(&self, source: SourceId) -> io::Result<()> {
|
||||
if self
|
||||
.sources
|
||||
.write()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.remove(&source)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::from(io::ErrorKind::NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes a file descriptor.
|
||||
pub fn delete(&self, fd: BorrowedFd<'_>) -> io::Result<()> {
|
||||
// Simply delete interest in the file descriptor.
|
||||
self.modify(fd, Event::none(0), PollMode::Oneshot)?;
|
||||
|
||||
self.remove_source(SourceId::Fd(fd.as_raw_fd()))
|
||||
}
|
||||
|
||||
/// Waits for I/O events with an optional timeout.
|
||||
pub fn wait(&self, events: &mut Events, timeout: Option<Duration>) -> io::Result<()> {
|
||||
log::debug!("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,
|
||||
});
|
||||
let span = tracing::trace_span!(
|
||||
"wait",
|
||||
kqueue_fd = ?self.kqueue_fd.as_raw_fd(),
|
||||
?timeout,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
// 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);
|
||||
let res = unsafe { kqueue::kevent(&self.kqueue_fd, &changelist, eventlist, timeout)? };
|
||||
|
||||
tracing::trace!(
|
||||
kqueue_fd = ?self.kqueue_fd.as_raw_fd(),
|
||||
?res,
|
||||
"new events",
|
||||
);
|
||||
|
||||
// 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: crate::NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
)?;
|
||||
self.notify.reregister(self)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a notification to wake up the current or next `wait()` call.
|
||||
pub fn notify(&self) -> io::Result<()> {
|
||||
log::debug!("notify: kqueue_fd={}", self.kqueue_fd);
|
||||
let _ = (&self.write_stream).write(&[1]);
|
||||
let span = tracing::trace_span!(
|
||||
"notify",
|
||||
kqueue_fd = ?self.kqueue_fd.as_raw_fd(),
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
self.notify.notify(self).ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Poller {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.kqueue_fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for Poller {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.kqueue_fd.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Poller {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("drop: kqueue_fd={}", self.kqueue_fd);
|
||||
let _ = self.remove(self.read_stream.as_raw_fd());
|
||||
let _ = syscall!(close(self.kqueue_fd));
|
||||
let span = tracing::trace_span!(
|
||||
"drop",
|
||||
kqueue_fd = ?self.kqueue_fd.as_raw_fd(),
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
let _ = self.notify.deregister(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of reported I/O events.
|
||||
pub struct Events {
|
||||
list: Box<[libc::kevent]>,
|
||||
len: usize,
|
||||
list: Vec<kqueue::Event>,
|
||||
}
|
||||
|
||||
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 }
|
||||
pub fn with_capacity(cap: usize) -> Events {
|
||||
Events {
|
||||
list: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over I/O events.
|
||||
|
@ -268,11 +309,259 @@ impl Events {
|
|||
// 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),
|
||||
self.list.iter().map(|ev| Event {
|
||||
key: ev.udata() as usize,
|
||||
readable: matches!(
|
||||
ev.filter(),
|
||||
kqueue::EventFilter::Read(..)
|
||||
| kqueue::EventFilter::Vnode { .. }
|
||||
| kqueue::EventFilter::Proc { .. }
|
||||
| kqueue::EventFilter::Signal { .. }
|
||||
| kqueue::EventFilter::Timer { .. }
|
||||
),
|
||||
writable: matches!(ev.filter(), kqueue::EventFilter::Write(..))
|
||||
|| (matches!(ev.filter(), kqueue::EventFilter::Read(..))
|
||||
&& (ev.flags().intersects(kqueue::EventFlags::EOF))),
|
||||
extra: EventExtra,
|
||||
})
|
||||
}
|
||||
|
||||
/// Clears the list.
|
||||
pub fn clear(&mut self) {
|
||||
self.list.clear();
|
||||
}
|
||||
|
||||
/// Get the capacity of the list.
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.list.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra information associated with an event.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct EventExtra;
|
||||
|
||||
impl EventExtra {
|
||||
/// Create a new, empty version of this struct.
|
||||
#[inline]
|
||||
pub const fn empty() -> EventExtra {
|
||||
EventExtra
|
||||
}
|
||||
|
||||
/// Set the interrupt flag.
|
||||
#[inline]
|
||||
pub fn set_hup(&mut self, _value: bool) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
/// Set the priority flag.
|
||||
#[inline]
|
||||
pub fn set_pri(&mut self, _value: bool) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
/// Is the interrupt flag set?
|
||||
#[inline]
|
||||
pub fn is_hup(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Is the priority flag set?
|
||||
#[inline]
|
||||
pub fn is_pri(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mode_to_flags(mode: PollMode) -> kqueue::EventFlags {
|
||||
use kqueue::EventFlags as EV;
|
||||
|
||||
match mode {
|
||||
PollMode::Oneshot => EV::ONESHOT,
|
||||
PollMode::Level => EV::empty(),
|
||||
PollMode::Edge => EV::CLEAR,
|
||||
PollMode::EdgeOneshot => EV::ONESHOT | EV::CLEAR,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
))]
|
||||
mod notify {
|
||||
use super::Poller;
|
||||
use rustix::event::kqueue;
|
||||
use std::io;
|
||||
use std::os::unix::io::BorrowedFd;
|
||||
|
||||
/// A notification pipe.
|
||||
///
|
||||
/// This implementation uses `EVFILT_USER` to avoid allocating a pipe.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Notify;
|
||||
|
||||
impl Notify {
|
||||
/// Creates a new notification pipe.
|
||||
pub(super) fn new() -> io::Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
/// Registers this notification pipe in the `Poller`.
|
||||
pub(super) fn register(&self, poller: &Poller) -> io::Result<()> {
|
||||
// Register an EVFILT_USER event.
|
||||
poller.submit_changes([kqueue::Event::new(
|
||||
kqueue::EventFilter::User {
|
||||
ident: 0,
|
||||
flags: kqueue::UserFlags::empty(),
|
||||
user_flags: kqueue::UserDefinedFlags::new(0),
|
||||
},
|
||||
kqueue::EventFlags::ADD | kqueue::EventFlags::RECEIPT | kqueue::EventFlags::CLEAR,
|
||||
crate::NOTIFY_KEY as _,
|
||||
)])
|
||||
}
|
||||
|
||||
/// Reregister this notification pipe in the `Poller`.
|
||||
pub(super) fn reregister(&self, _poller: &Poller) -> io::Result<()> {
|
||||
// We don't need to do anything, it's already registered as EV_CLEAR.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Notifies the `Poller`.
|
||||
pub(super) fn notify(&self, poller: &Poller) -> io::Result<()> {
|
||||
// Trigger the EVFILT_USER event.
|
||||
poller.submit_changes([kqueue::Event::new(
|
||||
kqueue::EventFilter::User {
|
||||
ident: 0,
|
||||
flags: kqueue::UserFlags::TRIGGER,
|
||||
user_flags: kqueue::UserDefinedFlags::new(0),
|
||||
},
|
||||
kqueue::EventFlags::ADD | kqueue::EventFlags::RECEIPT,
|
||||
crate::NOTIFY_KEY as _,
|
||||
)])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deregisters this notification pipe from the `Poller`.
|
||||
pub(super) fn deregister(&self, poller: &Poller) -> io::Result<()> {
|
||||
// Deregister the EVFILT_USER event.
|
||||
poller.submit_changes([kqueue::Event::new(
|
||||
kqueue::EventFilter::User {
|
||||
ident: 0,
|
||||
flags: kqueue::UserFlags::empty(),
|
||||
user_flags: kqueue::UserDefinedFlags::new(0),
|
||||
},
|
||||
kqueue::EventFlags::DELETE | kqueue::EventFlags::RECEIPT,
|
||||
crate::NOTIFY_KEY as _,
|
||||
)])
|
||||
}
|
||||
|
||||
/// Whether this raw file descriptor is associated with this pipe.
|
||||
pub(super) fn has_fd(&self, _fd: BorrowedFd<'_>) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
)))]
|
||||
mod notify {
|
||||
use super::Poller;
|
||||
use crate::{Event, PollMode, NOTIFY_KEY};
|
||||
use std::io::{self, prelude::*};
|
||||
use std::os::unix::{
|
||||
io::{AsFd, AsRawFd, BorrowedFd},
|
||||
net::UnixStream,
|
||||
};
|
||||
|
||||
/// A notification pipe.
|
||||
///
|
||||
/// This implementation uses a pipe to send notifications.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Notify {
|
||||
/// The read end of the pipe.
|
||||
read_stream: UnixStream,
|
||||
|
||||
/// The write end of the pipe.
|
||||
write_stream: UnixStream,
|
||||
}
|
||||
|
||||
impl Notify {
|
||||
/// Creates a new notification pipe.
|
||||
pub(super) fn new() -> io::Result<Self> {
|
||||
let (read_stream, write_stream) = UnixStream::pair()?;
|
||||
read_stream.set_nonblocking(true)?;
|
||||
write_stream.set_nonblocking(true)?;
|
||||
|
||||
Ok(Self {
|
||||
read_stream,
|
||||
write_stream,
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers this notification pipe in the `Poller`.
|
||||
pub(super) fn register(&self, poller: &Poller) -> io::Result<()> {
|
||||
// Register the read end of this pipe.
|
||||
unsafe {
|
||||
poller.add(
|
||||
self.read_stream.as_raw_fd(),
|
||||
Event::readable(NOTIFY_KEY),
|
||||
PollMode::Oneshot,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reregister this notification pipe in the `Poller`.
|
||||
pub(super) fn reregister(&self, poller: &Poller) -> io::Result<()> {
|
||||
// Clear out the notification.
|
||||
while (&self.read_stream).read(&mut [0; 64]).is_ok() {}
|
||||
|
||||
// Reregister the read end of this pipe.
|
||||
poller.modify(
|
||||
self.read_stream.as_fd(),
|
||||
Event::readable(NOTIFY_KEY),
|
||||
PollMode::Oneshot,
|
||||
)
|
||||
}
|
||||
|
||||
/// Notifies the `Poller`.
|
||||
#[allow(clippy::unused_io_amount)]
|
||||
pub(super) fn notify(&self, _poller: &Poller) -> io::Result<()> {
|
||||
// Write to the write end of the pipe
|
||||
(&self.write_stream).write(&[1])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deregisters this notification pipe from the `Poller`.
|
||||
pub(super) fn deregister(&self, poller: &Poller) -> io::Result<()> {
|
||||
// Deregister the read end of the pipe.
|
||||
poller.delete(self.read_stream.as_fd())
|
||||
}
|
||||
|
||||
/// Whether this raw file descriptor is associated with this pipe.
|
||||
pub(super) fn has_fd(&self, fd: BorrowedFd<'_>) -> bool {
|
||||
self.read_stream.as_raw_fd() == fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
987
src/lib.rs
987
src/lib.rs
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
|||
//! Platform-specific functionality.
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly",
|
||||
),
|
||||
not(polling_test_poll_backend),
|
||||
))]
|
||||
pub mod kqueue;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod iocp;
|
||||
|
||||
mod __private {
|
||||
#[doc(hidden)]
|
||||
#[allow(dead_code)]
|
||||
pub trait PollerSealed {}
|
||||
|
||||
impl PollerSealed for crate::Poller {}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
//! Functionality that is only available for IOCP-based platforms.
|
||||
|
||||
pub use crate::sys::CompletionPacket;
|
||||
|
||||
use super::__private::PollerSealed;
|
||||
use crate::{Event, PollMode, Poller};
|
||||
|
||||
use std::io;
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
use std::os::windows::prelude::{AsHandle, BorrowedHandle};
|
||||
|
||||
/// Extension trait for the [`Poller`] type that provides functionality specific to IOCP-based
|
||||
/// platforms.
|
||||
///
|
||||
/// [`Poller`]: crate::Poller
|
||||
pub trait PollerIocpExt: PollerSealed {
|
||||
/// Post a new [`Event`] to the poller.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use polling::{Poller, Event, Events};
|
||||
/// use polling::os::iocp::{CompletionPacket, PollerIocpExt};
|
||||
///
|
||||
/// use std::thread;
|
||||
/// use std::sync::Arc;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() -> std::io::Result<()> {
|
||||
/// // Spawn a thread to wake us up after 100ms.
|
||||
/// let poller = Arc::new(Poller::new()?);
|
||||
/// thread::spawn({
|
||||
/// let poller = poller.clone();
|
||||
/// move || {
|
||||
/// let packet = CompletionPacket::new(Event::readable(0));
|
||||
/// thread::sleep(Duration::from_millis(100));
|
||||
/// poller.post(packet).unwrap();
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// // Wait for the event.
|
||||
/// let mut events = Events::new();
|
||||
/// poller.wait(&mut events, None)?;
|
||||
///
|
||||
/// assert_eq!(events.len(), 1);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
fn post(&self, packet: CompletionPacket) -> io::Result<()>;
|
||||
|
||||
/// Add a waitable handle to this poller.
|
||||
///
|
||||
/// Some handles in Windows are "waitable", which means that they emit a "readiness" signal
|
||||
/// after some event occurs. This function can be used to wait for such events to occur
|
||||
/// on a handle. This function can be used in addition to regular socket polling.
|
||||
///
|
||||
/// Waitable objects include the following:
|
||||
///
|
||||
/// - Console inputs
|
||||
/// - Waitable events
|
||||
/// - Mutexes
|
||||
/// - Processes
|
||||
/// - Semaphores
|
||||
/// - Threads
|
||||
/// - Timer
|
||||
///
|
||||
/// Once the object has been signalled, the poller will emit the `interest` event.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The added handle must not be dropped before it is deleted.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use polling::{Poller, Event, Events, PollMode};
|
||||
/// use polling::os::iocp::PollerIocpExt;
|
||||
///
|
||||
/// use std::process::Command;
|
||||
///
|
||||
/// // Spawn a new process.
|
||||
/// let mut child = Command::new("echo")
|
||||
/// .arg("Hello, world!")
|
||||
/// .spawn()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // Create a new poller.
|
||||
/// let poller = Poller::new().unwrap();
|
||||
///
|
||||
/// // Add the child process to the poller.
|
||||
/// unsafe {
|
||||
/// poller.add_waitable(&child, Event::all(0), PollMode::Oneshot).unwrap();
|
||||
/// }
|
||||
///
|
||||
/// // Wait for the child process to exit.
|
||||
/// let mut events = Events::new();
|
||||
/// poller.wait(&mut events, None).unwrap();
|
||||
///
|
||||
/// assert_eq!(events.len(), 1);
|
||||
/// assert_eq!(events.iter().next().unwrap(), Event::all(0));
|
||||
/// ```
|
||||
unsafe fn add_waitable(
|
||||
&self,
|
||||
handle: impl AsRawWaitable,
|
||||
interest: Event,
|
||||
mode: PollMode,
|
||||
) -> io::Result<()>;
|
||||
|
||||
/// Modify an existing waitable handle.
|
||||
///
|
||||
/// This function can be used to change the emitted event and/or mode of an existing waitable
|
||||
/// handle. The handle must have been previously added to the poller using [`add_waitable`].
|
||||
///
|
||||
/// [`add_waitable`]: Self::add_waitable
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use polling::{Poller, Event, Events, PollMode};
|
||||
/// use polling::os::iocp::PollerIocpExt;
|
||||
///
|
||||
/// use std::process::Command;
|
||||
///
|
||||
/// // Spawn a new process.
|
||||
/// let mut child = Command::new("echo")
|
||||
/// .arg("Hello, world!")
|
||||
/// .spawn()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // Create a new poller.
|
||||
/// let poller = Poller::new().unwrap();
|
||||
///
|
||||
/// // Add the child process to the poller.
|
||||
/// unsafe {
|
||||
/// poller.add_waitable(&child, Event::all(0), PollMode::Oneshot).unwrap();
|
||||
/// }
|
||||
///
|
||||
/// // Wait for the child process to exit.
|
||||
/// let mut events = Events::new();
|
||||
/// poller.wait(&mut events, None).unwrap();
|
||||
///
|
||||
/// assert_eq!(events.len(), 1);
|
||||
/// assert_eq!(events.iter().next().unwrap(), Event::all(0));
|
||||
///
|
||||
/// // Modify the waitable handle.
|
||||
/// poller.modify_waitable(&child, Event::readable(0), PollMode::Oneshot).unwrap();
|
||||
/// ```
|
||||
fn modify_waitable(
|
||||
&self,
|
||||
handle: impl AsWaitable,
|
||||
interest: Event,
|
||||
mode: PollMode,
|
||||
) -> io::Result<()>;
|
||||
|
||||
/// Remove a waitable handle from this poller.
|
||||
///
|
||||
/// This function can be used to remove a waitable handle from the poller. The handle must
|
||||
/// have been previously added to the poller using [`add_waitable`].
|
||||
///
|
||||
/// [`add_waitable`]: Self::add_waitable
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use polling::{Poller, Event, Events, PollMode};
|
||||
/// use polling::os::iocp::PollerIocpExt;
|
||||
///
|
||||
/// use std::process::Command;
|
||||
///
|
||||
/// // Spawn a new process.
|
||||
/// let mut child = Command::new("echo")
|
||||
/// .arg("Hello, world!")
|
||||
/// .spawn()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // Create a new poller.
|
||||
/// let poller = Poller::new().unwrap();
|
||||
///
|
||||
/// // Add the child process to the poller.
|
||||
/// unsafe {
|
||||
/// poller.add_waitable(&child, Event::all(0), PollMode::Oneshot).unwrap();
|
||||
/// }
|
||||
///
|
||||
/// // Wait for the child process to exit.
|
||||
/// let mut events = Events::new();
|
||||
/// poller.wait(&mut events, None).unwrap();
|
||||
///
|
||||
/// assert_eq!(events.len(), 1);
|
||||
/// assert_eq!(events.iter().next().unwrap(), Event::all(0));
|
||||
///
|
||||
/// // Remove the waitable handle.
|
||||
/// poller.remove_waitable(&child).unwrap();
|
||||
/// ```
|
||||
fn remove_waitable(&self, handle: impl AsWaitable) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl PollerIocpExt for Poller {
|
||||
fn post(&self, packet: CompletionPacket) -> io::Result<()> {
|
||||
self.poller.post(packet)
|
||||
}
|
||||
|
||||
unsafe fn add_waitable(
|
||||
&self,
|
||||
handle: impl AsRawWaitable,
|
||||
event: Event,
|
||||
mode: PollMode,
|
||||
) -> io::Result<()> {
|
||||
self.poller
|
||||
.add_waitable(handle.as_raw_handle(), event, mode)
|
||||
}
|
||||
|
||||
fn modify_waitable(
|
||||
&self,
|
||||
handle: impl AsWaitable,
|
||||
interest: Event,
|
||||
mode: PollMode,
|
||||
) -> io::Result<()> {
|
||||
self.poller
|
||||
.modify_waitable(handle.as_waitable().as_raw_handle(), interest, mode)
|
||||
}
|
||||
|
||||
fn remove_waitable(&self, handle: impl AsWaitable) -> io::Result<()> {
|
||||
self.poller
|
||||
.remove_waitable(handle.as_waitable().as_raw_handle())
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that represents a waitable handle.
|
||||
pub trait AsRawWaitable {
|
||||
/// Returns the raw handle of this waitable.
|
||||
fn as_raw_handle(&self) -> RawHandle;
|
||||
}
|
||||
|
||||
impl AsRawWaitable for RawHandle {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRawHandle + ?Sized> AsRawWaitable for &T {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
AsRawHandle::as_raw_handle(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that represents a waitable handle.
|
||||
pub trait AsWaitable: AsHandle {
|
||||
/// Returns the raw handle of this waitable.
|
||||
fn as_waitable(&self) -> BorrowedHandle<'_> {
|
||||
self.as_handle()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsHandle + ?Sized> AsWaitable for T {}
|
|
@ -0,0 +1,303 @@
|
|||
//! Functionality that is only available for `kqueue`-based platforms.
|
||||
|
||||
use crate::sys::{mode_to_flags, SourceId};
|
||||
use crate::{PollMode, Poller};
|
||||
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::process::Child;
|
||||
use std::time::Duration;
|
||||
|
||||
use rustix::event::kqueue;
|
||||
|
||||
use super::__private::PollerSealed;
|
||||
use __private::FilterSealed;
|
||||
|
||||
// TODO(notgull): We should also have EVFILT_AIO, EVFILT_VNODE and EVFILT_USER. However, the current
|
||||
// API makes it difficult to effectively express events from these filters. At the next breaking
|
||||
// change, we should change `Event` to be a struct with private fields, and encode additional
|
||||
// information in there.
|
||||
|
||||
/// Functionality that is only available for `kqueue`-based platforms.
|
||||
///
|
||||
/// `kqueue` is able to monitor much more than just read/write readiness on file descriptors. Using
|
||||
/// this extension trait, you can monitor for signals, process exits, and more. See the implementors
|
||||
/// of the [`Filter`] trait for more information.
|
||||
pub trait PollerKqueueExt<F: Filter>: PollerSealed {
|
||||
/// Add a filter to the poller.
|
||||
///
|
||||
/// This is similar to [`add`][Poller::add], but it allows you to specify a filter instead of
|
||||
/// a socket. See the implementors of the [`Filter`] trait for more information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use polling::{Events, Poller, PollMode};
|
||||
/// use polling::os::kqueue::{Filter, PollerKqueueExt, Signal};
|
||||
///
|
||||
/// let poller = Poller::new().unwrap();
|
||||
///
|
||||
/// // Register the SIGINT signal.
|
||||
/// poller.add_filter(Signal(rustix::process::Signal::Int as _), 0, PollMode::Oneshot).unwrap();
|
||||
///
|
||||
/// // Wait for the signal.
|
||||
/// let mut events = Events::new();
|
||||
/// poller.wait(&mut events, None).unwrap();
|
||||
/// # let _ = events;
|
||||
/// ```
|
||||
fn add_filter(&self, filter: F, key: usize, mode: PollMode) -> io::Result<()>;
|
||||
|
||||
/// Modify a filter in the poller.
|
||||
///
|
||||
/// This is similar to [`modify`][Poller::modify], but it allows you to specify a filter
|
||||
/// instead of a socket. See the implementors of the [`Filter`] trait for more information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use polling::{Events, Poller, PollMode};
|
||||
/// use polling::os::kqueue::{Filter, PollerKqueueExt, Signal};
|
||||
///
|
||||
/// let poller = Poller::new().unwrap();
|
||||
///
|
||||
/// // Register the SIGINT signal.
|
||||
/// poller.add_filter(Signal(rustix::process::Signal::Int as _), 0, PollMode::Oneshot).unwrap();
|
||||
///
|
||||
/// // Re-register with a different key.
|
||||
/// poller.modify_filter(Signal(rustix::process::Signal::Int as _), 1, PollMode::Oneshot).unwrap();
|
||||
///
|
||||
/// // Wait for the signal.
|
||||
/// let mut events = Events::new();
|
||||
/// poller.wait(&mut events, None).unwrap();
|
||||
/// # let _ = events;
|
||||
/// ```
|
||||
fn modify_filter(&self, filter: F, key: usize, mode: PollMode) -> io::Result<()>;
|
||||
|
||||
/// Remove a filter from the poller.
|
||||
///
|
||||
/// This is used to remove filters that were previously added with
|
||||
/// [`add_filter`](PollerKqueueExt::add_filter).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use polling::{Poller, PollMode};
|
||||
/// use polling::os::kqueue::{Filter, PollerKqueueExt, Signal};
|
||||
///
|
||||
/// let poller = Poller::new().unwrap();
|
||||
///
|
||||
/// // Register the SIGINT signal.
|
||||
/// poller.add_filter(Signal(rustix::process::Signal::Int as _), 0, PollMode::Oneshot).unwrap();
|
||||
///
|
||||
/// // Remove the filter.
|
||||
/// poller.delete_filter(Signal(rustix::process::Signal::Int as _)).unwrap();
|
||||
/// ```
|
||||
fn delete_filter(&self, filter: F) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<F: Filter> PollerKqueueExt<F> for Poller {
|
||||
#[inline(always)]
|
||||
fn add_filter(&self, filter: F, key: usize, mode: PollMode) -> io::Result<()> {
|
||||
// No difference between adding and modifying in kqueue.
|
||||
self.poller.add_source(filter.source_id())?;
|
||||
self.modify_filter(filter, key, mode)
|
||||
}
|
||||
|
||||
fn modify_filter(&self, filter: F, key: usize, mode: PollMode) -> io::Result<()> {
|
||||
self.poller.has_source(filter.source_id())?;
|
||||
|
||||
// Convert the filter into a kevent.
|
||||
let event = filter.filter(kqueue::EventFlags::ADD | mode_to_flags(mode), key);
|
||||
|
||||
// Modify the filter.
|
||||
self.poller.submit_changes([event])
|
||||
}
|
||||
|
||||
fn delete_filter(&self, filter: F) -> io::Result<()> {
|
||||
// Convert the filter into a kevent.
|
||||
let event = filter.filter(kqueue::EventFlags::DELETE, 0);
|
||||
|
||||
// Delete the filter.
|
||||
self.poller.submit_changes([event])?;
|
||||
|
||||
self.poller.remove_source(filter.source_id())
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter that can be registered into a `kqueue`.
|
||||
pub trait Filter: FilterSealed {}
|
||||
|
||||
unsafe impl<T: FilterSealed + ?Sized> FilterSealed for &T {
|
||||
#[inline(always)]
|
||||
fn filter(&self, flags: kqueue::EventFlags, key: usize) -> kqueue::Event {
|
||||
(**self).filter(flags, key)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn source_id(&self) -> SourceId {
|
||||
(**self).source_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Filter + ?Sized> Filter for &T {}
|
||||
|
||||
/// Monitor this signal number.
|
||||
///
|
||||
/// No matter what `PollMode` is specified, this filter will always be
|
||||
/// oneshot-only.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Signal(pub std::os::raw::c_int);
|
||||
|
||||
unsafe impl FilterSealed for Signal {
|
||||
#[inline(always)]
|
||||
fn filter(&self, flags: kqueue::EventFlags, key: usize) -> kqueue::Event {
|
||||
kqueue::Event::new(
|
||||
kqueue::EventFilter::Signal {
|
||||
signal: rustix::process::Signal::from_raw(self.0).expect("invalid signal number"),
|
||||
times: 0,
|
||||
},
|
||||
flags | kqueue::EventFlags::RECEIPT,
|
||||
key as _,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn source_id(&self) -> SourceId {
|
||||
SourceId::Signal(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for Signal {}
|
||||
|
||||
/// Monitor a child process.
|
||||
#[derive(Debug)]
|
||||
pub struct Process<'a> {
|
||||
/// The process ID to monitor.
|
||||
pid: rustix::process::Pid,
|
||||
|
||||
/// The operation to monitor.
|
||||
ops: ProcessOps,
|
||||
|
||||
/// Lifetime of the underlying process.
|
||||
_lt: PhantomData<&'a Child>,
|
||||
}
|
||||
|
||||
/// The operations that a monitored process can perform.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ProcessOps {
|
||||
/// The process exited.
|
||||
Exit,
|
||||
|
||||
/// The process was forked.
|
||||
Fork,
|
||||
|
||||
/// The process executed a new process.
|
||||
Exec,
|
||||
}
|
||||
|
||||
impl<'a> Process<'a> {
|
||||
/// Monitor a child process.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Once registered into the `Poller`, the `Child` object must outlive this filter's
|
||||
/// registration into the poller.
|
||||
pub unsafe fn new(child: &'a Child, ops: ProcessOps) -> Self {
|
||||
Self {
|
||||
pid: rustix::process::Pid::from_child(child),
|
||||
ops,
|
||||
_lt: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Process` from a PID.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The PID must be tied to an actual child process.
|
||||
pub unsafe fn from_pid(pid: std::num::NonZeroI32, ops: ProcessOps) -> Self {
|
||||
Self {
|
||||
pid: unsafe { rustix::process::Pid::from_raw_unchecked(pid.get()) },
|
||||
ops,
|
||||
_lt: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl FilterSealed for Process<'_> {
|
||||
#[inline(always)]
|
||||
fn filter(&self, flags: kqueue::EventFlags, key: usize) -> kqueue::Event {
|
||||
let events = match self.ops {
|
||||
ProcessOps::Exit => kqueue::ProcessEvents::EXIT,
|
||||
ProcessOps::Fork => kqueue::ProcessEvents::FORK,
|
||||
ProcessOps::Exec => kqueue::ProcessEvents::EXEC,
|
||||
};
|
||||
|
||||
kqueue::Event::new(
|
||||
kqueue::EventFilter::Proc {
|
||||
// SAFETY: We know that the PID is nonzero.
|
||||
pid: self.pid,
|
||||
flags: events,
|
||||
},
|
||||
flags | kqueue::EventFlags::RECEIPT,
|
||||
key as _,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn source_id(&self) -> SourceId {
|
||||
// SAFETY: We know that the PID is nonzero
|
||||
SourceId::Pid(self.pid)
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for Process<'_> {}
|
||||
|
||||
/// Wait for a timeout to expire.
|
||||
///
|
||||
/// Modifying the timeout after it has been added to the poller will reset it.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Timer {
|
||||
/// Identifier for the timer.
|
||||
pub id: usize,
|
||||
|
||||
/// The timeout to wait for.
|
||||
pub timeout: Duration,
|
||||
}
|
||||
|
||||
unsafe impl FilterSealed for Timer {
|
||||
fn filter(&self, flags: kqueue::EventFlags, key: usize) -> kqueue::Event {
|
||||
kqueue::Event::new(
|
||||
kqueue::EventFilter::Timer {
|
||||
ident: self.id as _,
|
||||
timer: Some(self.timeout),
|
||||
},
|
||||
flags | kqueue::EventFlags::RECEIPT,
|
||||
key as _,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn source_id(&self) -> SourceId {
|
||||
SourceId::Timer(self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for Timer {}
|
||||
|
||||
mod __private {
|
||||
use crate::sys::SourceId;
|
||||
use rustix::event::kqueue;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub unsafe trait FilterSealed {
|
||||
/// Get the filter for the given event.
|
||||
///
|
||||
/// This filter's flags must have `EV_RECEIPT`.
|
||||
fn filter(&self, flags: kqueue::EventFlags, key: usize) -> kqueue::Event;
|
||||
|
||||
/// Get the source ID for this source.
|
||||
fn source_id(&self) -> SourceId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,830 @@
|
|||
//! Bindings to poll (VxWorks, Fuchsia, other Unix systems).
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Condvar, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(not(target_os = "hermit"))]
|
||||
use rustix::fd::{AsFd, AsRawFd, BorrowedFd};
|
||||
#[cfg(target_os = "hermit")]
|
||||
use std::os::hermit::io::{AsFd, AsRawFd, BorrowedFd};
|
||||
|
||||
use syscall::{poll, PollFd, PollFlags};
|
||||
|
||||
// std::os::unix doesn't exist on Fuchsia
|
||||
type RawFd = std::os::raw::c_int;
|
||||
|
||||
use crate::{Event, PollMode};
|
||||
|
||||
/// Interface to poll.
|
||||
#[derive(Debug)]
|
||||
pub struct Poller {
|
||||
/// File descriptors to poll.
|
||||
fds: Mutex<Fds>,
|
||||
/// Notification pipe for waking up the poller.
|
||||
///
|
||||
/// On all platforms except ESP IDF, the `pipe` syscall is used.
|
||||
/// On ESP IDF, the `eventfd` syscall is used instead.
|
||||
notify: notify::Notify,
|
||||
/// 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<PollFd<'static>>,
|
||||
/// 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<RawFd, FdData>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// Whether to remove this file descriptor from the poller on the next call to `wait`.
|
||||
remove: bool,
|
||||
}
|
||||
|
||||
impl Poller {
|
||||
/// Creates a new poller.
|
||||
pub fn new() -> io::Result<Poller> {
|
||||
let notify = notify::Notify::new()?;
|
||||
|
||||
tracing::trace!(?notify, "new");
|
||||
|
||||
Ok(Self {
|
||||
fds: Mutex::new(Fds {
|
||||
poll_fds: vec![PollFd::from_borrowed_fd(
|
||||
// SAFETY: `notify.fd()` will remain valid until we drop `self`.
|
||||
unsafe { BorrowedFd::borrow_raw(notify.fd().as_raw_fd()) },
|
||||
notify.poll_flags(),
|
||||
)],
|
||||
fd_data: HashMap::new(),
|
||||
}),
|
||||
notify,
|
||||
waiting_operations: AtomicUsize::new(0),
|
||||
operations_complete: Condvar::new(),
|
||||
notified: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether this poller supports level-triggered events.
|
||||
pub fn supports_level(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether the poller supports edge-triggered events.
|
||||
pub fn supports_edge(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Adds a new file descriptor.
|
||||
pub fn add(&self, fd: RawFd, ev: Event, mode: PollMode) -> io::Result<()> {
|
||||
if self.notify.has_fd(fd) {
|
||||
return Err(io::Error::from(io::ErrorKind::InvalidInput));
|
||||
}
|
||||
|
||||
let span = tracing::trace_span!(
|
||||
"add",
|
||||
notify_read = ?self.notify.fd().as_raw_fd(),
|
||||
?fd,
|
||||
?ev,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
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,
|
||||
remove: cvt_mode_as_remove(mode)?,
|
||||
},
|
||||
);
|
||||
|
||||
fds.poll_fds.push(PollFd::from_borrowed_fd(
|
||||
// SAFETY: Until we have I/O safety, assume that `fd` is valid forever.
|
||||
unsafe { BorrowedFd::borrow_raw(fd) },
|
||||
poll_events(ev),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Modifies an existing file descriptor.
|
||||
pub fn modify(&self, fd: BorrowedFd<'_>, ev: Event, mode: PollMode) -> io::Result<()> {
|
||||
if self.notify.has_fd(fd.as_raw_fd()) {
|
||||
return Err(io::Error::from(io::ErrorKind::InvalidInput));
|
||||
}
|
||||
|
||||
let span = tracing::trace_span!(
|
||||
"modify",
|
||||
notify_read = ?self.notify.fd().as_raw_fd(),
|
||||
?fd,
|
||||
?ev,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
self.modify_fds(|fds| {
|
||||
let data = fds
|
||||
.fd_data
|
||||
.get_mut(&fd.as_raw_fd())
|
||||
.ok_or(io::ErrorKind::NotFound)?;
|
||||
data.key = ev.key;
|
||||
let poll_fds_index = data.poll_fds_index;
|
||||
|
||||
// SAFETY: This is essentially transmuting a `PollFd<'a>` to a `PollFd<'static>`, which
|
||||
// only works if it's removed in time with `delete()`.
|
||||
fds.poll_fds[poll_fds_index] = PollFd::from_borrowed_fd(
|
||||
unsafe { BorrowedFd::borrow_raw(fd.as_raw_fd()) },
|
||||
poll_events(ev),
|
||||
);
|
||||
data.remove = cvt_mode_as_remove(mode)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Deletes a file descriptor.
|
||||
pub fn delete(&self, fd: BorrowedFd<'_>) -> io::Result<()> {
|
||||
if self.notify.has_fd(fd.as_raw_fd()) {
|
||||
return Err(io::Error::from(io::ErrorKind::InvalidInput));
|
||||
}
|
||||
|
||||
let span = tracing::trace_span!(
|
||||
"delete",
|
||||
notify_read = ?self.notify.fd().as_raw_fd(),
|
||||
?fd,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
self.modify_fds(|fds| {
|
||||
let data = fds
|
||||
.fd_data
|
||||
.remove(&fd.as_raw_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.as_fd().as_raw_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<Duration>) -> io::Result<()> {
|
||||
let span = tracing::trace_span!(
|
||||
"wait",
|
||||
notify_read = ?self.notify.fd().as_raw_fd(),
|
||||
?timeout,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
let deadline = timeout.and_then(|t| Instant::now().checked_add(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.notify.pop_notification();
|
||||
} else if self.waiting_operations.load(Ordering::SeqCst) == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
fds = self.operations_complete.wait(fds).unwrap();
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Perform the poll.
|
||||
let num_events = poll(&mut fds.poll_fds, timeout_ms)?;
|
||||
let notified = !fds.poll_fds[0].revents().is_empty();
|
||||
let num_fd_events = if notified { num_events - 1 } else { num_events };
|
||||
tracing::trace!(?num_events, ?notified, ?num_fd_events, "new events",);
|
||||
|
||||
// Read all notifications.
|
||||
if notified {
|
||||
self.notify.pop_all_notifications()?;
|
||||
}
|
||||
|
||||
// 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 poll_fd = &mut fds.poll_fds[fd_data.poll_fds_index];
|
||||
if !poll_fd.revents().is_empty() {
|
||||
// Store event
|
||||
let revents = poll_fd.revents();
|
||||
events.inner.push(Event {
|
||||
key: fd_data.key,
|
||||
readable: revents.intersects(read_events()),
|
||||
writable: revents.intersects(write_events()),
|
||||
extra: EventExtra { flags: revents },
|
||||
});
|
||||
// Remove interest if necessary
|
||||
if fd_data.remove {
|
||||
*poll_fd = PollFd::from_borrowed_fd(
|
||||
unsafe { BorrowedFd::borrow_raw(poll_fd.as_fd().as_raw_fd()) },
|
||||
PollFlags::empty(),
|
||||
);
|
||||
}
|
||||
|
||||
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<()> {
|
||||
let span = tracing::trace_span!(
|
||||
"notify",
|
||||
notify_read = ?self.notify.fd().as_raw_fd(),
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
if !self.notified.swap(true, Ordering::SeqCst) {
|
||||
self.notify.notify()?;
|
||||
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.notify().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.notify.pop_notification();
|
||||
}
|
||||
|
||||
let res = f(&mut fds);
|
||||
|
||||
if self.waiting_operations.fetch_sub(1, Ordering::SeqCst) == 1 {
|
||||
self.operations_complete.notify_one();
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the input poll events for the given event.
|
||||
fn poll_events(ev: Event) -> PollFlags {
|
||||
(if ev.readable {
|
||||
PollFlags::IN | PollFlags::PRI
|
||||
} else {
|
||||
PollFlags::empty()
|
||||
}) | (if ev.writable {
|
||||
PollFlags::OUT | PollFlags::WRBAND
|
||||
} else {
|
||||
PollFlags::empty()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returned poll events for reading.
|
||||
fn read_events() -> PollFlags {
|
||||
PollFlags::IN | PollFlags::PRI | PollFlags::HUP | PollFlags::ERR
|
||||
}
|
||||
|
||||
/// Returned poll events for writing.
|
||||
fn write_events() -> PollFlags {
|
||||
PollFlags::OUT | PollFlags::WRBAND | PollFlags::HUP | PollFlags::ERR
|
||||
}
|
||||
|
||||
/// A list of reported I/O events.
|
||||
pub struct Events {
|
||||
inner: Vec<Event>,
|
||||
}
|
||||
|
||||
impl Events {
|
||||
/// Creates an empty list.
|
||||
pub fn with_capacity(cap: usize) -> Events {
|
||||
Self {
|
||||
inner: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over I/O events.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Event> + '_ {
|
||||
self.inner.iter().copied()
|
||||
}
|
||||
|
||||
/// Clear the list.
|
||||
pub fn clear(&mut self) {
|
||||
self.inner.clear();
|
||||
}
|
||||
|
||||
/// Get the capacity of the list.
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.inner.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra information associated with an event.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct EventExtra {
|
||||
/// Flags associated with this event.
|
||||
flags: PollFlags,
|
||||
}
|
||||
|
||||
impl EventExtra {
|
||||
/// Creates an empty set of extra information.
|
||||
#[inline]
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
flags: PollFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the interrupt flag.
|
||||
#[inline]
|
||||
pub fn set_hup(&mut self, value: bool) {
|
||||
self.flags.set(PollFlags::HUP, value);
|
||||
}
|
||||
|
||||
/// Set the priority flag.
|
||||
#[inline]
|
||||
pub fn set_pri(&mut self, value: bool) {
|
||||
self.flags.set(PollFlags::PRI, value);
|
||||
}
|
||||
|
||||
/// Is this an interrupt event?
|
||||
#[inline]
|
||||
pub fn is_hup(&self) -> bool {
|
||||
self.flags.contains(PollFlags::HUP)
|
||||
}
|
||||
|
||||
/// Is this a priority event?
|
||||
#[inline]
|
||||
pub fn is_pri(&self) -> bool {
|
||||
self.flags.contains(PollFlags::PRI)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(PollFlags::ERR) || self.flags.contains(PollFlags::HUP))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(PollFlags::ERR))
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt_mode_as_remove(mode: PollMode) -> io::Result<bool> {
|
||||
match mode {
|
||||
PollMode::Oneshot => Ok(true),
|
||||
PollMode::Level => Ok(false),
|
||||
_ => Err(crate::unsupported_error(
|
||||
"edge-triggered I/O events are not supported in poll()",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod syscall {
|
||||
pub(super) use rustix::event::{poll, PollFd, PollFlags};
|
||||
|
||||
#[cfg(target_os = "espidf")]
|
||||
pub(super) use rustix::event::{eventfd, EventfdFlags};
|
||||
#[cfg(target_os = "espidf")]
|
||||
pub(super) use rustix::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
|
||||
#[cfg(target_os = "espidf")]
|
||||
pub(super) use rustix::io::{read, write};
|
||||
}
|
||||
|
||||
#[cfg(target_os = "hermit")]
|
||||
mod syscall {
|
||||
// TODO: Remove this shim once HermitOS is supported in Rustix.
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::BitOr;
|
||||
|
||||
pub(super) use std::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
|
||||
|
||||
/// Create an eventfd.
|
||||
pub(super) fn eventfd(count: u64, _flags: EventfdFlags) -> io::Result<OwnedFd> {
|
||||
let fd = unsafe { hermit_abi::eventfd(count, 0) };
|
||||
|
||||
if fd < 0 {
|
||||
Err(io::Error::from_raw_os_error(unsafe {
|
||||
hermit_abi::get_errno()
|
||||
}))
|
||||
} else {
|
||||
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Read some bytes.
|
||||
pub(super) fn read(fd: BorrowedFd<'_>, bytes: &mut [u8]) -> io::Result<usize> {
|
||||
let count = unsafe { hermit_abi::read(fd.as_raw_fd(), bytes.as_mut_ptr(), bytes.len()) };
|
||||
|
||||
cvt(count)
|
||||
}
|
||||
|
||||
/// Write some bytes.
|
||||
pub(super) fn write(fd: BorrowedFd<'_>, bytes: &[u8]) -> io::Result<usize> {
|
||||
let count = unsafe { hermit_abi::write(fd.as_raw_fd(), bytes.as_ptr(), bytes.len()) };
|
||||
|
||||
cvt(count)
|
||||
}
|
||||
|
||||
/// Safe wrapper around the `poll` system call.
|
||||
pub(super) fn poll(fds: &mut [PollFd<'_>], timeout: i32) -> io::Result<usize> {
|
||||
let call = unsafe {
|
||||
hermit_abi::poll(
|
||||
fds.as_mut_ptr() as *mut hermit_abi::pollfd,
|
||||
fds.len(),
|
||||
timeout,
|
||||
)
|
||||
};
|
||||
|
||||
cvt(call as isize)
|
||||
}
|
||||
|
||||
/// Safe wrapper around `pollfd`.
|
||||
#[repr(transparent)]
|
||||
pub(super) struct PollFd<'a> {
|
||||
inner: hermit_abi::pollfd,
|
||||
_lt: PhantomData<BorrowedFd<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> PollFd<'a> {
|
||||
pub(super) fn from_borrowed_fd(fd: BorrowedFd<'a>, inflags: PollFlags) -> Self {
|
||||
Self {
|
||||
inner: hermit_abi::pollfd {
|
||||
fd: fd.as_raw_fd(),
|
||||
events: inflags.0,
|
||||
revents: 0,
|
||||
},
|
||||
_lt: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn revents(&self) -> PollFlags {
|
||||
PollFlags(self.inner.revents)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for PollFd<'_> {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.inner.fd) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PollFd<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PollFd")
|
||||
.field("fd", &format_args!("0x{:x}", self.inner.fd))
|
||||
.field("events", &PollFlags(self.inner.events))
|
||||
.field("revents", &PollFlags(self.inner.revents))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around polling flags.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) struct PollFlags(i16);
|
||||
|
||||
impl PollFlags {
|
||||
/// Empty set of flags.
|
||||
pub(super) const fn empty() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub(super) const IN: PollFlags = PollFlags(hermit_abi::POLLIN);
|
||||
pub(super) const OUT: PollFlags = PollFlags(hermit_abi::POLLOUT);
|
||||
pub(super) const WRBAND: PollFlags = PollFlags(hermit_abi::POLLWRBAND);
|
||||
pub(super) const ERR: PollFlags = PollFlags(hermit_abi::POLLERR);
|
||||
pub(super) const HUP: PollFlags = PollFlags(hermit_abi::POLLHUP);
|
||||
pub(super) const PRI: PollFlags = PollFlags(hermit_abi::POLLPRI);
|
||||
|
||||
/// Tell if this contains some flags.
|
||||
pub(super) fn contains(self, flags: PollFlags) -> bool {
|
||||
self.0 & flags.0 != 0
|
||||
}
|
||||
|
||||
/// Set a flag.
|
||||
pub(super) fn set(&mut self, flags: PollFlags, set: bool) {
|
||||
if set {
|
||||
self.0 |= flags.0;
|
||||
} else {
|
||||
self.0 &= !(flags.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tell if this is empty.
|
||||
pub(super) fn is_empty(self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Tell if this intersects with some flags.
|
||||
pub(super) fn intersects(self, flags: PollFlags) -> bool {
|
||||
self.contains(flags)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for PollFlags {
|
||||
type Output = PollFlags;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(super) struct EventfdFlags;
|
||||
|
||||
impl EventfdFlags {
|
||||
pub(super) fn empty() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a number to an actual result.
|
||||
#[inline]
|
||||
fn cvt(len: isize) -> io::Result<usize> {
|
||||
if len < 0 {
|
||||
Err(io::Error::from_raw_os_error(unsafe {
|
||||
hermit_abi::get_errno()
|
||||
}))
|
||||
} else {
|
||||
Ok(len as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "espidf", target_os = "hermit")))]
|
||||
mod notify {
|
||||
use std::io;
|
||||
|
||||
use rustix::event::PollFlags;
|
||||
use rustix::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
|
||||
use rustix::fs::{fcntl_getfl, fcntl_setfl, OFlags};
|
||||
use rustix::io::{fcntl_getfd, fcntl_setfd, read, write, FdFlags};
|
||||
#[cfg(not(target_os = "haiku"))]
|
||||
use rustix::pipe::pipe_with;
|
||||
use rustix::pipe::{pipe, PipeFlags};
|
||||
|
||||
/// A notification pipe.
|
||||
///
|
||||
/// This implementation uses a pipe to send notifications.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Notify {
|
||||
/// The file descriptor of the read half of the notify pipe. This is also stored as the first
|
||||
/// file descriptor in `fds.poll_fds`.
|
||||
read_pipe: OwnedFd,
|
||||
/// The file descriptor of the write half of the notify pipe.
|
||||
///
|
||||
/// Data is written to this to wake up the current instance of `Poller::wait`, which can occur when the
|
||||
/// user notifies it (in which case `Poller::notified` would have been set) or when an operation needs
|
||||
/// to occur (in which case `Poller::waiting_operations` would have been incremented).
|
||||
write_pipe: OwnedFd,
|
||||
}
|
||||
|
||||
impl Notify {
|
||||
/// Creates a new notification pipe.
|
||||
pub(super) fn new() -> io::Result<Self> {
|
||||
let fallback_pipe = |_| {
|
||||
let (read_pipe, write_pipe) = pipe()?;
|
||||
fcntl_setfd(&read_pipe, fcntl_getfd(&read_pipe)? | FdFlags::CLOEXEC)?;
|
||||
fcntl_setfd(&write_pipe, fcntl_getfd(&write_pipe)? | FdFlags::CLOEXEC)?;
|
||||
io::Result::Ok((read_pipe, write_pipe))
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "haiku"))]
|
||||
let (read_pipe, write_pipe) = pipe_with(PipeFlags::CLOEXEC).or_else(fallback_pipe)?;
|
||||
|
||||
#[cfg(target_os = "haiku")]
|
||||
let (read_pipe, write_pipe) = fallback_pipe(PipeFlags::CLOEXEC)?;
|
||||
|
||||
// Put the reading side into non-blocking mode.
|
||||
fcntl_setfl(&read_pipe, fcntl_getfl(&read_pipe)? | OFlags::NONBLOCK)?;
|
||||
|
||||
Ok(Self {
|
||||
read_pipe,
|
||||
write_pipe,
|
||||
})
|
||||
}
|
||||
|
||||
/// Provides the file handle of the read half of the notify pipe that needs to be registered by the `Poller`.
|
||||
pub(super) fn fd(&self) -> BorrowedFd<'_> {
|
||||
self.read_pipe.as_fd()
|
||||
}
|
||||
|
||||
/// Provides the poll flags to be used when registering the read half of the notify pipe with the `Poller`.
|
||||
pub(super) fn poll_flags(&self) -> PollFlags {
|
||||
PollFlags::RDNORM
|
||||
}
|
||||
|
||||
/// Notifies the `Poller` instance via the write half of the notify pipe.
|
||||
pub(super) fn notify(&self) -> Result<(), io::Error> {
|
||||
write(&self.write_pipe, &[0; 1])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pops a notification (if any) from the pipe.
|
||||
pub(super) fn pop_notification(&self) -> Result<(), io::Error> {
|
||||
// Pipes on Vita do not guarantee that after `write` call succeeds, the
|
||||
// data becomes immediately available for reading on the other side of the pipe.
|
||||
// To ensure that the notification is not lost, the read side of the pipe is temporarily
|
||||
// switched to blocking for a single `read` call.
|
||||
#[cfg(target_os = "vita")]
|
||||
rustix::fs::fcntl_setfl(
|
||||
&self.read_pipe,
|
||||
rustix::fs::fcntl_getfl(&self.read_pipe)? & !rustix::fs::OFlags::NONBLOCK,
|
||||
)?;
|
||||
|
||||
let result = read(&self.read_pipe, &mut [0; 1]);
|
||||
|
||||
#[cfg(target_os = "vita")]
|
||||
rustix::fs::fcntl_setfl(
|
||||
&self.read_pipe,
|
||||
rustix::fs::fcntl_getfl(&self.read_pipe)? | rustix::fs::OFlags::NONBLOCK,
|
||||
)?;
|
||||
|
||||
result?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pops all notifications from the pipe.
|
||||
pub(super) fn pop_all_notifications(&self) -> Result<(), io::Error> {
|
||||
while read(&self.read_pipe, &mut [0; 64]).is_ok() {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether this raw file descriptor is associated with this notifier.
|
||||
pub(super) fn has_fd(&self, fd: RawFd) -> bool {
|
||||
self.read_pipe.as_raw_fd() == fd || self.write_pipe.as_raw_fd() == fd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "espidf", target_os = "hermit"))]
|
||||
mod notify {
|
||||
use std::io;
|
||||
use std::mem;
|
||||
|
||||
use super::syscall::{
|
||||
eventfd, read, write, AsFd, AsRawFd, BorrowedFd, EventfdFlags, OwnedFd, PollFlags, RawFd,
|
||||
};
|
||||
|
||||
/// A notification pipe.
|
||||
///
|
||||
/// This implementation uses the `eventfd` syscall to send notifications.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Notify {
|
||||
/// The file descriptor of the eventfd object. This is also stored as the first
|
||||
/// file descriptor in `fds.poll_fds`.
|
||||
///
|
||||
/// Data is written to this to wake up the current instance of `Poller::wait`, which can occur when the
|
||||
/// user notifies it (in which case `Poller::notified` would have been set) or when an operation needs
|
||||
/// to occur (in which case `Poller::waiting_operations` would have been incremented).
|
||||
event_fd: OwnedFd,
|
||||
}
|
||||
|
||||
impl Notify {
|
||||
/// Creates a new notification pipe.
|
||||
pub(super) fn new() -> io::Result<Self> {
|
||||
// Note that the eventfd() implementation in ESP-IDF deviates from the specification in the following ways:
|
||||
// 1) The file descriptor is always in a non-blocking mode, as if EFD_NONBLOCK was passed as a flag;
|
||||
// passing EFD_NONBLOCK or calling fcntl(.., F_GETFL/F_SETFL) on the eventfd() file descriptor is not supported
|
||||
// 2) It always returns the counter value, even if it is 0. This is contrary to the specification which mandates
|
||||
// that it should instead fail with EAGAIN
|
||||
//
|
||||
// (1) is not a problem for us, as we want the eventfd() file descriptor to be in a non-blocking mode anyway
|
||||
// (2) is also not a problem, as long as we don't try to read the counter value in an endless loop when we detect being notified
|
||||
|
||||
let flags = EventfdFlags::empty();
|
||||
let event_fd = eventfd(0, flags).map_err(|err| {
|
||||
match io::Error::from(err) {
|
||||
err if err.kind() == io::ErrorKind::PermissionDenied => {
|
||||
// EPERM can happen if the eventfd isn't initialized yet.
|
||||
// Tell the user to call esp_vfs_eventfd_register.
|
||||
io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
"failed to initialize eventfd for polling, try calling `esp_vfs_eventfd_register`"
|
||||
)
|
||||
},
|
||||
err => err,
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self { event_fd })
|
||||
}
|
||||
|
||||
/// Provides the eventfd file handle that needs to be registered by the `Poller`.
|
||||
pub(super) fn fd(&self) -> BorrowedFd<'_> {
|
||||
self.event_fd.as_fd()
|
||||
}
|
||||
|
||||
/// Provides the eventfd file handle poll flags to be used when registering it with the `Poller`.
|
||||
pub(super) fn poll_flags(&self) -> PollFlags {
|
||||
PollFlags::IN
|
||||
}
|
||||
|
||||
/// Notifies the `Poller` instance via the eventfd file descriptor.
|
||||
pub(super) fn notify(&self) -> Result<(), io::Error> {
|
||||
write(self.event_fd.as_fd(), &1u64.to_ne_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pops a notification (if any) from the eventfd file descriptor.
|
||||
pub(super) fn pop_notification(&self) -> Result<(), io::Error> {
|
||||
read(self.event_fd.as_fd(), &mut [0; mem::size_of::<u64>()])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pops all notifications from the eventfd file descriptor.
|
||||
/// Since the eventfd object accumulates all writes in a single 64 bit value,
|
||||
/// this operation is - in fact - equivalent to `pop_notification`.
|
||||
pub(super) fn pop_all_notifications(&self) -> Result<(), io::Error> {
|
||||
let _ = self.pop_notification();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether this raw file descriptor is associated with this notifier.
|
||||
pub(super) fn has_fd(&self, fd: RawFd) -> bool {
|
||||
self.event_fd.as_raw_fd() == fd
|
||||
}
|
||||
}
|
||||
}
|
314
src/port.rs
314
src/port.rs
|
@ -1,201 +1,263 @@
|
|||
//! 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::io;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::Event;
|
||||
use rustix::event::{port, PollFlags};
|
||||
use rustix::fd::OwnedFd;
|
||||
use rustix::io::{fcntl_getfd, fcntl_setfd, FdFlags};
|
||||
|
||||
use crate::{Event, PollMode};
|
||||
|
||||
/// 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,
|
||||
port_fd: OwnedFd,
|
||||
}
|
||||
|
||||
impl Poller {
|
||||
/// Creates a new poller.
|
||||
pub fn new() -> io::Result<Poller> {
|
||||
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))?;
|
||||
let port_fd = port::port_create()?;
|
||||
let flags = fcntl_getfd(&port_fd)?;
|
||||
fcntl_setfd(&port_fd, flags | FdFlags::CLOEXEC)?;
|
||||
|
||||
// Set up the notification pipe.
|
||||
let (read_stream, write_stream) = UnixStream::pair()?;
|
||||
read_stream.set_nonblocking(true)?;
|
||||
write_stream.set_nonblocking(true)?;
|
||||
tracing::trace!(
|
||||
port_fd = ?port_fd.as_raw_fd(),
|
||||
"new",
|
||||
);
|
||||
|
||||
let poller = Poller {
|
||||
port_fd,
|
||||
read_stream,
|
||||
write_stream,
|
||||
};
|
||||
poller.interest(
|
||||
poller.read_stream.as_raw_fd(),
|
||||
Event {
|
||||
key: crate::NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(poller)
|
||||
Ok(Poller { port_fd })
|
||||
}
|
||||
|
||||
/// 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))?;
|
||||
|
||||
syscall!(port_associate(
|
||||
self.port_fd,
|
||||
libc::PORT_SOURCE_FD,
|
||||
fd as _,
|
||||
0,
|
||||
0 as _,
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
/// Whether this poller supports level-triggered events.
|
||||
pub fn supports_level(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// 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 flags = 0;
|
||||
/// Whether this poller supports edge-triggered events.
|
||||
pub fn supports_edge(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Adds a file descriptor.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `fd` must be a valid file descriptor and it must last until it is deleted.
|
||||
pub unsafe fn add(&self, fd: RawFd, ev: Event, mode: PollMode) -> io::Result<()> {
|
||||
// File descriptors don't need to be added explicitly, so just modify the interest.
|
||||
self.modify(BorrowedFd::borrow_raw(fd), ev, mode)
|
||||
}
|
||||
|
||||
/// Modifies an existing file descriptor.
|
||||
pub fn modify(&self, fd: BorrowedFd<'_>, ev: Event, mode: PollMode) -> io::Result<()> {
|
||||
let span = tracing::trace_span!(
|
||||
"modify",
|
||||
port_fd = ?self.port_fd.as_raw_fd(),
|
||||
?fd,
|
||||
?ev,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
let mut flags = PollFlags::empty();
|
||||
if ev.readable {
|
||||
flags |= libc::POLLIN;
|
||||
flags |= read_flags();
|
||||
}
|
||||
if ev.writable {
|
||||
flags |= libc::POLLOUT;
|
||||
flags |= write_flags();
|
||||
}
|
||||
|
||||
syscall!(port_associate(
|
||||
self.port_fd,
|
||||
libc::PORT_SOURCE_FD,
|
||||
fd as _,
|
||||
flags as _,
|
||||
ev.key as _,
|
||||
))?;
|
||||
if mode != PollMode::Oneshot {
|
||||
return Err(crate::unsupported_error(
|
||||
"this kind of event is not supported with event ports",
|
||||
));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
port::port_associate_fd(&self.port_fd, fd, flags, ev.key as _)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a file descriptor.
|
||||
pub fn remove(&self, fd: RawFd) -> io::Result<()> {
|
||||
syscall!(port_dissociate(
|
||||
self.port_fd,
|
||||
libc::PORT_SOURCE_FD,
|
||||
fd as usize,
|
||||
))?;
|
||||
/// Deletes a file descriptor.
|
||||
pub fn delete(&self, fd: BorrowedFd<'_>) -> io::Result<()> {
|
||||
let span = tracing::trace_span!(
|
||||
"delete",
|
||||
port_fd = ?self.port_fd.as_raw_fd(),
|
||||
?fd,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
let result = unsafe { port::port_dissociate_fd(&self.port_fd, fd) };
|
||||
if let Err(e) = result {
|
||||
match e {
|
||||
rustix::io::Errno::NOENT => return Ok(()),
|
||||
_ => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits for I/O events with an optional timeout.
|
||||
pub fn wait(&self, events: &mut Events, timeout: Option<Duration>) -> 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;
|
||||
let span = tracing::trace_span!(
|
||||
"wait",
|
||||
port_fd = ?self.port_fd.as_raw_fd(),
|
||||
?timeout,
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
// 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,
|
||||
}
|
||||
));
|
||||
let res = port::port_getn(&self.port_fd, &mut events.list, 1, timeout);
|
||||
tracing::trace!(
|
||||
port_fd = ?self.port_fd,
|
||||
res = ?events.list.len(),
|
||||
"new events"
|
||||
);
|
||||
|
||||
// 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.interest(
|
||||
self.read_stream.as_raw_fd(),
|
||||
Event {
|
||||
key: crate::NOTIFY_KEY,
|
||||
readable: true,
|
||||
writable: false,
|
||||
},
|
||||
)?;
|
||||
if let Err(e) = res {
|
||||
match e {
|
||||
rustix::io::Errno::TIME => {}
|
||||
_ => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
const PORT_SOURCE_USER: i32 = 3;
|
||||
|
||||
let span = tracing::trace_span!(
|
||||
"notify",
|
||||
port_fd = ?self.port_fd.as_raw_fd(),
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
// Use port_send to send a notification to the port.
|
||||
port::port_send(&self.port_fd, PORT_SOURCE_USER, crate::NOTIFY_KEY as _)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Poller {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.remove(self.read_stream.as_raw_fd());
|
||||
let _ = syscall!(close(self.port_fd));
|
||||
impl AsRawFd for Poller {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.port_fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for Poller {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.port_fd.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll flags for all possible readability events.
|
||||
fn read_flags() -> libc::c_short {
|
||||
libc::POLLIN | libc::POLLHUP | libc::POLLERR | libc::POLLPRI
|
||||
fn read_flags() -> PollFlags {
|
||||
PollFlags::IN | PollFlags::HUP | PollFlags::ERR | PollFlags::PRI
|
||||
}
|
||||
|
||||
/// Poll flags for all possible writability events.
|
||||
fn write_flags() -> libc::c_short {
|
||||
libc::POLLOUT | libc::POLLHUP | libc::POLLERR
|
||||
fn write_flags() -> PollFlags {
|
||||
PollFlags::OUT | PollFlags::HUP | PollFlags::ERR
|
||||
}
|
||||
|
||||
/// A list of reported I/O events.
|
||||
pub struct Events {
|
||||
list: Box<[libc::port_event]>,
|
||||
len: usize,
|
||||
list: Vec<port::Event>,
|
||||
}
|
||||
|
||||
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 }
|
||||
pub fn with_capacity(cap: usize) -> Events {
|
||||
Events {
|
||||
list: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over I/O events.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Event> + '_ {
|
||||
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,
|
||||
self.list.iter().map(|ev| {
|
||||
let flags = PollFlags::from_bits_truncate(ev.events() as _);
|
||||
Event {
|
||||
key: ev.userdata() as usize,
|
||||
readable: flags.intersects(read_flags()),
|
||||
writable: flags.intersects(write_flags()),
|
||||
extra: EventExtra { flags },
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear the list.
|
||||
pub fn clear(&mut self) {
|
||||
self.list.clear();
|
||||
}
|
||||
|
||||
/// Get the capacity of the list.
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.list.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra information associated with an event.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct EventExtra {
|
||||
/// Flags associated with this event.
|
||||
flags: PollFlags,
|
||||
}
|
||||
|
||||
impl EventExtra {
|
||||
/// Create a new, empty version of this struct.
|
||||
#[inline]
|
||||
pub const fn empty() -> EventExtra {
|
||||
EventExtra {
|
||||
flags: PollFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the interrupt flag.
|
||||
#[inline]
|
||||
pub fn set_hup(&mut self, value: bool) {
|
||||
self.flags.set(PollFlags::HUP, value);
|
||||
}
|
||||
|
||||
/// Set the priority flag.
|
||||
#[inline]
|
||||
pub fn set_pri(&mut self, value: bool) {
|
||||
self.flags.set(PollFlags::PRI, value);
|
||||
}
|
||||
|
||||
/// Is this an interrupt event?
|
||||
#[inline]
|
||||
pub fn is_hup(&self) -> bool {
|
||||
self.flags.contains(PollFlags::HUP)
|
||||
}
|
||||
|
||||
/// Is this a priority event?
|
||||
#[inline]
|
||||
pub fn is_pri(&self) -> bool {
|
||||
self.flags.contains(PollFlags::PRI)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_connect_failed(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(PollFlags::ERR) && self.flags.contains(PollFlags::HUP))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_err(&self) -> Option<bool> {
|
||||
Some(self.flags.contains(PollFlags::ERR))
|
||||
}
|
||||
}
|
||||
|
|
238
src/wepoll.rs
238
src/wepoll.rs
|
@ -1,238 +0,0 @@
|
|||
//! 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_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,
|
||||
notified: AtomicBool,
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
let notified = AtomicBool::new(false);
|
||||
log::debug!("new: handle={:?}", handle);
|
||||
Ok(Poller { handle, notified })
|
||||
}
|
||||
|
||||
/// Inserts a socket.
|
||||
pub fn insert(&self, sock: RawSocket) -> io::Result<()> {
|
||||
log::debug!("insert: handle={:?}, sock={}", self.handle, sock);
|
||||
|
||||
// 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: we::EPOLLONESHOT,
|
||||
data: we::epoll_data {
|
||||
u64: crate::NOTIFY_KEY as u64,
|
||||
},
|
||||
};
|
||||
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, ev: Event) -> io::Result<()> {
|
||||
log::debug!(
|
||||
"interest: handle={:?}, sock={}, ev={:?}",
|
||||
self.handle,
|
||||
sock,
|
||||
ev
|
||||
);
|
||||
|
||||
let mut flags = we::EPOLLONESHOT;
|
||||
if ev.readable {
|
||||
flags |= READ_FLAGS;
|
||||
}
|
||||
if ev.writable {
|
||||
flags |= WRITE_FLAGS;
|
||||
}
|
||||
|
||||
let mut ev = we::epoll_event {
|
||||
events: flags as u32,
|
||||
data: we::epoll_data { u64: ev.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<()> {
|
||||
log::debug!("remove: handle={:?}, sock={}", self.handle, sock);
|
||||
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<()> {
|
||||
log::debug!("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 += 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 libc::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::debug!("notify: handle={:?}", self.handle);
|
||||
|
||||
if !self
|
||||
.notified
|
||||
.compare_and_swap(false, true, Ordering::SeqCst)
|
||||
{
|
||||
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) {
|
||||
log::debug!("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<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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
use std::io::{self, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use easy_parallel::Parallel;
|
||||
use polling::{Event, Events, Poller};
|
||||
|
||||
#[test]
|
||||
fn concurrent_add() -> io::Result<()> {
|
||||
let (reader, mut writer) = tcp_pair()?;
|
||||
let poller = Poller::new()?;
|
||||
|
||||
let mut events = Events::new();
|
||||
|
||||
let result = Parallel::new()
|
||||
.add(|| {
|
||||
poller.wait(&mut events, None)?;
|
||||
Ok(())
|
||||
})
|
||||
.add(|| {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
unsafe {
|
||||
poller.add(&reader, Event::readable(0))?;
|
||||
}
|
||||
writer.write_all(&[1])?;
|
||||
Ok(())
|
||||
})
|
||||
.run()
|
||||
.into_iter()
|
||||
.collect::<io::Result<()>>();
|
||||
|
||||
poller.delete(&reader)?;
|
||||
result?;
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(0)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent_modify() -> io::Result<()> {
|
||||
let (reader, mut writer) = tcp_pair()?;
|
||||
let poller = Poller::new()?;
|
||||
unsafe {
|
||||
poller.add(&reader, Event::none(0))?;
|
||||
}
|
||||
|
||||
let mut events = Events::new();
|
||||
|
||||
Parallel::new()
|
||||
.add(|| {
|
||||
poller.wait(&mut events, Some(Duration::from_secs(10)))?;
|
||||
Ok(())
|
||||
})
|
||||
.add(|| {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
poller.modify(&reader, Event::readable(0))?;
|
||||
writer.write_all(&[1])?;
|
||||
Ok(())
|
||||
})
|
||||
.run()
|
||||
.into_iter()
|
||||
.collect::<io::Result<()>>()?;
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(0)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "vita")))]
|
||||
#[test]
|
||||
fn concurrent_interruption() -> io::Result<()> {
|
||||
struct MakeItSend<T>(T);
|
||||
unsafe impl<T> Send for MakeItSend<T> {}
|
||||
|
||||
let (reader, _writer) = tcp_pair()?;
|
||||
let poller = Poller::new()?;
|
||||
unsafe {
|
||||
poller.add(&reader, Event::none(0))?;
|
||||
}
|
||||
|
||||
let mut events = Events::new();
|
||||
let events_borrow = &mut events;
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
|
||||
Parallel::new()
|
||||
.add(move || {
|
||||
// Register a signal handler so that the syscall is actually interrupted. A signal that
|
||||
// is ignored by default does not cause an interrupted syscall.
|
||||
signal_hook::flag::register(signal_hook::consts::signal::SIGURG, Default::default())?;
|
||||
|
||||
// Signal to the other thread how to send a signal to us
|
||||
sender
|
||||
.send(MakeItSend(unsafe { libc::pthread_self() }))
|
||||
.unwrap();
|
||||
|
||||
poller.wait(events_borrow, Some(Duration::from_secs(1)))?;
|
||||
Ok(())
|
||||
})
|
||||
.add(move || {
|
||||
let MakeItSend(target_thread) = receiver.recv().unwrap();
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
assert_eq!(0, unsafe {
|
||||
libc::pthread_kill(target_thread, libc::SIGURG)
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.run()
|
||||
.into_iter()
|
||||
.collect::<io::Result<()>>()?;
|
||||
|
||||
assert_eq!(events.len(), 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))
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
use polling::{Event, Events, Poller};
|
||||
use std::io::{self, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn basic_io() {
|
||||
let poller = Poller::new().unwrap();
|
||||
let (read, mut write) = tcp_pair().unwrap();
|
||||
unsafe {
|
||||
poller.add(&read, Event::readable(1)).unwrap();
|
||||
}
|
||||
|
||||
// Nothing should be available at first.
|
||||
let mut events = Events::new();
|
||||
assert_eq!(
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(0)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
|
||||
// After a write, the event should be available now.
|
||||
write.write_all(&[1]).unwrap();
|
||||
assert_eq!(
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(1)
|
||||
);
|
||||
poller.delete(&read).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_twice() {
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::AsRawSocket;
|
||||
|
||||
let (read, mut write) = tcp_pair().unwrap();
|
||||
let read = Arc::new(read);
|
||||
|
||||
let poller = Poller::new().unwrap();
|
||||
unsafe {
|
||||
#[cfg(unix)]
|
||||
let read = read.as_raw_fd();
|
||||
#[cfg(windows)]
|
||||
let read = read.as_raw_socket();
|
||||
|
||||
poller.add(read, Event::readable(1)).unwrap();
|
||||
assert_eq!(
|
||||
poller.add(read, Event::readable(1)).unwrap_err().kind(),
|
||||
io::ErrorKind::AlreadyExists
|
||||
);
|
||||
}
|
||||
|
||||
write.write_all(&[1]).unwrap();
|
||||
let mut events = Events::new();
|
||||
assert_eq!(
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(1)
|
||||
);
|
||||
|
||||
poller.delete(&read).unwrap();
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
//! Tests to ensure more than 32 connections can be polled at once.
|
||||
|
||||
// Doesn't work on OpenBSD.
|
||||
#![cfg(not(target_os = "openbsd"))]
|
||||
|
||||
use std::io::{self, prelude::*};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::time::Duration;
|
||||
|
||||
use polling::Events;
|
||||
|
||||
#[test]
|
||||
fn many_connections() {
|
||||
// Create 100 connections.
|
||||
let mut connections = Vec::new();
|
||||
for i in 0..100 {
|
||||
let (reader, writer) = tcp_pair().unwrap();
|
||||
connections.push((i, reader, writer));
|
||||
}
|
||||
|
||||
// Create a poller and add all the connections.
|
||||
let poller = polling::Poller::new().unwrap();
|
||||
|
||||
for (i, reader, _) in connections.iter() {
|
||||
unsafe {
|
||||
poller.add(reader, polling::Event::readable(*i)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let mut events = Events::new();
|
||||
while !connections.is_empty() {
|
||||
// Choose a random connection to write to.
|
||||
let i = fastrand::usize(..connections.len());
|
||||
let (id, mut reader, mut writer) = connections.remove(i);
|
||||
|
||||
// Write a byte to the connection.
|
||||
writer.write_all(&[1]).unwrap();
|
||||
|
||||
// Wait for the connection to become readable.
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
|
||||
// Check that the connection is readable.
|
||||
let current_events = events.iter().collect::<Vec<_>>();
|
||||
assert_eq!(current_events.len(), 1, "events: {:?}", current_events);
|
||||
assert_eq!(
|
||||
current_events[0].with_no_extra(),
|
||||
polling::Event::readable(id)
|
||||
);
|
||||
|
||||
// Read the byte from the connection.
|
||||
let mut buf = [0];
|
||||
reader.read_exact(&mut buf).unwrap();
|
||||
assert_eq!(buf, [1]);
|
||||
poller.delete(&reader).unwrap();
|
||||
events.clear();
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
//! Test registering one source into multiple pollers.
|
||||
|
||||
use polling::{Event, Events, PollMode, Poller};
|
||||
|
||||
use std::io::{self, prelude::*};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn level_triggered() {
|
||||
let poller1 = Poller::new().unwrap();
|
||||
let poller2 = Poller::new().unwrap();
|
||||
let mut events = Events::new();
|
||||
|
||||
if !poller1.supports_level() || !poller2.supports_level() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the source into both pollers.
|
||||
let (mut reader, mut writer) = tcp_pair().unwrap();
|
||||
unsafe {
|
||||
poller1
|
||||
.add_with_mode(&reader, Event::readable(1), PollMode::Level)
|
||||
.unwrap();
|
||||
poller2
|
||||
.add_with_mode(&reader, Event::readable(2), PollMode::Level)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Neither poller should have any events.
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
|
||||
// Write to the source.
|
||||
writer.write_all(&[1]).unwrap();
|
||||
|
||||
// At least one poller should have an event.
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(1)
|
||||
);
|
||||
|
||||
events.clear();
|
||||
// poller2 should have zero or one events.
|
||||
match poller2.wait(&mut events, Some(Duration::from_secs(1))) {
|
||||
Ok(1) => {
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(2)
|
||||
);
|
||||
}
|
||||
Ok(0) => assert!(events.is_empty()),
|
||||
_ => panic!("unexpected error"),
|
||||
}
|
||||
|
||||
// Writing more data should cause the same event.
|
||||
writer.write_all(&[1]).unwrap();
|
||||
events.clear();
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(1)
|
||||
);
|
||||
|
||||
// poller2 should have zero or one events.
|
||||
events.clear();
|
||||
match poller2.wait(&mut events, Some(Duration::from_secs(1))) {
|
||||
Ok(1) => {
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(2)
|
||||
);
|
||||
}
|
||||
Ok(0) => assert!(events.is_empty()),
|
||||
_ => panic!("unexpected error"),
|
||||
}
|
||||
|
||||
// Read from the source.
|
||||
reader.read_exact(&mut [0; 2]).unwrap();
|
||||
|
||||
// Both pollers should not have any events.
|
||||
events.clear();
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
|
||||
// Dereference the pollers.
|
||||
poller1.delete(&reader).unwrap();
|
||||
poller2.delete(&reader).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_triggered() {
|
||||
let poller1 = Poller::new().unwrap();
|
||||
let poller2 = Poller::new().unwrap();
|
||||
let mut events = Events::new();
|
||||
|
||||
if !poller1.supports_edge() || !poller2.supports_edge() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the source into both pollers.
|
||||
let (mut reader, mut writer) = tcp_pair().unwrap();
|
||||
unsafe {
|
||||
poller1
|
||||
.add_with_mode(&reader, Event::readable(1), PollMode::Edge)
|
||||
.unwrap();
|
||||
poller2
|
||||
.add_with_mode(&reader, Event::readable(2), PollMode::Edge)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Neither poller should have any events.
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
|
||||
// Write to the source.
|
||||
writer.write_all(&[1]).unwrap();
|
||||
|
||||
// Both pollers should have an event.
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(1)
|
||||
);
|
||||
|
||||
events.clear();
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(2)
|
||||
);
|
||||
|
||||
// Writing to the poller again should cause an event.
|
||||
writer.write_all(&[1]).unwrap();
|
||||
|
||||
// Both pollers should have one event.
|
||||
events.clear();
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(1)
|
||||
);
|
||||
|
||||
events.clear();
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(2)
|
||||
);
|
||||
|
||||
// Read from the source.
|
||||
reader.read_exact(&mut [0; 2]).unwrap();
|
||||
|
||||
// Both pollers should not have any events.
|
||||
events.clear();
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
|
||||
// Dereference the pollers.
|
||||
poller1.delete(&reader).unwrap();
|
||||
poller2.delete(&reader).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oneshot_triggered() {
|
||||
let poller1 = Poller::new().unwrap();
|
||||
let poller2 = Poller::new().unwrap();
|
||||
let mut events = Events::new();
|
||||
|
||||
// Register the source into both pollers.
|
||||
let (mut reader, mut writer) = tcp_pair().unwrap();
|
||||
unsafe {
|
||||
poller1
|
||||
.add_with_mode(&reader, Event::readable(1), PollMode::Oneshot)
|
||||
.unwrap();
|
||||
poller2
|
||||
.add_with_mode(&reader, Event::readable(2), PollMode::Oneshot)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Neither poller should have any events.
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
|
||||
// Write to the source.
|
||||
writer.write_all(&[1]).unwrap();
|
||||
|
||||
// Sources should have either one or no events.
|
||||
match poller1.wait(&mut events, Some(Duration::from_secs(1))) {
|
||||
Ok(1) => {
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(1)
|
||||
);
|
||||
}
|
||||
Ok(0) => assert!(events.is_empty()),
|
||||
_ => panic!("unexpected error"),
|
||||
}
|
||||
events.clear();
|
||||
|
||||
match poller2.wait(&mut events, Some(Duration::from_secs(1))) {
|
||||
Ok(1) => {
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(2)
|
||||
);
|
||||
}
|
||||
Ok(0) => assert!(events.is_empty()),
|
||||
_ => panic!("unexpected error"),
|
||||
}
|
||||
events.clear();
|
||||
|
||||
// Writing more data should not cause an event.
|
||||
writer.write_all(&[1]).unwrap();
|
||||
|
||||
// Sources should have no events.
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
|
||||
// Read from the source.
|
||||
reader.read_exact(&mut [0; 2]).unwrap();
|
||||
|
||||
// Sources should have no events.
|
||||
assert_eq!(
|
||||
poller1
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
assert_eq!(
|
||||
poller2
|
||||
.wait(&mut events, Some(Duration::from_secs(1)))
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
|
@ -3,16 +3,18 @@ use std::thread;
|
|||
use std::time::Duration;
|
||||
|
||||
use easy_parallel::Parallel;
|
||||
use polling::Events;
|
||||
use polling::Poller;
|
||||
|
||||
#[test]
|
||||
fn simple() -> io::Result<()> {
|
||||
let poller = Poller::new()?;
|
||||
let mut events = Vec::new();
|
||||
let mut events = Events::new();
|
||||
|
||||
for _ in 0..10 {
|
||||
poller.notify()?;
|
||||
poller.wait(&mut events, None)?;
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -21,7 +23,7 @@ fn simple() -> io::Result<()> {
|
|||
#[test]
|
||||
fn concurrent() -> io::Result<()> {
|
||||
let poller = Poller::new()?;
|
||||
let mut events = Vec::new();
|
||||
let mut events = Events::new();
|
||||
|
||||
for _ in 0..2 {
|
||||
Parallel::new()
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
//! Tests for level triggered and edge triggered mode.
|
||||
|
||||
#![allow(clippy::unused_io_amount)]
|
||||
|
||||
use std::io::{self, prelude::*};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::time::Duration;
|
||||
|
||||
use polling::{Event, Events, PollMode, Poller};
|
||||
|
||||
#[test]
|
||||
fn level_triggered() {
|
||||
// Create our streams.
|
||||
let (mut reader, mut writer) = tcp_pair().unwrap();
|
||||
let reader_token = 1;
|
||||
|
||||
// Create our poller and register our streams.
|
||||
let poller = Poller::new().unwrap();
|
||||
if unsafe { poller.add_with_mode(&reader, Event::readable(reader_token), PollMode::Level) }
|
||||
.is_err()
|
||||
{
|
||||
// Only panic if we're on a platform that should support level mode.
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "solaris", target_os = "illumos"))] {
|
||||
return;
|
||||
} else {
|
||||
panic!("Level mode should be supported on this platform");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write some data to the writer.
|
||||
let data = [1, 2, 3, 4, 5];
|
||||
writer.write_all(&data).unwrap();
|
||||
|
||||
// A "readable" notification should be delivered.
|
||||
let mut events = Events::new();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(reader_token)
|
||||
);
|
||||
|
||||
// If we read some of the data, the notification should still be available.
|
||||
reader.read_exact(&mut [0; 3]).unwrap();
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(reader_token)
|
||||
);
|
||||
|
||||
// If we read the rest of the data, the notification should be gone.
|
||||
reader.read_exact(&mut [0; 2]).unwrap();
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(0)))
|
||||
.unwrap();
|
||||
|
||||
assert!(events.is_empty());
|
||||
|
||||
// After modifying the stream and sending more data, it should be oneshot.
|
||||
poller
|
||||
.modify_with_mode(&reader, Event::readable(reader_token), PollMode::Oneshot)
|
||||
.unwrap();
|
||||
|
||||
writer.write(&data).unwrap();
|
||||
events.clear();
|
||||
|
||||
// BUG: Somehow, the notification here is delayed?
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(reader_token)
|
||||
);
|
||||
|
||||
// After reading, the notification should vanish.
|
||||
reader.read(&mut [0; 5]).unwrap();
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(0)))
|
||||
.unwrap();
|
||||
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_triggered() {
|
||||
// Create our streams.
|
||||
let (mut reader, mut writer) = tcp_pair().unwrap();
|
||||
let reader_token = 1;
|
||||
|
||||
// Create our poller and register our streams.
|
||||
let poller = Poller::new().unwrap();
|
||||
if unsafe { poller.add_with_mode(&reader, Event::readable(reader_token), PollMode::Edge) }
|
||||
.is_err()
|
||||
{
|
||||
// Only panic if we're on a platform that should support level mode.
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly"
|
||||
),
|
||||
not(polling_test_poll_backend)
|
||||
))] {
|
||||
panic!("Edge mode should be supported on this platform");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write some data to the writer.
|
||||
let data = [1, 2, 3, 4, 5];
|
||||
writer.write_all(&data).unwrap();
|
||||
|
||||
// A "readable" notification should be delivered.
|
||||
let mut events = Events::new();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(reader_token)
|
||||
);
|
||||
|
||||
// If we read some of the data, the notification should not still be available.
|
||||
reader.read_exact(&mut [0; 3]).unwrap();
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(0)))
|
||||
.unwrap();
|
||||
assert!(events.is_empty());
|
||||
|
||||
// If we write more data, a notification should be delivered.
|
||||
writer.write_all(&data).unwrap();
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(reader_token)
|
||||
);
|
||||
|
||||
// After modifying the stream and sending more data, it should be oneshot.
|
||||
poller
|
||||
.modify_with_mode(&reader, Event::readable(reader_token), PollMode::Oneshot)
|
||||
.unwrap();
|
||||
|
||||
writer.write_all(&data).unwrap();
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(reader_token)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_oneshot_triggered() {
|
||||
// Create our streams.
|
||||
let (mut reader, mut writer) = tcp_pair().unwrap();
|
||||
let reader_token = 1;
|
||||
|
||||
// Create our poller and register our streams.
|
||||
let poller = Poller::new().unwrap();
|
||||
if unsafe {
|
||||
poller.add_with_mode(
|
||||
&reader,
|
||||
Event::readable(reader_token),
|
||||
PollMode::EdgeOneshot,
|
||||
)
|
||||
}
|
||||
.is_err()
|
||||
{
|
||||
// Only panic if we're on a platform that should support level mode.
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonfly"
|
||||
),
|
||||
not(polling_test_poll_backend)
|
||||
))] {
|
||||
panic!("Edge mode should be supported on this platform");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write some data to the writer.
|
||||
let data = [1, 2, 3, 4, 5];
|
||||
writer.write_all(&data).unwrap();
|
||||
|
||||
// A "readable" notification should be delivered.
|
||||
let mut events = Events::new();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(reader_token)
|
||||
);
|
||||
|
||||
// If we read some of the data, the notification should not still be available.
|
||||
reader.read_exact(&mut [0; 3]).unwrap();
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(0)))
|
||||
.unwrap();
|
||||
assert!(events.is_empty());
|
||||
|
||||
// If we modify to re-enable the notification, it should be delivered.
|
||||
poller
|
||||
.modify_with_mode(
|
||||
&reader,
|
||||
Event::readable(reader_token),
|
||||
PollMode::EdgeOneshot,
|
||||
)
|
||||
.unwrap();
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(0)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(reader_token)
|
||||
);
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
use std::io;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use polling::Poller;
|
||||
use polling::{Events, Poller};
|
||||
|
||||
#[test]
|
||||
fn below_ms() -> io::Result<()> {
|
||||
let poller = Poller::new()?;
|
||||
let mut events = Vec::new();
|
||||
let mut events = Events::new();
|
||||
|
||||
let dur = Duration::from_micros(100);
|
||||
let margin = Duration::from_micros(500);
|
||||
|
@ -18,11 +18,22 @@ fn below_ms() -> io::Result<()> {
|
|||
let elapsed = now.elapsed();
|
||||
|
||||
assert_eq!(n, 0);
|
||||
assert!(elapsed >= dur);
|
||||
assert!(elapsed >= dur, "{:?} < {:?}", elapsed, dur);
|
||||
lowest = lowest.min(elapsed);
|
||||
}
|
||||
|
||||
if cfg!(not(windows)) {
|
||||
if cfg!(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
),
|
||||
not(polling_test_poll_backend)
|
||||
)) {
|
||||
assert!(lowest < dur + margin);
|
||||
}
|
||||
Ok(())
|
||||
|
@ -31,7 +42,7 @@ fn below_ms() -> io::Result<()> {
|
|||
#[test]
|
||||
fn above_ms() -> io::Result<()> {
|
||||
let poller = Poller::new()?;
|
||||
let mut events = Vec::new();
|
||||
let mut events = Events::new();
|
||||
|
||||
let dur = Duration::from_micros(3_100);
|
||||
let margin = Duration::from_micros(500);
|
||||
|
@ -43,11 +54,24 @@ fn above_ms() -> io::Result<()> {
|
|||
let elapsed = now.elapsed();
|
||||
|
||||
assert_eq!(n, 0);
|
||||
assert!(elapsed >= dur);
|
||||
assert!(elapsed >= dur, "{:?} < {:?}", elapsed, dur);
|
||||
lowest = lowest.min(elapsed);
|
||||
}
|
||||
|
||||
if cfg!(not(windows)) {
|
||||
if cfg!(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "illumos",
|
||||
target_os = "solaris",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
),
|
||||
not(polling_test_poll_backend)
|
||||
)) {
|
||||
assert!(lowest < dur + margin);
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use std::io;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use polling::Poller;
|
||||
use polling::{Events, Poller};
|
||||
|
||||
#[test]
|
||||
fn twice() -> io::Result<()> {
|
||||
let poller = Poller::new()?;
|
||||
let mut events = Vec::new();
|
||||
let mut events = Events::new();
|
||||
|
||||
for _ in 0..2 {
|
||||
let start = Instant::now();
|
||||
|
@ -22,7 +22,7 @@ fn twice() -> io::Result<()> {
|
|||
#[test]
|
||||
fn non_blocking() -> io::Result<()> {
|
||||
let poller = Poller::new()?;
|
||||
let mut events = Vec::new();
|
||||
let mut events = Events::new();
|
||||
|
||||
for _ in 0..100 {
|
||||
poller.wait(&mut events, Some(Duration::from_secs(0)))?;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
//! Tests for the post() function on Windows.
|
||||
|
||||
#![cfg(windows)]
|
||||
|
||||
use polling::os::iocp::{CompletionPacket, PollerIocpExt};
|
||||
use polling::{Event, Events, Poller};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn post_smoke() {
|
||||
let poller = Poller::new().unwrap();
|
||||
let mut events = Events::new();
|
||||
|
||||
poller
|
||||
.post(CompletionPacket::new(Event::readable(1)))
|
||||
.unwrap();
|
||||
poller.wait(&mut events, None).unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::readable(1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn post_multithread() {
|
||||
let poller = Arc::new(Poller::new().unwrap());
|
||||
let mut events = Events::new();
|
||||
|
||||
thread::spawn({
|
||||
let poller = Arc::clone(&poller);
|
||||
move || {
|
||||
for i in 0..3 {
|
||||
poller
|
||||
.post(CompletionPacket::new(Event::writable(i)))
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for i in 0..3 {
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_secs(5)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(
|
||||
events.iter().next().unwrap().with_no_extra(),
|
||||
Event::writable(i)
|
||||
);
|
||||
events.clear();
|
||||
}
|
||||
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_millis(10)))
|
||||
.unwrap();
|
||||
assert_eq!(events.len(), 0);
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
//! Tests for the waitable polling on Windows.
|
||||
|
||||
#![cfg(windows)]
|
||||
|
||||
use polling::os::iocp::PollerIocpExt;
|
||||
use polling::{Event, Events, PollMode, Poller};
|
||||
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::System::Threading::{CreateEventW, ResetEvent, SetEvent};
|
||||
|
||||
use std::io;
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
use std::os::windows::prelude::{AsHandle, BorrowedHandle};
|
||||
use std::time::Duration;
|
||||
|
||||
/// A basic wrapper around the Windows event object.
|
||||
struct EventHandle(RawHandle);
|
||||
|
||||
impl Drop for EventHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CloseHandle(self.0 as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventHandle {
|
||||
fn new(manual_reset: bool) -> io::Result<Self> {
|
||||
let handle = unsafe {
|
||||
CreateEventW(
|
||||
std::ptr::null_mut(),
|
||||
manual_reset as _,
|
||||
false as _,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
|
||||
if handle == 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(Self(handle as _))
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the event object.
|
||||
fn reset(&self) -> io::Result<()> {
|
||||
if unsafe { ResetEvent(self.0 as _) } != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the event object.
|
||||
fn set(&self) -> io::Result<()> {
|
||||
if unsafe { SetEvent(self.0 as _) } != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawHandle for EventHandle {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsHandle for EventHandle {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.0) }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let poller = Poller::new().unwrap();
|
||||
|
||||
let event = EventHandle::new(true).unwrap();
|
||||
|
||||
unsafe {
|
||||
poller
|
||||
.add_waitable(&event, Event::all(0), PollMode::Oneshot)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let mut events = Events::new();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_millis(100)))
|
||||
.unwrap();
|
||||
|
||||
assert!(events.is_empty());
|
||||
|
||||
// Signal the event.
|
||||
event.set().unwrap();
|
||||
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_millis(100)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(events.iter().next().unwrap().with_no_extra(), Event::all(0));
|
||||
|
||||
// Interest should be cleared.
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_millis(100)))
|
||||
.unwrap();
|
||||
|
||||
assert!(events.is_empty());
|
||||
|
||||
// If we modify the waitable, it should be added again.
|
||||
poller
|
||||
.modify_waitable(&event, Event::all(0), PollMode::Oneshot)
|
||||
.unwrap();
|
||||
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_millis(100)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(events.iter().next().unwrap().with_no_extra(), Event::all(0));
|
||||
|
||||
// If we reset the event, it should not be signaled.
|
||||
event.reset().unwrap();
|
||||
poller
|
||||
.modify_waitable(&event, Event::all(0), PollMode::Oneshot)
|
||||
.unwrap();
|
||||
|
||||
events.clear();
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_millis(100)))
|
||||
.unwrap();
|
||||
|
||||
assert!(events.is_empty());
|
||||
}
|
Loading…
Reference in New Issue