-
Notifications
You must be signed in to change notification settings - Fork 114
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
Defer work until polled #165
Conversation
|
@Noah-Kennedy I have a question: What are the semantics for Ops polled outside of the runtime? Failure or panic? Line 75 in 4b0e07e
This PR keeps that special casing (Close is the only caller of try_submit_with). I actually think try_submit_with with should be However, I'm unsure why we're going to such lengths to allow Ops to execute outside of a runtime? |
src/driver/op.rs
Outdated
@@ -40,14 +42,18 @@ pub(crate) struct Op<T: 'static, CqeType = SingleCQE> { | |||
/// A Marker for Ops which expect only a single completion event | |||
pub(crate) struct SingleCQE; | |||
|
|||
/// A Marker for Ops which may be submitted when the ring is not present | |||
/// The only user of this is `Close` currently | |||
pub(crate) struct Fallible; |
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'm unconvinced this is needed. changing try_submit_with to try_submit_with_immediate or similar, which would submit the operation immediately would be sufficient
Summary: Ops no longer immediately submit. Instead, when a new op is created, the Sqe is calculated as before, and placed in a new Lifecycle variant, This has some nice properties.
The question re |
I actually think that this special casing of close needs to go. We should really be doing a synchronous close anyways. That said, this is an issue that needs some discussion. Right now "panic" is fine, but if we add a |
@Noah-Kennedy @FrankReh Ready for review
Benchmarks
|
@@ -9,7 +9,7 @@ pub(crate) struct Accept { | |||
} | |||
|
|||
impl Op<Accept> { | |||
pub(crate) fn accept(fd: &SharedFd) -> io::Result<Op<Accept>> { |
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.
Op creation is infallible
|
||
pub(crate) struct Close { | ||
fd: RawFd, | ||
} | ||
|
||
impl Op<Close> { | ||
/// Close a file descriptor |
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.
Comments unrelated to the PR, but helped me understand the whole Close shutdown saga whilst implementing
pub(crate) fn close(fd: RawFd) -> io::Result<Op<Close>> { | ||
use io_uring::{opcode, types}; | ||
|
||
Op::try_submit_with(Close { fd }, |close| { | ||
let op = Op::submit_with(Close { fd }, |close| { |
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.
Create a Close Op: infallible
}) | ||
}); | ||
|
||
op.enqueue() |
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.
immediately enqueue the Op: fallible
@@ -13,7 +13,7 @@ pub(crate) struct Connect { | |||
|
|||
impl Op<Connect> { | |||
/// Submit a request to connect. | |||
pub(crate) fn connect(fd: &SharedFd, socket_addr: SockAddr) -> io::Result<Op<Connect>> { | |||
pub(crate) fn connect(fd: &SharedFd, socket_addr: SockAddr) -> Op<Connect> { |
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.
Op creation is infallible
for (_, cycle) in self.lifecycle.iter() { | ||
match cycle { | ||
Lifecycle::Completed(_) | Lifecycle::Pending(_) => {} | ||
c => unreachable!("Lifecycle {:?} found during driver drop", c), | ||
} | ||
} |
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.
Stricvly unrelated, but gives better panic messages when something goes wrong during debug. can revert or PR elsewhere if preferred.
@@ -46,7 +48,10 @@ pub(crate) trait Completable { | |||
} | |||
|
|||
pub(crate) enum Lifecycle { | |||
/// The operation has been submitted to uring and is currently in-flight | |||
/// The operation has been created, and will be submitted when polled | |||
Pending(Entry), |
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 meat of the PR
impl std::fmt::Debug for Lifecycle { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
Self::Pending(_) => f.debug_tuple("Pending").finish(), | ||
Self::Submitted => f.debug_tuple("Submitted").finish(), | ||
Self::Waiting(_) => f.debug_tuple("Waiting").finish(), | ||
Self::Ignored(_) => f.debug_tuple("Ignored").finish(), | ||
Self::Completed(_) => f.debug_tuple("Completed").finish(), | ||
Self::CompletionList(_) => f.debug_tuple("CompletionList").finish(), | ||
} | ||
} | ||
} |
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.
Debug only. Again, can revert or PR elsewhere if preferred.
/// the kernel. | ||
pub(super) fn submit_with<F>(data: T, f: F) -> io::Result<Self> | ||
pub(super) fn submit_with<F>(mut data: T, f: F) -> Self |
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.
Submission to the driver is infallible. This now places the seq for submission into the Pending state
/// | ||
/// This is useful when any failure in io_uring submission needs | ||
/// to be handled at the point of submission. | ||
pub(super) fn enqueue(self) -> io::Result<Self> { |
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.
Does nothing if called on an already submitted operation. TODO: improve docs
Lifecycle::Submitted | Lifecycle::Waiting(_) => { | ||
*lifecycle = Lifecycle::Ignored(Box::new(self.data.take())); | ||
} | ||
Lifecycle::Completed(..) => { | ||
Lifecycle::Completed(..) | Lifecycle::Pending(_) => { |
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 the op is dropped before it is submitted to the ring, no work on the ring is scheduled.
// | ||
// TODO: Should we warn? | ||
*state = match CONTEXT.try_with(|cx| cx.is_set()) { | ||
Ok(true) => match Op::close(self.fd) { |
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.
As Close is special cased (preserving the current behavior to immediately submitting to the ring) , we can fallback to synchronous Close as required.
*state = State::Closed; | ||
Poll::Ready(()) | ||
Poll::Ready(r) |
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.
Propagates any error in Close from io_uring
} else { | ||
Err(io::ErrorKind::Other.into()) | ||
} | ||
/// Enqueue an operation on the submission ring, without `await`ing the future |
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.
Could end the sentence with a period, to be consistent with other changes to this 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.
Not expressing an opinion on this one. @Noah-Kennedy 's is the only one that matters here as he's working on this issue too. I'm happy to read and think through this more if either of you think it's warranted however.
@ollie-etl see my comments in #120. I'm not sure that this is the right approach. |
@mzabaluev As you're pretty active in PR's currently, do you have a view on deferred operations? |
// Try to push the new operation | ||
while unsafe { driver.uring.submission().push(&sqe).is_err() } { | ||
// Fail with an IoError if encountered | ||
driver.submit()?; |
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 lifecycle entry will stay in the Submitted
state even if this fails?
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.
Hmm, yes. Bug. It should be completed
@@ -40,12 +40,12 @@ impl Socket { | |||
} | |||
|
|||
pub(crate) async fn write<T: IoBuf>(&self, buf: T) -> crate::BufResult<usize, T> { | |||
let op = Op::write_at(&self.fd, buf, 0).unwrap(); | |||
let op = Op::write_at(&self.fd, buf, 0); |
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 believe the async
transformation works so that this is not run until the future returned by write
is polled for the first time. If Op
creation only happens in such places, we have nothing to worry about?
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 don't follow, sorry
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.
Line 43 is not executed synchronously, that happens on the first poll of the future returned by write
. So this method does not actually do any work until polled.
Is there any instance where submit_with
is called inside a synchronous fn?
@oliverbunting as a result of the convo we had in the issue thread as well as discord, I think it might be better to move this to draft |
Or potentially even close this since its actually part of a larger discussion we are having. |
In my experience, PRs marked draft get ignored, and Id like to invite feedback |
This PR closes #120. It changes the semantics of all oeprations to do no work unless polled, which is generally the expected behavior of Futures.
As an aside, the API change gets rid of quite a few unwraps around Op creation, which are now mostly infallible, unless they have fallible steps during their creation