-
Notifications
You must be signed in to change notification settings - Fork 122
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
Implement IntoRawFd for SharedFd and File #228
base: master
Are you sure you want to change the base?
Conversation
afeb136
to
ad6d598
Compare
This is probably very reasonable but three things come to mind. |
To quote from https://man.archlinux.org/man/close.2.en
|
Maybe that PR should include a function that can be used to test whether a File is a regular file descriptor or a fixed slot file descriptor so code that would otherwise panic can be circumvented by the caller. |
|
// Consumes this object, returning the raw underlying file descriptor. | ||
// Since all in-flight operations hold a reference to &self, the type-checker | ||
// will ensure that there are no in-flight operations when this method is | ||
// called. |
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 understand this comment that says the in-flight operations hold a reference to &self. The in-flight operations I think you mean actually hold a cloned SharedFd, like in
Line 10 in afab7f2
fd: SharedFd, |
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.
Ah yeah... when I was trying to wrap my head around SharedFd, I came to the following conclusion (please correct me If I am wrong): The SharedFd close
method will hang forever if there is any in-flight IO. However, the actual File::close API does not appear to share this issue because the Rust borrow-checker already actively prevents in-flight asynchronous methods (or even just references to File) when transferring ownership of a tokio_uring::File (either by File::close or by File::into_raw_fd).
Example 1: OK
fn main() -> std::io::Result<()> {
tokio_uring::start(async {
let file = File::open("test.txt").await?;
let fd = file.into_raw_fd();
let file = unsafe { File::from_raw_fd(fd) };
file.close().await?;
Ok(())
})
}
Example 2: Compilation Error
An async block that isn't awaited before calling into_raw_fd
or close
will result in an error.
fn main() -> std::io::Result<()> {
tokio_uring::start(async {
let file = File::open("test.txt").await?;
tokio_uring::spawn(async {
let buf: Vec<u8> = Vec::with_capacity(16);
let (buf, res) = file.read_at(buf, 0).await;
});
let fd = file.into_raw_fd();
let file = unsafe { File::from_raw_fd(fd) };
file.close().await?;
Ok(())
})
}
Example 3: Compilation Error
cannot move out of an Rc
fn main() -> std::io::Result<()> {
tokio_uring::start(async {
let file = std::rc::Rc::new(File::open("test.txt").await?);
let file2 = file.clone();
tokio_uring::spawn(async move {
let buf: Vec<u8> = Vec::with_capacity(16);
let (buf, res) = file2.read_at(buf, 0).await;
});
let fd = file.into_raw_fd();
let file = unsafe { File::from_raw_fd(fd) };
file.close().await?;
Ok(())
})
}
Example 4: Ok
This compiles since we use try_unwrap (which ensures that there are no other references to File).
fn main() -> std::io::Result<()> {
tokio_uring::start(async {
let file = std::rc::Rc::new(File::open("test.txt").await?);
let file2 = file.clone();
tokio_uring::spawn(async move {
let buf: Vec<u8> = Vec::with_capacity(16);
let (buf, res) = file2.read_at(buf, 0).await;
});
match std::rc::Rc::try_unwrap(file) {
Ok(file) => {
let fd = file.into_raw_fd();
let file = unsafe { File::from_raw_fd(fd) };
file.close().await;
},
Err(err) => {
panic!("unexpected io in-flight")
}
}
Ok(())
})
}
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.
Example 5: In-flight IO possible only with (incorrect usage of) unsafe code.
fn main() -> std::io::Result<()> {
tokio_uring::start(async {
let file = File::open("test.txt").await?;
let fd = file.into_raw_fd();
tokio_uring::spawn(async move {
let file = unsafe { File::from_raw_fd(fd) };
let buf: Vec<u8> = Vec::with_capacity(16);
let (buf, res) = file.read_at(buf, 0).await;
});
let file = unsafe { File::from_raw_fd(fd) };
file.close().await;
Ok(())
})
}
Does the whole notion of going from File to raw and back to a new File, when the SharedFd can be cloned, and defining the close call to triggered when a SharedFd's Inner's refcount drops to zero, call into question our whole close model? |
ad6d598
to
ee7a299
Compare
I'm right with you on both points. I think that we should absolutely simplify the SharedFd close logic. If I am thinking about this correctly, it would be sufficient for SharedFd to just panic if there are still any in-flight operations when closed. |
Well, I don't want to invoke potential panics - in the code or in the users. The user may choose to call the synchronous close(2) on the raw fd. The raw_fd is available without giving up ownership, and without using I'm thinking we should offer some functionality under |
@FrankReh This is not the case, the RawFd type is useless on its own (you can't perform any IO, it isn't closed on drop, etc). Synchronously losing a RawFd requires either using libc (unsafe), |
@thomasbarrett Yes, as in the other issue, I was incorrect thinking close on raw didn't require unsafe code. |
How does panicking when there are in flight operations during a drop help? Isn't it better to wait for the in flight operations to finish? |
Are you saying the current logic for waiting and then closing is broken? I wish you had referenced If that is essentially the same issue, I would have agreed something should be done. Let me try to implement reproducer of the perceived problem. |
@thomasbarrett And the comment you are proposing to add at line 860, "Since all in-flight operations hold a reference to &self". |
Good point. It would probably be more semantically correct to replace the panics with |
Ahhh I should have looked through the old issues :). This seems very likely related. I will test the example on my "Simplify SharedFd close logic" branch and see if I can reproduce it. |
I see now that there was something that I didn't fully understand . The "in-flight" operations don't necessarily need to be associated with any future held by the library user... there may also be futures that the user dropped (cancelled) in-flight. I'm not sure what should be done. I found this old reddit link from when tokio_uring was first released. Although I can't find the original design doc, someone quoted the design doc in saying:
|
Ownership over operation futures that are dropped by library users are transferred to the runtime, which is supposed to cancel them and drop. I will update this PR so that into_raw_fd waits for cancelled operations to complete instead of panic-ing once the fix for this is merged. |
This PR implements the
IntoRawFd
trait for both the SharedFd and File types.See this issue for additional context.