Skip to content
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

Relax lifetime requirements for PollFd::new #2134

Merged
merged 4 commits into from
Oct 1, 2023
Merged

Conversation

asomers
Copy link
Member

@asomers asomers commented Sep 23, 2023

Fixes #2118

@asomers
Copy link
Member Author

asomers commented Sep 23, 2023

cc @SteveLauC

@MaxVerevkin
Copy link

I think the argument should not be a reference at all, there is impl<T: AsFd> AsFd for &T.

src/poll.rs Outdated Show resolved Hide resolved
@SteveLauC
Copy link
Member

SteveLauC commented Sep 24, 2023

If it takes fd by value, then we should add a warning to the users in the doc comment to not let them pass the types that are owned file descriptors (e.g., File)

Kinda think we are facing this issue everywhere, here is also a thread discussing this stuff: #2115

@asomers
Copy link
Member Author

asomers commented Sep 29, 2023

I think the argument should not be a reference at all, there is impl<T: AsFd> AsFd for &T.

Actually, this is subtly wrong. And it's probably wrong for all constructors. If PollFd::new takes fd by value, then it immediately drops it, causing the file descriptor to be closed. The fact that it currently compiles at all means that there is probably something wrong with the lifetime handling; I'll double check that.

Now that I've reflected on this, the original approach of only taking a reference was a pretty good approach, because usually a PollFd isn't useful if it owns the file descriptor. After all, you usually want to do something else with the file other than just poll it. If we want PollFd to actually own it, then we should probably take an OwnedFd.

@asomers
Copy link
Member Author

asomers commented Sep 29, 2023

@MaxVerevkin Here's an example that demonstrates why PollFd must take its argument by reference. In this example, PollFd::new appears to take its argument by ownership, but it actually drops the OwnedFd at the end of the new method. This example compiles with the current state of the branch, but it ought to fail.

    /// ```compile_fail,E0597
    /// # use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd};
    /// # use nix::{
    /// #     poll::{PollFd, PollFlags, poll},
    /// #     unistd::{pipe, read}
    /// # };
    /// let pfd = {
    ///     let (r, w) = pipe().unwrap();
    ///     let r = unsafe { OwnedFd::from_raw_fd(r) };
    ///     PollFd::new(r, PollFlags::POLLIN)
    /// };
    /// let mut fds = [pfd];
    /// poll(&mut fds, -1).unwrap();
    /// let mut buf = [0u8; 80];
    /// read(pfd.as_fd().as_raw_fd(), &mut buf[..]);
    /// ```

So either we must take by reference, or else the struct should include an OwnedFd. Or, it could take an OwnedFd and perform into_raw, and we could subsequently to a OwnedFd::from_raw_fd() during Drop. But that's complicated, and it wouldn't be generic for any AsFd argument.

@MaxVerevkin
Copy link

If PollFd::new takes fd by value, then it immediately drops it, causing the file descriptor to be closed.

Good observation. Yeah, by-value only make sense for BorrowedFd...

If we want PollFd to actually own it, then we should probably take an OwnedFd.

Please no :)


By the way, don't know if it deserves its own issue. How do you reuse the same fd buffer with this new API? If I have a Vec<PollFd>, it captures the lifetime and prevents me from using IO resources even if I .clear() the buffer. Storing this buffer in a struct along with these IO resources is impossible (self-referencing). This forces me to create a new Vec each time in most of my projects, which forced me to "downgrade" to libc::poll.

@asomers
Copy link
Member Author

asomers commented Sep 29, 2023

Ok, I just pushed changes that restore the reference, and also rewords @SteveLauC 's explanatory comment a bit.

@MaxVerevkin
Copy link

MaxVerevkin commented Sep 29, 2023

This doesn't work either:

let mut fds = Vec::new();

{
    let file = std::fs::File::open("/etc/hostname").unwrap();
    fds.push(PollFd::<'static>::new(&file, PollFlags::POLLIN));
    drop(file);
}

poll(&mut fds, -1).unwrap();

This compiles because File: 'static is satisfied. But previous approach (&'fd Fd) doesn't work with borrowed fds.

@asomers
Copy link
Member Author

asomers commented Sep 29, 2023

This doesn't work either:

let mut fds = Vec::new();

{
    let file = std::fs::File::open("/etc/hostname").unwrap();
    fds.push(PollFd::<'static>::new(&file, PollFlags::POLLIN));
    drop(file);
}

poll(&mut fds, -1).unwrap();

This compiles because File: 'static is satisfied. But previous approach (&'fd Fd) doesn't work with borrowed fds.

Ouch. That's a big problem. Maybe we should change Poll::new to take a BorrowedFd by value, just to be sure that the lifetime is specified?

@MaxVerevkin
Copy link

Maybe we should change Poll::new to take a BorrowedFd by value

That's definitely a solution. I wanted to propose it but I wasn't sure if it's a good one.

@SteveLauC
Copy link
Member

Maybe we should change Poll::new to take a BorrowedFd by value

This approach LGTM

&AsFd didn't work because there are 'static types, like std::fs::File,
which implement AsFd.  The created BorrowedFd type within the
PollFd::new method would have a very brief lifetime, but the PhantomData
would capture the lifetime of the std::fs::File.  Taking BorrowFd<'fd>
argument makes the lifetime explicit.
src/poll.rs Outdated Show resolved Hide resolved
@SteveLauC SteveLauC self-assigned this Oct 1, 2023
@SteveLauC SteveLauC added this pull request to the merge queue Oct 1, 2023
Merged via the queue into master with commit ea88824 Oct 1, 2023
35 checks passed
@SteveLauC SteveLauC deleted the pollfd-lifetime branch October 1, 2023 11:52
@SteveLauC SteveLauC mentioned this pull request Nov 12, 2023
29 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PollFd::new requires too restrictive lifetime
3 participants