Compare commits

...

199 Commits

Author SHA1 Message Date
John Nunley cf2d60efca
ci: Use latest stable wine in testing
This works around bugs in Rust v1.78 that introduce incompatibilities
into Wine.

https://github.com/smol-rs/polling/pull/201#issuecomment-2092385046

Signed-off-by: John Nunley <dev@notgull.net>
2024-05-03 17:55:23 -07:00
John Nunley 0b4afcaf0a
ci: Remove +nightly and use default
Previously in the "cross" CI job, checks would use "+nightly" to ensure
that the nightly compiler is used. However this adds a lot of noise. In
order to clean up CI this commit replaces "+nightly" with "rustup
default nightly" at the start of the job.

cc https://github.com/smol-rs/polling/pull/197#discussion_r1573375732

Signed-off-by: John Nunley <dev@notgull.net>
2024-04-22 18:36:07 -07:00
John Nunley eb9d92a2e0
v3.7.0
Signed-off-by: John Nunley <dev@notgull.net>
2024-04-22 16:33:52 -07:00
Nikolay Arhipov 9e46c8455c
feat: ported to Vita target
Fixes #160
2024-04-20 11:22:46 -07:00
John Nunley 1c16a1e4af
v3.6.0
Signed-off-by: John Nunley <dev@notgull.net>
2024-03-23 20:42:01 -07:00
irvingouj @ Devolutions e25b3b4e4c
feat: Replace is_connect_failed with is_err
In linux, epoll, EPOLLHUP may happen even if no connection call is made. It
would confuse callers for what is actually happening.

Replaced is_connect_failed, and we detect if connection failed by using the
combination of is_err and is_interrupt, please see the example, tcp_client
2024-03-20 22:04:46 -07:00
John Nunley 50454d1cea
feat: Add support for HermitOS
HermitOS is a microkernel target aiming to provide a simple OS for
virtualized applications. It recently added support for the poll() and
eventfd() system calls, which means we can target it with our poll()
based backend.

Hermit does not have a traditional libc; instead it uses hermit-abi.
However rustix does not support using hermit-abi as its underlying
layer yet. So we have to build a shim layer until it does.

Closes #177
cc bytecodealliance/rustix#1012

Signed-off-by: John Nunley <dev@notgull.net>
2024-03-12 21:33:06 -07:00
John Nunley 634a77c264 bugfix: Remove CallOnDrop from port.rs
CallOnDrop is no longer used in the Windows IOCP backend, and it wasn't
being flagged as unused until the latest nightly. In order to fix
Windows builds, this commit removes CallOnDrop.

Signed-off-by: John Nunley <dev@notgull.net>
2024-03-12 20:46:02 -07:00
John Nunley 4d64fdc572
v3.5.0
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-17 22:02:48 -08:00
John Nunley 77b4ed1156
feat: On RedoxOS, use epoll instead of the poll backend
Technically RedoxOS supports the poll syscall, so we already support
RedoxOS. However, this is very slow. This commit ports this code to
epoll, which should be more efficient.

Closes #176
2024-02-11 08:31:13 -08:00
John Nunley ac7fbcae31
v3.4.0
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-05 19:18:30 -08:00
John Nunley 24e3691794
feat: Add a way to wait on process by PID
This is needed to make processes work in `async-process`.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-01 06:34:02 -08:00
John Nunley 62430fd56e Annotate ESP-IDF EPERM error with eventfd info
If eventfd isn't initialized, `Polling::new` will fail with an EPERM
error. You need to call the "esp_vfs_eventfd_register" function to
initialize the eventfd subsystem in ESP-IDF. This commit indicates to
the user that this needs to happen.

Signed-off-by: John Nunley <dev@notgull.net>
2024-01-27 21:17:58 -08:00
John Nunley ae484a0a12 tests: Fix clippy error in wait-signal
Signed-off-by: John Nunley <dev@notgull.net>
2024-01-27 21:17:58 -08:00
irvingouj @ Devolutions cf25dd85f8
feat: Add the ability to identify if the connection has failed 2024-01-26 12:58:39 -08:00
John Nunley 6125508c93
v3.3.2
Signed-off-by: John Nunley <dev@notgull.net>
2024-01-14 09:00:57 -08:00
John Nunley ea5a38a500
feat(windows): AFD failure now sources underlying I/O error
Previously, if AFD failed to initialize `polling` would return a custom
I/O error with a string error, containing the formatted version of the
underlying system error. However, this means that information about the
underlying system error is lost to the user.

This commit makes it so the returned `io::Error` wraps a user
inaccessible type: `AfdError`. This `AfdError`, when stringified,
returns a similar error message as what was previously returned. In
addition when `.source()` is used it returns the underlying system
error.

Closes #174

Signed-off-by: John Nunley <dev@notgull.net>
2024-01-08 16:34:13 -08:00
John Nunley 1f13664bbb
ci: Add async-io tests back to CI
Closes #145

Signed-off-by: John Nunley <dev@notgull.net>
2024-01-08 08:23:02 -08:00
Taiki Endo 0c794fce50 Ignore dead_code warning for tuple struct
This lint does not take into account destructors.

