-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow consumers to implement POSIX AIO sources. #4054
Conversation
Unlike every other kqueue event source, POSIX AIO registers events not via kevent(2) but by a different mechanism that needs the kqueue's file descriptor. This commit adds a new `PollAio` type, designed to be used by an external crate that implements a POSIX AIO Future for use with Tokio's reactor. Fixes: tokio-rs#3197
Per @Darksonn Tokio has a policy of never exposing mio data types in its public API. This change creates a new AioSource trait that is similar to mio::event::Source but stripped down to work only for POSIX AIO.
cc @Darksonn |
tokio/tests/io_poll_aio.rs
Outdated
mod aio { | ||
use super::*; | ||
|
||
/// aio_fsync is the simplest AIO operation. This test ensures that Tokio | ||
/// can register an aiocb, and set its readiness. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For both tests, I'd like to see something that performs more than one operation to verify that the readiness flag behaves properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For non-lio-listio operations there really isn't any such thing as performing more than one operation. The kernel forgets about the aiocb as soon as you call aio_return
to get its result. You could change some fields and resubmit it, but that's equivalent to creating a new aiocb
.
For lio_listio, it's hard to write a small test that requires clear_readiness
. The only way that would ever be required would be due to a race or a system resource limitation. Here's a test that deliberately hits a system resource limitation
https://github.com/asomers/tokio-file/blob/master/tests/lio_listio_incomplete.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you already have a test for it, it would be nice to include it in Tokio as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but it will take a while to adapt, to make it run without all of the other stuff in tokio-file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's important to have at least one test that includes a call to clear_readiness
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, here are the tests you asked for.
The original version of these tests used a futures_test::task::noop_context so it could manually poll the futures. But it still constructed a Runtime, because PollAio requires a Runtime for its construction. This caused the Runtime to poll the future in a tight loop after event notification. Remove noop_context, implement Future, and use a normal #[tokio::test] instead.
Also, document clear_ready's uses more accurately
/// | ||
/// An example bypassing mio_aio and Nix to demonstrate how the kevent | ||
/// registration actually works, under the hood. | ||
struct LlSource(Pin<Box<libc::aiocb>>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The libc::aiocb
type is Unpin
so pinning it doesn't do anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is actually a kernel requirement, not a Futures requirement, that the libc::aiocb
have a stable address, because the kernel uses that address to identify it. Nix gets around the Unpin
problem by adding a std::marker::PhantomPinned
field. I could do that here too, though it isn't strictly necessary to test Tokio's functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Putting it in a box is enough here, but I'm also ok with keeping the Pin
as it still does serve to document your intent. You may find this interesting.
Per @Darksonn, even though these things aren't really related, they may as well use the same feature flag because they both pull in the same mio dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggestions below will make PollAio
show up in the docs.rs documentation for Tokio, which is built on Linux rather than FreeBSD. Feel free to tweak the details.
tokio/src/macros/cfg.rs
Outdated
macro_rules! cfg_aio { | ||
($($item:item)*) => { | ||
$( | ||
#[cfg(all(target_os = "freebsd", feature = "net"))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#[cfg(all(target_os = "freebsd", feature = "net"))] | |
#[cfg(any(docsrs, all(target_os = "freebsd", feature = "net")))] |
* export PollAio from tokio::io::poll_aio instead of tokio::io, to be more consistent with how AsyncFd does it. * build the docs for PollAio when building docs for any OS.
It passed on my system. Not sure why it failed in CI.
@Darksonn anything else you'd like to see here? |
There will probably also be some other people taking a look, but I don't have anything else. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. I think it is almost there and left a few smaller naming / API comments.
@Darksonn if you disagree w/ any of these comments, feel free to push back.
tokio/src/io/poll_aio.rs
Outdated
/// is scheduled to receive a wakeup when the underlying operation | ||
/// completes. Note that on multiple calls to poll, only the Waker from the | ||
/// Context passed to the most recent call is scheduled to receive a wakeup. | ||
pub fn poll<'a>(&'a self, cx: &mut Context<'_>) -> Poll<io::Result<PollAioEvent>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We avoid inherent fns named just poll
as they would conflict w/ Future
. Also, why is this an inherent fn? If it were implemented as a future, then it could be used with .await
.
Perhaps, the inherent fn could be named poll_ready
(or something) and an equivalent async fn
is added async fn ready(&self) -> io::Result<AioEvent>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under the principle that "futures should do nothing until polled", I elected to submit the operation to the kernel (aio_write
, etc) as part of poll
. But that means that it needs to be in the external crate instead of in Tokio. Hence the inherent function. I'll rename it to poll_ready
.
The test failure is not the result of this PR. |
Co-authored-by: Alice Ryhl <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. There are a few minor details below, but nothing blocking.
Spelling and formatting in the comments, plus remove the trait bound from Aio's struct definition. Co-authored-by: Alice Ryhl <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sticking around and getting this through 👍
Thanks guys! BTW, just this morning I learned of a new use case for POSIX AIO on sockets. I think it should work fine with the interface in this PR, though. I may add a new test case for it. |
Sure, go ahead and open a PR with the extra test case. |
Unlike every other kqueue event source, POSIX AIO registers events not
via kevent(2) but by a different mechanism that needs the kqueue's file
descriptor. This commit adds a new PollAio type, designed to be used
by an external crate that implements a POSIX AIO Future for use with
Tokio's reactor.
Fixes: #3197
Motivation
With Tokio 0.1 and 0.2, PollEvented was sufficiently powerful to allow external crates to implement a POSIX AIO event source. Beginning with Tokio 0.3, PollEvented became private, and it also lost some needed functionality. Thus, there was no possible way for a consumer to use POSIX AIO with Tokio.
Solution
The first proposed solution was to reexpose
PollEvented.
That idea was rejected because Tokio's maintainers thought thatPollEvented
peered too intimately into Tokio's internals, and they wanted a more stable external API that wasn't tied to mio's implementation.The second solution was made in PR #3841. It created a new type,
PollAio
that worked much likePollEvented
but crippled so that it would only be useful with POSIX AIO. However, its public API did include themio::event::Source
trait, so Tokio's maintainers rejected it because it still exposed mio data types. This PR preserves that solution as a discrete commit.This PR introduces a third solution. It builds on the previous one, but instead of using
mio::event::Source
in the public API, it creates a new trait:AioSource
. The new trait is very similar tomio::event::Source
, but stripped down.The new solution's advantages are that:
mio::event::Source
The new solution's disadvantages are that:
AioSource
is an unhelpful anti-abstraction.mio_aio::AioCb
already implementsmio::event::Source
. But now tokio_file must implementAioSource
on top of that, only for Tokio to internally reimplementmio::event::Source
again. At least the runtime impact is likely to be tiny.AioSource
deabstractsmio_aio::AioCb
andmio_aio::LioCb
. Previously in solution 2 those could be passed to Tokio in exactly the same way. Now, Tokio's consumers must separately implementAioSource
for both. See the tests in this PR.RawFd
andusize
in the public API instead ofRegistry
andToken
it conceals information, reducing the ability to detect bugs at compile time, though not technicallyunsafe
.