In order to implement a timeout for a thread, I created wrappers for io::Read
and io::Write
which will set the correct timeout prior to any read or write operation such that my handler can abort the thread if it hangs during an I/O operation. (I assume there is no other way using Rust's standard library to limit the runtime of a thread and terminating it cleanly on timeout, right?)
#![feature(trait_alias)]
use std::io;
use std::time::{Duration, Instant};
pub trait TimeoutSetter = FnMut(Duration) -> io::Result<()>;
pub struct DoomedReader<R: io::Read, S: TimeoutSetter> {
reader: R,
set_timeout: S,
end_of_life: Instant,
}
impl<R: io::Read, S: TimeoutSetter> DoomedReader<R, S> {
pub fn new(reader: R, set_timeout: S, duration: Duration) -> Self {
DoomedReader {
reader,
set_timeout,
end_of_life: Instant::now() + duration,
}
}
}
impl<R: io::Read, S: TimeoutSetter> io::Read for DoomedReader<R, S> { /* … */ }
I wanted this to work with any sort of stream (i.e. not just TcpStream
but also UnixStream
or any other, which only have the Read
and Write
traits in common). This is why I pass a TimeoutSetter
closure that can call the corresponding function, e.g. TcpStream::set_read_timeout
.
I implemented this for both the reading and the writing direction (as separate traits).
Now my problem is that a TcpStream
is a single object that serves both for reading and writing. The usual way to duplicate this stream is to borrow it because Read
is implemented also by &TcpStream
(in addition to TcpStream
).
let conn = TcpStream::connect("localhost:1234")?;
let (reader, writer) = (&conn, &conn);
let doomed_reader = DoomedReader::new(
reader,
|duration| reader.set_read_timeout(Some(duration)),
Duration::new(300, 0), // do not allow reading after 300 seconds
);
let doomed_writer = DoomedWriter::new(
writer,
|duration| writer.set_write_timeout(Some(duration)),
Duration::new(300, 0), // do not allow writing after 300 seconds
);
So far, so good. But now I want to store the reader and writer in a struct. But I cannot, because I had to borrow conn
and that will go out of scope once my function returns.
There is TcpStream::try_clone
, but it can fail and seems to operate on the OS level?. It doesn't seem like the right solution to me.
So here comes my question: What's the best way of splitting a connection into a reader and a writer, and can I store these in a struct without introducing a lifetime dependency on the original stream?