timeout_readwrite/
reader.rs

1// Copyright 2017 Jonathan Creekmore
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use nix::libc::c_int;
10use nix::poll::PollFlags;
11use std::io::Read;
12use std::io::Result;
13use std::io::Seek;
14use std::io::SeekFrom;
15use std::os::fd::AsFd;
16use std::time::Duration;
17
18use super::utils;
19
20/// The `TimeoutReader` struct adds read timeouts to any reader.
21///
22/// The `read` call on a `Read` instance will block forever until data is available.
23/// A `TimeoutReader` will wait until data is available, up until an optional timeout,
24/// before actually performing the `read` operation.
25///
26/// If any `Read` operation times out, the method called will return
27/// an `io::ErrorKind::TimedOut` variant as the value of `io::Error`. All other
28/// error values that would normally be produced by the underlying implementation
29/// of the `Read` trait could also be produced by the `TimeoutReader`.
30pub struct TimeoutReader<H>
31where
32    H: Read + AsFd,
33{
34    timeout: Option<c_int>,
35    handle: H,
36}
37
38impl<H> Read for TimeoutReader<H>
39where
40    H: Read + AsFd,
41{
42    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
43        utils::wait_until_ready(self.timeout, &self.handle, PollFlags::POLLIN)?;
44        self.handle.read(buf)
45    }
46}
47
48impl<H> Seek for TimeoutReader<H>
49where
50    H: Read + AsFd + Seek,
51{
52    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
53        self.handle.seek(pos)
54    }
55}
56
57impl<H> AsFd for TimeoutReader<H>
58where
59    H: Read + AsFd,
60{
61    fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> {
62        self.handle.as_fd()
63    }
64}
65
66impl<H> Clone for TimeoutReader<H>
67where
68    H: Read + AsFd + Clone,
69{
70    fn clone(&self) -> TimeoutReader<H> {
71        TimeoutReader {
72            handle: self.handle.clone(),
73            ..*self
74        }
75    }
76}
77
78impl<H> TimeoutReader<H>
79where
80    H: Read + AsFd,
81{
82    /// Create a new `TimeoutReader` with an optional timeout.
83    ///
84    /// # Examples
85    ///
86    /// This first example creates the `TimeoutReader` with a 5-second timeout.
87    ///
88    /// ```
89    /// use timeout_readwrite::TimeoutReader;
90    /// use std::fs::File;
91    /// use std::time::Duration;
92    ///
93    /// # fn foo() -> std::io::Result<()> {
94    /// let mut f = File::open("file.txt")?;
95    /// let mut rdr = TimeoutReader::new(f, Duration::new(5, 0));
96    /// # Ok(())
97    /// # }
98    /// ```
99    ///
100    /// This example creates the `TimeoutReader` without a timeout at all.
101    ///
102    /// ```
103    /// use timeout_readwrite::TimeoutReader;
104    /// use std::fs::File;
105    /// use std::time::Duration;
106    ///
107    /// # fn foo() -> std::io::Result<()> {
108    /// let mut f = File::open("file.txt")?;
109    /// let mut rdr = TimeoutReader::new(f, None);
110    /// # Ok(())
111    /// # }
112    /// ```
113    pub fn new<T: Into<Option<Duration>>>(handle: H, timeout: T) -> TimeoutReader<H> {
114        TimeoutReader {
115            timeout: timeout.into().map(utils::duration_to_ms),
116            handle,
117        }
118    }
119}
120
121pub trait TimeoutReadExt<H>
122where
123    H: Read + AsFd,
124{
125    fn with_timeout<T: Into<Option<Duration>>>(self, timeout: T) -> TimeoutReader<H>;
126}
127
128impl<H> TimeoutReadExt<H> for H
129where
130    H: Read + AsFd,
131{
132    fn with_timeout<T: Into<Option<Duration>>>(self, timeout: T) -> TimeoutReader<H> {
133        TimeoutReader::new(self, timeout)
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use std::env;
140    use std::fs::File;
141    use std::io::Read;
142    use std::os::fd::AsRawFd;
143    use std::path::PathBuf;
144    use std::time::Duration;
145
146    use super::*;
147
148    lazy_static! {
149        static ref CRATE_ROOT: PathBuf = {
150            env::current_exe()
151                .unwrap()
152                .parent()
153                .unwrap()
154                .parent()
155                .unwrap()
156                .parent()
157                .unwrap()
158                .parent()
159                .unwrap()
160                .to_path_buf()
161        };
162    }
163
164    #[test]
165    fn read_regular_file_with_timeout() {
166        let original_contents = include_str!("../test_data/regular_file.txt");
167
168        let mut regular_file = CRATE_ROOT.clone();
169        regular_file.push("test_data");
170        regular_file.push("regular_file.txt");
171
172        let fp = File::open(regular_file).unwrap();
173        let fp_fd = fp.as_raw_fd();
174        let mut fp = TimeoutReader::new(fp, Duration::new(5, 0));
175
176        let mut read_contents = String::new();
177        fp.read_to_string(&mut read_contents).unwrap();
178
179        assert_eq!(original_contents, read_contents);
180
181        assert_eq!(fp_fd, fp.as_fd().as_raw_fd());
182    }
183
184    #[test]
185    fn read_regular_file_no_timeout() {
186        let original_contents = include_str!("../test_data/regular_file.txt");
187
188        let mut regular_file = CRATE_ROOT.clone();
189        regular_file.push("test_data");
190        regular_file.push("regular_file.txt");
191
192        let fp = File::open(regular_file).unwrap();
193        let mut fp = TimeoutReader::new(fp, None);
194
195        let mut read_contents = String::new();
196        fp.read_to_string(&mut read_contents).unwrap();
197
198        assert_eq!(original_contents, read_contents);
199    }
200
201    #[test]
202    fn read_regular_file_with_timeout_extension_trait() {
203        let original_contents = include_str!("../test_data/regular_file.txt");
204
205        let mut regular_file = CRATE_ROOT.clone();
206        regular_file.push("test_data");
207        regular_file.push("regular_file.txt");
208
209        let fp = File::open(regular_file).unwrap();
210        let mut fp = fp.with_timeout(Duration::new(5, 0));
211
212        let mut read_contents = String::new();
213        fp.read_to_string(&mut read_contents).unwrap();
214
215        assert_eq!(original_contents, read_contents);
216    }
217}