use std::io;
use std::sync::Arc;
use crate::Timeout;
use super::time::{Duration, Instant};
use super::{NextTimeout, Transport};
pub struct TransportAdapter<T: Transport = Box<dyn Transport>> {
timeout: NextTimeout,
timeout_set_at: Instant,
current_time: Arc<dyn Fn() -> Instant + Send + Sync + 'static>,
transport: T,
}
impl<T: Transport> TransportAdapter<T> {
pub fn new(
transport: T,
current_time: Arc<dyn Fn() -> Instant + Send + Sync + 'static>,
) -> Self {
Self {
timeout: NextTimeout {
after: Duration::NotHappening,
reason: Timeout::Global,
..NextTimeout::default()
},
timeout_set_at: current_time(),
current_time,
transport,
}
}
pub fn set_timeout(&mut self, timeout: NextTimeout) {
self.set_timeout_at(timeout, (self.current_time)());
}
pub(crate) fn set_timeout_at(&mut self, timeout: NextTimeout, started: Instant) {
self.timeout = timeout;
self.timeout_set_at = started;
}
fn timeout_now(&self) -> NextTimeout {
let mut timeout = self.timeout;
timeout.after = remaining_after(timeout.after, self.timeout_set_at, (self.current_time)());
timeout
}
pub fn get_ref(&self) -> &dyn Transport {
&self.transport
}
pub fn get_mut(&mut self) -> &mut dyn Transport {
&mut self.transport
}
pub fn inner(&self) -> &dyn Transport {
&self.transport
}
pub fn into_inner(self) -> T {
self.transport
}
}
fn remaining_after(after: Duration, started: Instant, now: Instant) -> Duration {
if after.is_not_happening() {
return after;
}
match (after, now.duration_since(started)) {
(Duration::Exact(limit), Duration::Exact(elapsed)) => {
Duration::Exact(limit.saturating_sub(elapsed))
}
(Duration::Exact(_), Duration::NotHappening) => Duration::from_secs(0),
(Duration::NotHappening, _) => Duration::NotHappening,
}
}
impl<T: Transport> io::Read for TransportAdapter<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.transport
.maybe_await_input(self.timeout_now())
.map_err(|e| e.into_io())?;
let input = self.transport.buffers().input();
let max = buf.len().min(input.len());
buf[..max].copy_from_slice(&input[..max]);
self.transport.buffers().input_consume(max);
Ok(max)
}
}
impl<T: Transport> io::Write for TransportAdapter<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let output = self.transport.buffers().output();
let max = buf.len().min(output.len());
output[..max].copy_from_slice(&buf[..max]);
self.transport
.transmit_output(max, self.timeout_now())
.map_err(|e| e.into_io())?;
Ok(max)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Error;
use crate::transport::Buffers;
use crate::unversioned::transport::LazyBuffers;
use std::sync::Mutex;
#[derive(Debug)]
struct RecordingTransport {
buffers: LazyBuffers,
seen: Vec<Duration>,
}
impl Transport for RecordingTransport {
fn buffers(&mut self) -> &mut dyn Buffers {
&mut self.buffers
}
fn transmit_output(&mut self, _: usize, timeout: NextTimeout) -> Result<(), Error> {
let timeout = timeout.check()?;
self.seen.push(timeout.after);
Ok(())
}
fn await_input(&mut self, timeout: NextTimeout) -> Result<bool, Error> {
let timeout = timeout.check()?;
self.seen.push(timeout.after);
let buf = self.buffers.input_append_buf();
buf[0] = 1;
self.buffers.input_appended(1);
Ok(true)
}
fn is_open(&mut self) -> bool {
true
}
}
#[test]
fn adapter_io_uses_remaining_timeout_budget() {
use std::io::{Read, Write};
let clock = Arc::new(Mutex::new(Instant::now()));
let current_time = {
let clock = Arc::clone(&clock);
Arc::new(move || *clock.lock().unwrap())
};
let mut adapter = TransportAdapter::new(
RecordingTransport {
buffers: LazyBuffers::new(16, 16),
seen: Vec::new(),
},
current_time,
);
adapter.set_timeout(NextTimeout {
after: Duration::from_secs(2),
reason: Timeout::Connect,
..NextTimeout::default()
});
adapter.write_all(&[1]).unwrap();
{
let mut now = *clock.lock().unwrap();
now = now + Duration::from_millis(750);
*clock.lock().unwrap() = now;
}
adapter.read_exact(&mut [0]).unwrap();
{
let mut now = *clock.lock().unwrap();
now = now + Duration::from_millis(750);
*clock.lock().unwrap() = now;
}
adapter.write_all(&[1]).unwrap();
let seen = adapter.into_inner().seen;
assert_eq!(seen[0], Duration::from_secs(2));
assert_eq!(seen[1], Duration::from_millis(1250));
assert_eq!(seen[2], Duration::from_millis(500));
}
#[test]
fn adapter_io_times_out_when_the_budget_is_spent() {
use std::io::Read;
let start = Instant::now();
let clock = Arc::new(Mutex::new(start));
let current_time = {
let clock = Arc::clone(&clock);
Arc::new(move || *clock.lock().unwrap())
};
let mut adapter = TransportAdapter::new(
RecordingTransport {
buffers: LazyBuffers::new(16, 16),
seen: Vec::new(),
},
current_time,
);
adapter.set_timeout(NextTimeout {
after: Duration::from_secs(1),
reason: Timeout::Connect,
..NextTimeout::default()
});
*clock.lock().unwrap() = start + Duration::from_secs(1);
let error = adapter.read_exact(&mut [0]).unwrap_err();
let inner = error
.get_ref()
.and_then(|e| e.downcast_ref::<Error>())
.expect("ureq timeout");
assert!(matches!(inner, Error::Timeout(Timeout::Connect)));
}
}