#![allow(clippy::pedantic)]
use std::time::Duration;
use tenshift_core::error::{Error, Result};
use tenshift_core::pipeline::ErrorPolicy;
use tenshift_core::sample::{Sample, Tensor};
use tenshift_core::source::{Source, SourceIterator};
use tenshift_core::Pipeline;
struct PosixInterruptSource {
target_samples: usize,
eagain_probability: u8,
eintr_probability: u8,
rng_state: u64,
}
impl Source for PosixInterruptSource {
fn open(&self) -> Result<Box<dyn SourceIterator>> {
Ok(Box::new(PosixInterruptIterator {
yielded_count: 0,
target_samples: self.target_samples,
eagain_prob: self.eagain_probability,
eintr_prob: self.eintr_probability,
state: self.rng_state,
}))
}
fn name(&self) -> &str {
"posix-interrupt-sim"
}
}
struct PosixInterruptIterator {
yielded_count: usize,
target_samples: usize,
eagain_prob: u8,
eintr_prob: u8,
state: u64,
}
impl PosixInterruptIterator {
fn next_rand(&mut self) -> u8 {
self.state ^= self.state << 13;
self.state ^= self.state >> 17;
self.state ^= self.state << 5;
(self.state % 100) as u8
}
}
impl SourceIterator for PosixInterruptIterator {
fn next_sample(&mut self) -> Option<Result<Sample>> {
if self.yielded_count >= self.target_samples {
return None;
}
let roll = self.next_rand();
if roll < self.eagain_prob {
std::thread::sleep(Duration::from_micros(10));
}
if roll >= self.eagain_prob && roll < self.eagain_prob + self.eintr_prob {
std::thread::sleep(Duration::from_micros(5));
self.yielded_count += 1;
return Some(Err(Error::TransformFailed {
index: 0,
reason: "System call interrupted".into(),
}));
}
let mut s = Sample::new();
s.insert("data", Tensor::i64(&[self.yielded_count as i64], vec![1]));
self.yielded_count += 1;
Some(Ok(s))
}
}
#[test]
fn test_sqlite_eagain_and_eintr_starvation_resistance() {
let pipeline = Pipeline::from_source(PosixInterruptSource {
target_samples: 50_000,
eagain_probability: 30, eintr_probability: 40, rng_state: 0xDEAD_BEEF,
})
.workers(16)
.chunk_size(4)
.prefetch(128)
.on_error(ErrorPolicy::Skip)
.sequence_gap_timeout(Duration::from_secs(5))
.source_timeout(Duration::from_secs(5))
.batch(100);
let mut successes = 0;
let iter = pipeline.start().expect("init");
for batch in iter {
let n = batch[0].get("data").unwrap().shape()[0];
successes += n;
}
assert!(
successes > 28_000 && successes < 32_000,
"Pipeline corrupted data recovery rate under EINTR fragmentation"
);
}