use std::time::Duration;
use crate::error::AsynResult;
use crate::user::AsynUser;
use super::{OctetInterpose, OctetNext, OctetReadResult};
pub struct FlushTimeoutInterpose {
pub flush_timeout: Duration,
}
impl FlushTimeoutInterpose {
pub fn new(flush_timeout: Duration) -> Self {
Self { flush_timeout }
}
}
impl Default for FlushTimeoutInterpose {
fn default() -> Self {
Self::new(Duration::from_millis(1))
}
}
impl OctetInterpose for FlushTimeoutInterpose {
fn read(
&mut self,
user: &AsynUser,
buf: &mut [u8],
next: &mut dyn OctetNext,
) -> AsynResult<OctetReadResult> {
next.read(user, buf)
}
fn write(
&mut self,
user: &mut AsynUser,
data: &[u8],
next: &mut dyn OctetNext,
) -> AsynResult<usize> {
next.write(user, data)
}
fn flush(&mut self, user: &mut AsynUser, next: &mut dyn OctetNext) -> AsynResult<()> {
let save_timeout = user.timeout;
user.timeout = Some(self.flush_timeout);
let mut buffer = [0u8; 100];
loop {
let drained = match next.read(user, &mut buffer) {
Ok(result) => result.nbytes_transferred,
Err(e) => e.partial_read().map_or(0, |p| p.nbytes_transferred()),
};
if drained == 0 {
break;
}
}
user.timeout = save_timeout;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::error::{AsynError, AsynStatus};
use crate::interpose::{EomReason, PartialOctetRead};
struct FlushableBase {
read_count: Arc<AtomicUsize>,
reads_with_data: usize,
}
impl FlushableBase {
fn new(reads_with_data: usize) -> Self {
Self {
read_count: Arc::new(AtomicUsize::new(0)),
reads_with_data,
}
}
}
impl OctetNext for FlushableBase {
fn read(&mut self, _user: &AsynUser, buf: &mut [u8]) -> AsynResult<OctetReadResult> {
let n = self.read_count.fetch_add(1, Ordering::Relaxed);
if n < self.reads_with_data {
let msg = b"stale";
let len = msg.len().min(buf.len());
buf[..len].copy_from_slice(&msg[..len]);
Ok(OctetReadResult {
nbytes_transferred: len,
eom_reason: EomReason::CNT,
})
} else {
Ok(OctetReadResult {
nbytes_transferred: 0,
eom_reason: EomReason::CNT,
})
}
}
fn write(&mut self, _user: &mut AsynUser, data: &[u8]) -> AsynResult<usize> {
Ok(data.len())
}
fn flush(&mut self, _user: &mut AsynUser) -> AsynResult<()> {
Ok(())
}
}
#[test]
fn test_flush_discards_stale_data() {
let mut interpose = FlushTimeoutInterpose::new(Duration::from_millis(10));
let mut base = FlushableBase::new(2); let mut user = AsynUser::default();
interpose.flush(&mut user, &mut base).unwrap();
assert!(base.read_count.load(Ordering::Relaxed) >= 3);
}
#[test]
fn test_read_passthrough() {
let mut interpose = FlushTimeoutInterpose::default();
let mut base = FlushableBase::new(1); let user = AsynUser::default();
let mut buf = [0u8; 32];
let result = interpose.read(&user, &mut buf, &mut base).unwrap();
assert_eq!(&buf[..result.nbytes_transferred], b"stale");
}
#[test]
fn test_write_passthrough() {
let mut interpose = FlushTimeoutInterpose::default();
let mut base = FlushableBase::new(0);
let mut user = AsynUser::default();
let n = interpose.write(&mut user, b"hello", &mut base).unwrap();
assert_eq!(n, 5);
}
#[test]
fn test_flush_restores_timeout() {
let mut interpose = FlushTimeoutInterpose::new(Duration::from_millis(10));
let mut base = FlushableBase::new(0);
let original_timeout = Duration::from_secs(5);
let mut user = AsynUser {
timeout: Some(original_timeout),
..Default::default()
};
interpose.flush(&mut user, &mut base).unwrap();
assert_eq!(user.timeout, Some(original_timeout));
}
#[derive(Clone, Copy)]
enum Step {
Ok(usize),
ErrWith(usize),
ErrEmpty,
}
struct ScriptedBase {
steps: Vec<Step>,
reads: usize,
}
impl OctetNext for ScriptedBase {
fn read(&mut self, _user: &AsynUser, buf: &mut [u8]) -> AsynResult<OctetReadResult> {
let step = self.steps.get(self.reads).copied().unwrap_or(Step::Ok(0));
self.reads += 1;
let timeout = || AsynError::Status {
status: AsynStatus::Timeout,
message: "flush read timeout".into(),
};
let deliver = |buf: &mut [u8], n: usize| {
let n = n.min(buf.len());
buf[..n].fill(b'x');
n
};
match step {
Step::Ok(n) => Ok(OctetReadResult {
nbytes_transferred: deliver(buf, n),
eom_reason: EomReason::CNT,
}),
Step::ErrWith(n) => {
let n = deliver(buf, n);
Err(timeout().with_partial_read(PartialOctetRead {
data: vec![b'x'; n],
eom_reason: EomReason::empty(),
}))
}
Step::ErrEmpty => Err(timeout()),
}
}
fn write(&mut self, _user: &mut AsynUser, data: &[u8]) -> AsynResult<usize> {
Ok(data.len())
}
fn flush(&mut self, _user: &mut AsynUser) -> AsynResult<()> {
Ok(())
}
}
fn drain_reads(script: &[Step]) -> usize {
let mut interpose = FlushTimeoutInterpose::new(Duration::from_millis(10));
let mut base = ScriptedBase {
steps: script.to_vec(),
reads: 0,
};
let mut user = AsynUser::default();
interpose
.flush(&mut user, &mut base)
.expect("flush reports success whatever the drain saw, as C's flushIt does");
base.reads
}
#[test]
fn zero_byte_ok_read_terminates_the_drain() {
assert_eq!(drain_reads(&[Step::Ok(0)]), 1);
}
#[test]
fn positive_byte_ok_read_continues_the_drain() {
assert_eq!(drain_reads(&[Step::Ok(5), Step::Ok(0)]), 2);
}
#[test]
fn failing_read_with_partial_bytes_continues_the_drain() {
assert_eq!(drain_reads(&[Step::ErrWith(5), Step::Ok(0)]), 2);
assert_eq!(
drain_reads(&[Step::ErrWith(5), Step::ErrWith(3), Step::Ok(0)]),
3,
"the count governs for as long as failing reads keep delivering bytes"
);
}
#[test]
fn failing_read_with_no_bytes_terminates_the_drain() {
assert_eq!(drain_reads(&[Step::ErrEmpty]), 1);
}
}