```
error: field `0` is never read
    --> src\iocp\mod.rs:1155:13
     |
1155 |     Waiting(WaitHandle),
     |     ------- ^^^^^^^^^^
     |     |
     |     field in this variant
     |
     = note: `-D dead-code` implied by `-D warnings`
     = help: to override `-D warnings` add `#[allow(dead_code)]`
help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field
     |
1155 |     Waiting(()),
     |             ~~
```
2024-01-07 16:11:02 +09:00
Taiki Endo 94c5ebf78b ci: Temporarily disable riscv32imc-esp-espidf build 2024-01-07 16:11:02 +09:00
Taiki Endo e956a8ad64 ci: Update FreeBSD image to 13.2
```
pkg install -y git
Updating FreeBSD repository catalogue...
pkg: http://pkgmir.geo.freebsd.org/FreeBSD:12:amd64/quarterly/meta.txz: Not Found
repository FreeBSD has no meta file, using default settings
pkg: http://pkgmir.geo.freebsd.org/FreeBSD:12:amd64/quarterly/packagesite.pkg: Not Found
pkg: http://pkgmir.geo.freebsd.org/FreeBSD:12:amd64/quarterly/packagesite.txz: Not Found
Unable to update repository FreeBSD
Error updating repositories!
```
2024-01-07 16:11:02 +09:00
Taiki Endo 078c478346 ci: Use cargo-hack's --rust-version flag for msrv check
This respects rust-version field in Cargo.toml, so it removes the need
to manage MSRV in both the CI file and Cargo.toml.
2024-01-07 16:11:02 +09:00
John Nunley b57a7c32a2
v3.3.1
Signed-off-by: John Nunley <dev@notgull.net>
2023-11-24 08:22:27 -08:00
dependabot[bot] 08a316e1fc
m: Update windows-sys requirement from 0.48 to 0.52
* Update windows-sys requirement from 0.48 to 0.52

Updates the requirements on [windows-sys](https://github.com/microsoft/windows-rs) to permit the latest version.
- [Release notes](https://github.com/microsoft/windows-rs/releases)
- [Commits](https://github.com/microsoft/windows-rs/compare/0.48.0...0.52.0)

---
updated-dependencies:
- dependency-name: windows-sys
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Correct windows-sys imports

Signed-off-by: John Nunley <dev@notgull.net>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: John Nunley <dev@notgull.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Nunley <dev@notgull.net>
2023-11-24 07:53:04 -08:00
John Nunley 8087787ab2
v3.3.0
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-28 18:54:47 -07:00
John Nunley b9ab821df1
bugfix: Handle interrupts while polling
Previous, `Poller::wait` would bubble signal interruption error to the user.
However, this may be unexpected for simple use cases. Thus, this commit makes
it so, if `ErrorKind::Interrupted` is received by the underlying `wait()` call,
it clears the events and tries to wait again.

This also adds a test for this interruption written by @psychon.

Co-Authored-By: Uli Schlachter <psychon@users.noreply.github.com>
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-27 07:02:08 -07:00
Uli Schlachter 0575cbd4bc
docs: Fix wrong link in docs of Poller::wait()
Once upon a time, this got a Vec as an argument, but that was replaced
with the Events struct. Thus, this should link to Events and not Vec.

Signed-off-by: Uli Schlachter <psychon@znc.in>
2023-10-20 09:49:44 -07:00
Taiki Endo 37a1d4ecd2
Remove needless imports (#159) 2023-10-08 17:23:34 +09:00
Taiki Endo a559165acd
Migrate to Rust 2021 (#158) 2023-10-08 14:46:23 +09:00
tison ceb88a46c4
chore: prefer usize::MAX to std::usize::MAX
Signed-off-by: tison <wander4096@gmail.com>
2023-10-05 20:10:18 -07:00
John Nunley d9a65fdd73
v3.2.0
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-02 07:37:32 -07:00
Al Hoang 99a32b7607
feat: Add support for Haiku OS
Haiku does not support pipe_with at all, so just fall back to pipe().
2023-10-01 20:57:41 -07:00
John Nunley 9e143a38e1
bugfix: Manage sources being inserted into kqueue
Thus far, our kqueue implementation has been a relatively thin layer on
top of the OS kqueue. However, kqueue doesn't keep track of when the
same source is inserted twice, or when a source that doesn't exist is
removed. In the interest of keeping consistent behavior between backends
this commit adds a system for tracking when sources are inserted.

Closes #151

Signed-off-by: John Nunley <dev@notgull.net>
2023-09-27 21:30:46 -07:00
John Nunley 45ebe3b904
v3.1.0
Signed-off-by: John Nunley <dev@notgull.net>
2023-09-25 09:42:41 -07:00
David Hotham 254577da8d
feat: introduce Event::new()
This makes it easier to construct Events.
2023-09-12 06:02:02 -07:00
Taiki Endo 8c99506375 Update actions/checkout action to v4 2023-09-10 18:28:12 +09:00
John Nunley 256542375c
v3.0.0
Signed-off-by: John Nunley <dev@notgull.net>
2023-09-04 18:41:32 -07:00
John Nunley 90c661f5e1 Remove the std default feature
Added in 272bb11eaf for reasons that are
unclear to me. It serves no purpose aside from disabling the API of the
entire crate, so it's best to remove it.

Signed-off-by: John Nunley <dev@notgull.net>
2023-09-04 22:15:28 +02:00
John Nunley 7718565d11
Remove libc from dev deps (#146)
Signed-off-by: John Nunley <dev@notgull.net>
Signed-off-by: Alain Zscheile <fogti+devel@ytrizja.de>
2023-09-04 22:12:42 +02:00
Alex Touchet 792618094c
docs: Fix CI badge and update links
Closes #142
2023-09-04 08:33:22 -07:00
Taiki Endo 9a3fe18981 Use std::os::raw::c_int and remove our own type alias 2023-08-30 02:19:17 +09:00
tison d8595b56a5
feat: Make the constructors for Event const
This allows the Event struct to be used in constants.

Signed-off-by: tison <wander4096@gmail.com>
2023-08-21 18:32:21 -07:00
John Nunley c7cc91a1f1
docs: Specify behavior when registered in multiple pollers
This adds documentation to add() describing what happens when a source
is registered in multiple pollers. A test is also added to ensure this
behavior.

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-16 09:48:14 -07:00
John Nunley 2c279b871c
feat: Add a pipe-based notifier to epoll
In some containers, eventfd is not available as it cannot be implemented
securely in some hosts. This commit adds a fallback notifier that uses
a pipe instead of eventfd.

Closes #122

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-16 08:33:58 -07:00
John Nunley a521cd2c29
breaking: Extract the Events struct and make the Event struct opaque
* Add better documentation for the IOCP module

* Extract Events from Poller

This prevents the need to have an intermediate buffer to read events
from, reducing the need for an allocation and a copy. This is a breaking
change.

* Add event extra information

Foundation for more details later on.

* Add PRI and HUP events
* Fix various failing tests

- Make sure that waitable handles interact properly with the new
  infrastructure
- Fix failing doctests

* Review comments

- Make set_* take a boolean for the value of the flag
- Make Events !Sync
- Fix visibility modifiers
- Inline more methods
- Use a better strategy for testing

* Move completion packets into the Events buffer

This removes one of the mutexes that we have to lock.

* Review comments

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-14 10:03:20 -07:00
John Nunley e42664d57e
m: Remove libc from our dependencies
This means that we only depend on rustix, so all of the system calls
are direct. This should make mustang integration trivial.

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-13 18:49:43 -07:00
John Nunley c6a0890627
feat: Add support for waiting on waitable handles
* Add support for polling waitable handles

* Add a smoke test

* Fix failing tests

* Rebase on latest master

Signed-off-by: John Nunley <dev@notgull.net>

* Update semantics for new system

Signed-off-by: John Nunley <dev@notgull.net>

* Forgot about doctests

Signed-off-by: John Nunley <dev@notgull.net>

---------

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-08 20:40:21 -07:00
ivmarkov 6d13def8ab
Allow pinning Rust nightly to concrete version (#132) 2023-08-07 02:24:30 +09:00
ivmarkov 26afefbbbc
Remove patch.crates-io now that rustix 0.38.7 is out (#131) 2023-08-06 14:58:11 +09:00
John Nunley 49152081d0
Fix failing CI (#130)
- Disable async-io tests for wine
- Fix errant cast in Android

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-05 08:17:53 -07:00
ivmarkov 53793382a7
feat: Support for the ESP-IDF framework
* Support for the ESP-IDF framework

* Restore the spans to work with the raw notify fd

* On Linux eventfd needs PollFlags::IN

* Add cargo check for ESP IDF to the CI

---------

Co-authored-by: imarkov <imarkov@vmware.com>
2023-08-05 07:28:59 -07:00
John Nunley 6eb7679aa3
breaking: Rework the API for I/O safety
* Rework the API for I/O safety

* Bump to rustix v0.38
2023-08-03 20:15:59 -07:00
John Nunley c86c3894c1
Add smol-rs logo (#127) 2023-07-17 14:30:22 +09:00
dependabot[bot] ea5946c453 Update fastrand requirement from 1.9.0 to 2.0.0
Updates the requirements on [fastrand](https://github.com/smol-rs/fastrand) to permit the latest version.
- [Release notes](https://github.com/smol-rs/fastrand/releases)
- [Changelog](https://github.com/smol-rs/fastrand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/fastrand/compare/v1.9.0...v2.0.0)

---
updated-dependencies:
- dependency-name: fastrand
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-16 19:16:56 +09:00
Taiki Endo d41c05447a
Revert "Temporarily disable the wine test (#118)" (#126)
This reverts commit e2129ea879.

The upstream bug has been fixed.
2023-07-12 00:03:08 +09:00
John Nunley a8eb2dfc48
m: Remove the bitflags dependency 2023-06-28 20:19:12 -07:00
John Nunley 7a1fd31944
Replace log with tracing (#119) 2023-06-20 20:28:18 -07:00
John Nunley e2129ea879
Temporarily disable the wine test (#118) 2023-06-11 10:40:08 -07:00
John Nunley 5df378f811
Bump MSRV to 1.63 (#117)
Removes the build script and bumps bitflags to v2
2023-06-11 10:37:25 -07:00
Ivan Zvonimir Horvat e161698e6c
114: Examples; add nc message in 2 listeners (#115) 2023-05-31 08:50:27 -07:00
Taiki Endo 5e4410c937
Use Cirrus CI for NetBSD/OpenBSD (#110) 2023-04-29 06:05:19 +09:00
John Nunley 8d8d2efcc2
Replace libc with rustix in some backends (#108) 2023-04-23 07:26:29 -07:00
John Nunley d3a171b88b
v2.8.0 (#107) 2023-04-20 14:50:12 -07:00
John Nunley 75cff30584
feat: Add functionality for posting events to the IOCP (#101) 2023-04-16 07:37:48 -07:00
Taiki Endo 1e966a0848
Test patched async-io on more platforms (#79) 2023-04-10 15:34:34 +09:00
John Nunley 6857a165aa
Use the try-iter method from concurrent-queue (#105) 2023-04-08 22:05:37 -07:00
John Nunley 8f6d039b26
v2.7.0 (#104)
- Add edge/oneshot combination mode. (#96)
- Update windows-sys requirement from 0.45 to 0.48. (#103)
2023-04-07 11:20:57 -07:00
Taiki Endo 1e101c500f Update windows-sys to 0.48 2023-04-04 04:25:36 +09:00
John Nunley 0f38ed35ea
Add edge/oneshot combination mode (#96) 2023-03-25 15:22:45 +01:00
John Nunley e340458d3a
ci: Fix Android breakage (#99) 2023-03-22 11:21:31 -07:00
John Nunley e10c7e8da1
Version 2.6.0 (#92) 2023-03-08 10:42:18 -08:00
John Nunley f48f2c1a5a
test: Add test for more than 32 connections (#93)
Making sure that this works on Windows after #88
2023-03-08 10:40:23 -08:00
John Nunley 24900fb662
m(windows): Reimplement Wepoll in Rust (#88)
Reimplements the C-based wepoll backend in Rust, using some handwritten code. This PR also implements bindings to the I/O Completion Ports and \Device\Afd APIs. For more information on the latter, see my blog post on the subject: https://notgull.github.io/device-afd/

Note that the IOCP API is wrapped using a `Pin`-oriented "CompletionHandle" system that is relatively brittle. This should be replaced with a better model when one becomes available.
2023-03-05 16:25:25 -08:00
Taiki Endo e85331c437 Fix Dragonfly BSD CI
Dragonfly BSD 6.4.0 VM doesn't seem to have git.

```
  error: failed to get `cfg-if` as a dependency of package `polling v2.5.2 (/Users/runner/work/polling/polling)`

  Caused by:
    failed to load source for dependency `cfg-if`

  Caused by:
    Unable to update registry `crates-io`

  Caused by:
    failed to fetch `https://github.com/rust-lang/crates.io-index`

  Caused by:
    could not execute process `git fetch --force --update-head-ok 'https://github.com/rust-lang/crates.io-index' '+HEAD:refs/remotes/origin/HEAD'` (never executed)

  Caused by:
    No such file or directory (os error 2)
