Skip to content

Commit 8852303

Browse files
feat: Safer poll timeout
1 parent c5d6732 commit 8852303

File tree

3 files changed

+189
-10
lines changed

3 files changed

+189
-10
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
2323
- With I/O-safe type applied in `pty::OpenptyResult` and `pty::ForkptyResult`,
2424
users no longer need to manually close the file descriptors in these types.
2525
([#1921](https://github.com./nix-rust/nix/pull/1921))
26+
- The `timeout` argument of `poll::poll` is now of type `poll::PollTimeout`.
27+
([#1876](https://github.com./nix-rust/nix/pull/1876))
2628

2729
### Fixed
2830
- Fix `SockaddrIn6` bug that was swapping flowinfo and scope_id byte ordering.

src/poll.rs

+184-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! Wait for events to trigger on specific file descriptors
22
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
3+
use std::time::Duration;
34

45
use crate::errno::Errno;
56
use crate::Result;
6-
77
/// This is a wrapper around `libc::pollfd`.
88
///
99
/// It's meant to be used as an argument to the [`poll`](fn.poll.html) and
@@ -168,6 +168,180 @@ libc_bitflags! {
168168
}
169169
}
170170

171+
/// Timeout argument for [`poll`].
172+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
173+
pub struct PollTimeout(i32);
174+
175+
impl PollTimeout {
176+
/// Blocks indefinitely.
177+
///
178+
/// > Specifying a negative value in timeout means an infinite timeout.
179+
pub const NONE: Self = Self(-1);
180+
/// Returns immediately.
181+
///
182+
/// > Specifying a timeout of zero causes poll() to return immediately, even if no file
183+
/// > descriptors are ready.
184+
pub const ZERO: Self = Self(0);
185+
/// Blocks for at most [`std::i32::MAX`] milliseconds.
186+
pub const MAX: Self = Self(i32::MAX);
187+
/// Returns if `self` equals [`PollTimeout::NONE`].
188+
pub fn is_none(&self) -> bool {
189+
// > Specifying a negative value in timeout means an infinite timeout.
190+
*self <= Self::NONE
191+
}
192+
/// Returns if `self` does not equal [`PollTimeout::NONE`].
193+
pub fn is_some(&self) -> bool {
194+
!self.is_none()
195+
}
196+
/// Returns the timeout in milliseconds if there is some, otherwise returns `None`.
197+
pub fn timeout(&self) -> Option<i32> {
198+
self.is_some().then_some(self.0)
199+
}
200+
}
201+
202+
impl<T: Into<PollTimeout>> From<Option<T>> for PollTimeout {
203+
fn from(x: Option<T>) -> Self {
204+
x.map_or(Self::NONE, |x| x.into())
205+
}
206+
}
207+
impl TryFrom<Duration> for PollTimeout {
208+
type Error = <i32 as TryFrom<u128>>::Error;
209+
fn try_from(x: Duration) -> std::result::Result<Self, Self::Error> {
210+
Ok(Self(i32::try_from(x.as_millis())?))
211+
}
212+
}
213+
impl TryFrom<u128> for PollTimeout {
214+
type Error = <i32 as TryFrom<u128>>::Error;
215+
fn try_from(x: u128) -> std::result::Result<Self, Self::Error> {
216+
Ok(Self(i32::try_from(x)?))
217+
}
218+
}
219+
impl TryFrom<u64> for PollTimeout {
220+
type Error = <i32 as TryFrom<u64>>::Error;
221+
fn try_from(x: u64) -> std::result::Result<Self, Self::Error> {
222+
Ok(Self(i32::try_from(x)?))
223+
}
224+
}
225+
impl TryFrom<u32> for PollTimeout {
226+
type Error = <i32 as TryFrom<u32>>::Error;
227+
fn try_from(x: u32) -> std::result::Result<Self, Self::Error> {
228+
Ok(Self(i32::try_from(x)?))
229+
}
230+
}
231+
impl From<u16> for PollTimeout {
232+
fn from(x: u16) -> Self {
233+
Self(i32::from(x))
234+
}
235+
}
236+
impl From<u8> for PollTimeout {
237+
fn from(x: u8) -> Self {
238+
Self(i32::from(x))
239+
}
240+
}
241+
impl TryFrom<i128> for PollTimeout {
242+
type Error = <i32 as TryFrom<i128>>::Error;
243+
fn try_from(x: i128) -> std::result::Result<Self, Self::Error> {
244+
match x {
245+
// > Specifying a negative value in timeout means an infinite timeout.
246+
i128::MIN..=-1 => Ok(Self::NONE),
247+
millis @ 0.. => Ok(Self(i32::try_from(millis)?)),
248+
}
249+
}
250+
}
251+
impl TryFrom<i64> for PollTimeout {
252+
type Error = <i32 as TryFrom<i64>>::Error;
253+
fn try_from(x: i64) -> std::result::Result<Self, Self::Error> {
254+
match x {
255+
i64::MIN..=-1 => Ok(Self::NONE),
256+
millis @ 0.. => Ok(Self(i32::try_from(millis)?)),
257+
}
258+
}
259+
}
260+
impl From<i32> for PollTimeout {
261+
fn from(x: i32) -> Self {
262+
Self(x)
263+
}
264+
}
265+
impl From<i16> for PollTimeout {
266+
fn from(x: i16) -> Self {
267+
Self(i32::from(x))
268+
}
269+
}
270+
impl From<i8> for PollTimeout {
271+
fn from(x: i8) -> Self {
272+
Self(i32::from(x))
273+
}
274+
}
275+
impl TryFrom<PollTimeout> for Duration {
276+
type Error = ();
277+
fn try_from(x: PollTimeout) -> std::result::Result<Self, ()> {
278+
match x.timeout() {
279+
// SAFETY: When `x.timeout()` returns `Some(a)`, `a` is always non-negative.
280+
Some(millis) => Ok(Duration::from_millis(unsafe {
281+
u64::try_from(millis).unwrap_unchecked()
282+
})),
283+
None => Err(()),
284+
}
285+
}
286+
}
287+
impl TryFrom<PollTimeout> for u128 {
288+
type Error = <Self as TryFrom<i32>>::Error;
289+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
290+
Self::try_from(x.0)
291+
}
292+
}
293+
impl TryFrom<PollTimeout> for u64 {
294+
type Error = <Self as TryFrom<i32>>::Error;
295+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
296+
Self::try_from(x.0)
297+
}
298+
}
299+
impl TryFrom<PollTimeout> for u32 {
300+
type Error = <Self as TryFrom<i32>>::Error;
301+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
302+
Self::try_from(x.0)
303+
}
304+
}
305+
impl TryFrom<PollTimeout> for u16 {
306+
type Error = <Self as TryFrom<i32>>::Error;
307+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
308+
Self::try_from(x.0)
309+
}
310+
}
311+
impl TryFrom<PollTimeout> for u8 {
312+
type Error = <Self as TryFrom<i32>>::Error;
313+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
314+
Self::try_from(x.0)
315+
}
316+
}
317+
impl From<PollTimeout> for i128 {
318+
fn from(x: PollTimeout) -> Self {
319+
Self::from(x.0)
320+
}
321+
}
322+
impl From<PollTimeout> for i64 {
323+
fn from(x: PollTimeout) -> Self {
324+
Self::from(x.0)
325+
}
326+
}
327+
impl From<PollTimeout> for i32 {
328+
fn from(x: PollTimeout) -> Self {
329+
x.0
330+
}
331+
}
332+
impl TryFrom<PollTimeout> for i16 {
333+
type Error = <Self as TryFrom<i32>>::Error;
334+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
335+
Self::try_from(x.0)
336+
}
337+
}
338+
impl TryFrom<PollTimeout> for i8 {
339+
type Error = <Self as TryFrom<i32>>::Error;
340+
fn try_from(x: PollTimeout) -> std::result::Result<Self, Self::Error> {
341+
Self::try_from(x.0)
342+
}
343+
}
344+
171345
/// `poll` waits for one of a set of file descriptors to become ready to perform I/O.
172346
/// ([`poll(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html))
173347
///
@@ -184,16 +358,19 @@ libc_bitflags! {
184358
///
185359
/// Note that the timeout interval will be rounded up to the system clock
186360
/// granularity, and kernel scheduling delays mean that the blocking
187-
/// interval may overrun by a small amount. Specifying a negative value
188-
/// in timeout means an infinite timeout. Specifying a timeout of zero
189-
/// causes `poll()` to return immediately, even if no file descriptors are
190-
/// ready.
191-
pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result<libc::c_int> {
361+
/// interval may overrun by a small amount. Specifying a [`PollTimeout::NONE`]
362+
/// in timeout means an infinite timeout. Specifying a timeout of
363+
/// [`PollTimeout::ZERO`] causes `poll()` to return immediately, even if no file
364+
/// descriptors are ready.
365+
pub fn poll<T: Into<PollTimeout>>(
366+
fds: &mut [PollFd],
367+
timeout: T,
368+
) -> Result<libc::c_int> {
192369
let res = unsafe {
193370
libc::poll(
194371
fds.as_mut_ptr() as *mut libc::pollfd,
195372
fds.len() as libc::nfds_t,
196-
timeout,
373+
i32::from(timeout.into()),
197374
)
198375
};
199376

test/test_poll.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use nix::{
22
errno::Errno,
3-
poll::{poll, PollFd, PollFlags},
3+
poll::{poll, PollFd, PollFlags, PollTimeout},
44
unistd::{close, pipe, write},
55
};
66
use std::os::unix::io::{BorrowedFd, FromRawFd, OwnedFd};
@@ -24,14 +24,14 @@ fn test_poll() {
2424
let mut fds = [PollFd::new(&r, PollFlags::POLLIN)];
2525

2626
// Poll an idle pipe. Should timeout
27-
let nfds = loop_while_eintr!(poll(&mut fds, 100));
27+
let nfds = loop_while_eintr!(poll(&mut fds, PollTimeout::from(100u8)));
2828
assert_eq!(nfds, 0);
2929
assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN));
3030

3131
write(w, b".").unwrap();
3232

3333
// Poll a readable pipe. Should return an event.
34-
let nfds = poll(&mut fds, 100).unwrap();
34+
let nfds = poll(&mut fds, PollTimeout::from(100u8)).unwrap();
3535
assert_eq!(nfds, 1);
3636
assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN));
3737
close(w).unwrap();

0 commit comments

Comments
 (0)