```
2023-03-04 15:35:17 +09:00
Taiki Endo 1e4467b1be Test more Windows targets on CI
- Test x86_64 gnu, i686 msvc, i686 gnu on Windows host
- Test patched async-io with Wine
2023-03-04 15:35:17 +09:00
Taiki Endo d443196f64 Use image_family for FreeBSD image in Cirrus CI 2023-02-06 22:02:43 +09:00
John Nunley a5aae98805
feat: Expose other kqueue filters (#83)
* feat: Expose other kqueue filters

* Fix netbsd/openbsd compilation

* Build MSRV for FreeBsd/OpenBsd in CI

* Only run MSRV BSD builds on Linux

* Change API a little + fix netbsd timer

* Add inlines + move PollerSealed

* rustfmt

* Make filter fields public

* Fix examples
2023-02-03 11:14:33 -08:00
dependabot[bot] 914aa48d67 Update windows-sys requirement from 0.42 to 0.45
Updates the requirements on [windows-sys](https://github.com/microsoft/windows-rs) to permit the latest version.
- [Release notes](https://github.com/microsoft/windows-rs/releases)
- [Commits](https://github.com/microsoft/windows-rs/compare/0.42.0...0.45.0)

---
updated-dependencies:
- dependency-name: windows-sys
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-23 23:41:01 +09:00
Taiki Endo 900b00c061 Test NetBSD/OpenBSD/Dragonfly BSD with vmactions 2023-01-23 00:15:00 +09:00
Taiki Endo ff86c54dfd Minimize GITHUB_TOKEN permissions
Refs: https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token
2023-01-21 20:38:57 +09:00
Taiki Endo 9b67232ea4 Test FreeBSD with Cirrus CI 2023-01-21 20:38:57 +09:00
Taiki Endo 3bc6310121 Set CARGO_NET_GIT_FETCH_WITH_CLI=true in CI 2023-01-21 20:38:57 +09:00
John Nunley 27f23a9384
m: Use EVFILT_USER instead of a self-pipe on kqueue where supported (#73)
* Use EVFILT_USER instead of a self-pipe

* Fix kqueue flags
2023-01-12 21:35:49 -08:00
John Nunley 729b5ee071
Use port_send for event ports (#74) 2023-01-12 21:35:28 -08:00
John Nunley dc4c5b4ec0
bugfix: Prevent large timeouts from causing panics (#71) 2023-01-07 19:35:46 -08:00
John Nunley 181acc67d0
Add level and edge triggered modes to the poller (#59)
* Add level and edge triggered modes to the poller

* Refractor error handling

* Add tests for new modes
2022-12-30 14:43:47 -08:00
Taiki Endo bc56d1fb38
Test the backend that uses poll on CI (#63) 2022-12-29 16:01:25 +09:00
Taiki Endo 1a3f61da89 Enable dependabot update for Rust 2022-12-28 12:24:56 +09:00
Taiki Endo 5e9bedbae8 Sort target_os cfg to match docs 2022-12-28 12:24:56 +09:00
Taiki Endo 14e1488537 Clean up CI config 2022-12-28 12:24:56 +09:00
Taiki Endo 341ca612b8 Update taiki-e/install-action to v2 2022-12-28 12:24:56 +09:00
John Nunley 97e6ecd0d0
Support tvOS and watchOS (#60) 2022-12-19 19:54:45 -08:00
Taiki Endo f0afd7788e
Release 2.5.2 (#58) 2022-12-13 12:26:59 +09:00
Alan Somers cebf394ca6
Future-proof the kevent API (#56)
In FreeBSD 12, kevent grew some extra fields.  libc currently implements
a FreeBSD 11 ABI, but that will change some day.  Tweak the kevent
initialization so it will compile with either version.
2022-12-13 12:16:19 +09:00
Wesley Wiser 5343302970
Update use of libc::timespec to prepare for future libc version (#55)
In a future release of the `libc` crate, `libc::timespec` will contain
private padding fields on `*-linux-musl` targets and so the struct will
no longer be able to be created using the literal initialization syntax.

Update `TS_ZERO` to create a value by initializing an array of the
correct size to `0` and then transmuting to `libc::timespec`. Update
struct literal use of `libc::timespec` to initialize to `TS_ZERO` and
then manually update the appropriate fields. Also updates a raw syscall
to use the libc function instead as on musl 1.2, it correctly handles
`libc::timespec` values which, in musl 1.2, are always 16 bytes in
length regardless of platform.
2022-12-03 23:04:37 +09:00
John Nunley 00e7eefc4d
Add error message for Wepoll (#54)
* Add error message for Wepoll

* Avoid unsupported error kind where it is not supported
2022-11-30 20:26:28 -08:00
Taiki Endo 0b45549097
Test x86_64-pc-windows-gnu with Wine on CI (#53) 2022-11-30 23:21:01 +09:00
Taiki Endo 1d54c93f9d Release 2.5.1 2022-11-29 22:21:43 +09:00
Taiki Endo 6f459f89a9 Use std::os::raw::c_int instead of std::ffi::c_int 2022-11-29 22:21:43 +09:00
Taiki Endo e5fe94732c Release 2.5.0 2022-11-27 16:22:51 +09:00
Taiki Endo 76ee52a68c Remove msrv field from .clippy.toml
Since Rust 1.64, Clippy respects `rust-version` field in Cargo.toml.
rust-lang/rust@b776fb8
2022-11-27 16:22:51 +09:00
John Nunley 8982599ddf
Port to windows-sys (#47) 2022-11-26 20:50:12 -08:00
Taiki Endo ca52490c9d Release 2.4.0 2022-10-24 00:26:11 +09:00
Taiki Endo bf6cbcc31c Fix build error on solarish 2022-10-24 00:13:27 +09:00
Taiki Endo de312d15b6 Reduce size of sys::Events 2022-08-21 23:40:10 +09:00
Taiki Endo e0daa5b327 Bump MSRV to 1.47 2022-08-21 23:40:10 +09:00
Taiki Endo d32e79b5dd Release 2.3.0 2022-08-21 20:14:55 +09:00
Taiki Endo b9cce6645c Apply doc(cfg(...)) on platform-specific APIs 2022-08-21 19:47:00 +09:00
John Nunley 323473ec1a
Expose raw handles for the `Poller` (#39)
* expose raw handles

* add comment explaining no_*
2022-08-18 09:52:28 -07:00
Taiki Endo 61c2179a38 Apply clippy to all targets 2022-07-17 21:22:06 +09:00
Taiki Endo 989c515fa2 Pin cross to 0.2.1 2022-07-03 20:57:45 +09:00
Taiki Endo 95c8207ac1 Update CI config 2022-07-03 20:57:32 +09:00
Taiki Endo 6b219eb0d7 Update actions/checkout action to v3 2022-05-01 14:46:00 +09:00
Taiki Endo 055e2711ae Ignore clippy::useless_conversion and clippy::unnecessary_cast lints 2022-01-09 02:00:40 +09:00
Taiki Endo a12be08c97 Fix MSRV 2022-01-09 01:58:56 +09:00
Taiki Endo 191e91655e Create GitHub release automatically 2022-01-09 01:50:53 +09:00
Taiki Endo dec94cb423 Clean up CI config 2022-01-09 01:50:42 +09:00
Taiki Endo 89ce9bafaa Bump to v2.2.0 2021-11-10 19:31:42 +09:00
Taiki Endo 4ac15ff339
Merge pull request #26 from Kestrer/master
Support VxWorks, Fuchsia and other Unix systems by using poll
2021-09-11 18:09:28 +09:00
Kestrer e902924621 Add Fuchsia target before crosscompiling in CI 2021-09-04 19:52:59 +01:00
Kestrer 597b6aed86 Add concurrent modification tests 2021-09-04 19:19:02 +01:00
Kestrer bbce346140 Add Fuchsia to cross workflow on CI 2021-09-04 18:01:56 +01:00
Kestrer 1930801c40 Document `poll` support in README 2021-09-04 18:00:25 +01:00
Kestrer ee13911717 Remove `extra_traits` libc feature 2021-09-04 17:58:56 +01:00
Kestrer 9c257ccef6 Cast to `nfds_t` instead of `u64` in `poll` 2021-09-04 17:47:54 +01:00
Zeeshan Ali 952fccb2f5 Release 2.1.0
Bumping minor (rather than micro) version as this is not a bugfix
release exactly but changes deps and license (indirectly).
2021-06-14 13:45:43 +02:00
Zeeshan Ali d13366703a
Merge pull request #36 from aclysma/wepoll-ffi
Switch wepoll-sys to wepoll-ffi to reduce licenses used in dependency tree (discussed in #35)
2021-05-27 15:29:35 +02:00
Philip Degarmo 784f9c108c Bump wepoll-ffi to 0.1.2
This modifies the polling patch to include this diff: 48520c99d9
2021-05-26 21:24:11 -07:00
Kestrer 6d2ccc4210 Depend on libc on Fuchsia and VxWorks 2021-05-26 13:57:38 +01:00
Kestrer c174c9b7bb Prevent timeout overflow for poll and wepoll 2021-05-26 10:33:24 +01:00
Kestrer e0c0032cc0 swap_remove deleted file descriptors in poll backend 2021-05-26 10:25:24 +01:00
Philip Degarmo 88cce716c9 Include patch to enable passing null lpOverlapped pointer to wake a thread 2021-05-26 01:44:53 -07:00
Philip Degarmo 2295ca07b8 Switch wepoll-sys to wepoll-ffi to reduce licenses used in dependency tree (discussed in #35) 2021-05-25 22:56:05 -07:00
Taiki Endo 1019f90247
Merge pull request #34 from smol-rs/next
Bump to v2.0.3
2021-03-20 19:56:05 +09:00
Taiki Endo fba6877b3c Skip test of async-io in MSRV 2021-03-20 19:44:57 +09:00
Taiki Endo 38441655db Bump to v2.0.3 2021-03-20 19:04:14 +09:00
Taiki Endo a11153d7b1
Merge pull request #32 from smol-rs/readme
Remove readme field from Cargo.toml
2021-02-14 20:03:44 +09:00
Taiki Endo 94adda1433 Remove readme field from Cargo.toml 2021-02-14 19:44:44 +09:00
Taiki Endo d963b53f0f
Merge pull request #31 from smol-rs/badge
Update license badge to match Cargo.toml
2021-02-14 14:06:13 +09:00
Taiki Endo 458d5c9101 Update license badge to match Cargo.toml 2021-02-14 13:36:54 +09:00
Koxiaet a04a265f9b Merge branch 'master' 2021-01-03 10:19:51 +00:00
Taiki Endo 2e27830522
Merge pull request #30 from taiki-e/url
Update URLs
2020-12-27 00:04:58 +09:00
Taiki Endo 99d9e20ba0 Update URLs 2020-12-26 23:47:13 +09:00
Taiki Endo 5aa15b58b0
Merge pull request #29 from taiki-e/deps
Update cfg-if to 1
2020-12-25 18:37:32 +09:00
Taiki Endo 84a3453537 Update cfg-if to 1 2020-12-25 18:30:50 +09:00
Taiki Endo 4e32cca7d3
Merge pull request #28 from taiki-e/compare_and_swap
Replace deprecated compare_and_swap with compare_exchange
2020-12-24 21:36:38 +09:00
Taiki Endo c009653b99 Replace deprecated compare_and_swap with compare_exchange 2020-12-24 21:18:55 +09:00
Taiki Endo 14c8d34655
Merge pull request #27 from taiki-e/ci
Fix CI
2020-12-24 18:35:49 +09:00
Taiki Endo 995f1b1091 Fix CI 2020-12-24 18:29:59 +09:00
Koxiaet 20c1e19c46 Have notifications break poll's operation loop 2020-12-18 15:03:59 +00:00
Koxiaet dd15b4cd8a Add logging to poll backend 2020-12-18 14:33:05 +00:00
Koxiaet e0789a8ee0 Make poll's poller modifications interrupt `wait` 2020-12-18 14:18:03 +00:00
Koxiaet 0f2f6ed15a Don't set CURRENT_WEEK in CI 2020-12-17 19:08:08 +00:00
Koxiaet 2fc0831d40 Use poll on VxWorks, Fuchsia and other Unix systems 2020-12-17 18:56:48 +00:00
Stjepan Glavina 4df98de5ae
. 2020-11-30 12:30:45 +01:00
Stjepan Glavina 30fcade0e4
Merge pull request #23 from papertigers/illumos-fixes
port_dissociate should be aware of ENOENT
2020-10-20 16:08:39 +02:00
Mike Zeller 6f778cddf1 port_dissociate should be aware of ENOENT 2020-10-19 22:19:12 +00:00
Stjepan Glavina 9c7e8061c5 Bump to v2.0.2 2020-10-19 00:25:40 +02:00
Stjepan Glavina 3a3020bb5c Use as_ptr and as_mut_ptr 2020-10-19 00:24:56 +02:00
Stjepan Glavina f4ee76fb32 Bump to v2.0.1 2020-10-09 14:10:22 +02:00
Stjepan Glavina 421bfc635d Reenable async-io tests 2020-10-03 17:09:03 +02:00
Stjepan Glavina 112da8b4e5 Bump to v2.0.0 2020-10-03 16:35:24 +02:00
Stjepan Glavina 7d1a4e3fe3
Merge pull request #18 from stjepang/fix-sigsegv
Try fixing sigsegv
2020-10-02 19:30:17 +02:00
Stjepan Glavina e8023dcb58 Put back winapi 2020-10-02 19:25:43 +02:00
Stjepan Glavina 5bd5cd226c Update wepoll-sys to 3.0.0 2020-10-02 19:19:18 +02:00
Stjepan Glavina 0bc0553217 Switch to published wepoll-sys 2020-10-02 18:34:49 +02:00
Stjepan Glavina b562ab84ea Use notified flag to make timeouts correct 2020-10-02 18:31:00 +02:00
Stjepan Glavina b25847f0ed Try fixing sigsegv 2020-10-02 18:24:37 +02:00
Stjepan Glavina 89ef82d11d Fix wepoll-sys version 2020-10-02 18:20:55 +02:00
Stjepan Glavina a3c748bcd0 Use more explicit type conversions 2020-10-02 18:19:15 +02:00
Stjepan Glavina 8b656241d5 Use saturating_add to prevent overflow 2020-10-02 18:17:45 +02:00
Stjepan Glavina d4667889b4 Switch to wepoll-sys 2020-10-02 17:41:55 +02:00
Stjepan Glavina 0a299cf060 Small cleanup 2020-10-02 17:34:53 +02:00
Stjepan Glavina 35add590ce
Merge pull request #17 from YorickPeterse/separate-poller-methods
Refactor Poller.add and Poller.interest
2020-10-02 16:32:20 +02:00
Yorick Peterse 29791dec13
Temporarily disable async-io tests
These will fail until a new version of polling is released and async-io
is updated.
2020-10-02 01:32:44 +02:00
Yorick Peterse ba05307af1
Separate adding and modifying of file descriptors
This replaces Poller.insert() and Poller.interest() with Poller.add()
and Poller.modify(), and renames Poller.remove() to Poller.delete().

The method Poller.add() is used for adding a new file descriptor, while
Poller.modify() is used for updating an existing one. Poller.remove() is
renamed to Poller.delete() so the naming scheme of these methods follows
that of epoll, wepoll, etc.

This new setup means that adding a new socket only requires a single
call of Poller.add(), instead of a combination of Poller.insert() and
Poller.interest(). This reduces the amount of system calls necessary,
and leads to a more pleasant API.

On systems that use kqueue or ports, the behaviour of Poller.add() and
Poller.modify() is the same. This is because on these systems adding an
already existing file descriptor will just update its configuration.
This however is an implementation detail and should not be relied upon
by users.

Migrating to this new API is pretty simple, simply replace this:

    poller.insert(&socket);
    poller.interest(&socket, event);

With this:

    poller.add(&socket, event);

And for cases where Poller.interest() was used for updating an existing
file descriptor, simply replace it will a call to Poller.modify().

See https://github.com/stjepang/polling/issues/16 and
https://github.com/stjepang/polling/pull/17 for more information.
2020-10-01 21:50:59 +02:00
Yorick Peterse 4e5c3ce836
Don't automatically make descriptors non-blocking
This adds redundant system call overhead for file descriptors which have
already been turned into non-blocking file descriptors. In addition, the
polling crate doesn't need to implement platform specific code for
enabling non-blocking mode. Instead, users of polling can do so using
(for example) standard library methods such as
TcpListener.set_nonblocking().

See https://github.com/stjepang/polling/issues/16 for more information.
2020-10-01 20:06:07 +02:00
Stjepan Glavina 5309d571b2 Bump to v1.1.0 2020-09-20 17:45:49 +02:00
Stjepan Glavina 272bb11eaf Add feature 2020-09-20 17:45:24 +02:00
Stjepan Glavina 8c7ce8fb5b Bump to v1.0.3 2020-09-16 11:46:25 +02:00
Stjepan Glavina 8a77220000
Merge pull request #15 from JayceFayne/deps
remove `libc` dependency on windows
2020-09-16 11:45:32 +02:00
Jayce Fayne 2ecb5564e8 remove `libc` dependency on windows 2020-09-16 11:23:03 +02:00
Stjepan Glavina 8e35897e53 Bump to v1.0.2 2020-09-15 20:53:33 +02:00
Stjepan Glavina c2e3322b1e
Merge pull request #10 from oblique/master
Check if `epoll_create1` is implemented by its error code
2020-09-15 20:51:19 +02:00
oblique f7f67e8745 Fix linkage error for android-16 toolchain 2020-09-15 14:01:09 +03:00
oblique 62103c1ef0 Check if `epoll_create1` is implemented by its error code
If binary is statically linked to libc then `libc::dlsym`
will always return `NULL`. This is the case when a target
with musl is used.

To fix this we check if error code of `epoll_create1` is `ENOSYS`.
2020-09-15 14:01:09 +03:00
Stjepan Glavina 4fc3040bff Pass timeout in ms to epoll_wait 2020-09-14 21:06:16 +02:00
Stjepan Glavina a954600d6a Bump MSRV to 1.40.0 2020-09-14 20:44:44 +02:00
Stjepan Glavina 46634fc32a Fix CI 2020-09-14 20:38:59 +02:00
Stjepan Glavina d8c32a0140 Test async-io 2020-09-14 20:31:23 +02:00
Stjepan Glavina 7738ca0b25 Remove doc-comment because the readme then needs the ugly #-prefixed line 2020-09-07 19:53:31 +02:00
Stjepan Glavina 1992605564 Bump to v1.0.1 2020-09-07 19:41:24 +02:00
Stjepan Glavina b05877c3ec Bump to v1.0.0 2020-09-07 16:44:39 +02:00
Stjepan Glavina 8c2f5e4169 Bump to v0.1.9 2020-09-06 11:10:54 +02:00
Stjepan Glavina 6d4724ba35 Test on Linux x32 2020-09-06 10:56:48 +02:00
Stjepan Glavina a8e3673939
Merge pull request #14 from sunfishcode/master
Fix compilation on x32
2020-09-06 10:54:10 +02:00
Dan Gohman d22ed8fd7b Fix compilation on x32
Fix compilation on x86_64-unknown-linux-gnux32, where
libc's timespec's `tv_nsec` is [defined as `i64`]. See also
[this bug] bug for further discussion.

[defined as `i64`]: https://github.com/rust-lang/libc/blob/master/src/unix/mod.rs#L61
[this bug]: https://sourceware.org/bugzilla/show_bug.cgi?id=16437
2020-09-05 22:17:26 -07:00
Stjepan Glavina f03bb7acf0 Bump to v0.1.8 2020-09-03 12:56:12 +02:00
Stjepan Glavina bc3dc1ec6e Replace log::debug with log::trace 2020-09-03 12:55:46 +02:00
38 changed files with 7493 additions and 1111 deletions

61
.cirrus.yml Normal file
View File

@ -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
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
github: stjepang

9
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly
commit-message:
prefix: ''
labels: []

View File

@ -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

187
.github/workflows/ci.yml vendored Normal file
View File

@ -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 }}

View File

@ -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

View File

@ -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

22
.github/workflows/release.yml vendored Normal file
View File

@ -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 }}

View File

@ -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 }}

View File

@ -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.

View File

@ -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"

3
Cross.toml Normal file
View File

@ -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"

View File

@ -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.

36
examples/tcp_client.rs Normal file
View File

@ -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(())
}

View File

@ -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!(),
}

76
examples/wait-signal.rs Normal file
View File

@ -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.");
}

View File

@ -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() {},
}
}
}

688
src/iocp/afd.rs Normal file
View File

@ -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())
}

1397
src/iocp/mod.rs Normal file

File diff suppressed because it is too large Load Diff

298
src/iocp/port.rs Normal file
View File

@ -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() });
}
}

View File

@ -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()
}
}
}

File diff suppressed because it is too large Load Diff

27
src/os.rs Normal file
View File

@ -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 {}
}

253
src/os/iocp.rs Normal file
View File

@ -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 {}

303
src/os/kqueue.rs Normal file
View File

@ -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;
}
}

830
src/poll.rs Normal file
View File

@ -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
}
}
}

View File

@ -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))
}
}

View File

@ -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,
})
}
}

View File

@ -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))
}

89
tests/io.rs Normal file
View File

@ -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))
}

66
tests/many_connections.rs Normal file
View File

@ -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))
}

358
tests/multiple_pollers.rs Normal file
View File

@ -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))
}

View File

@ -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()

279
tests/other_modes.rs Normal file
View File

@ -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))
}

View File

@ -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(())

View File

@ -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)))?;

64
tests/windows_post.rs Normal file
View File

@ -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);
}

138
tests/windows_waitable.rs Normal file
View File

@ -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());
}