use std::collections::HashMap;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
pub struct DelayedServer {
pub base: String,
requests: Arc<AtomicUsize>,
bytes_served: Arc<AtomicUsize>,
peak_in_flight: Arc<AtomicUsize>,
}
impl DelayedServer {
pub fn start(routes: &[(&str, &str)], delay: Duration) -> Self {
let routes: HashMap<String, String> = routes
.iter()
.map(|(path, body)| (path.to_string(), body.to_string()))
.collect();
Self::start_with(move |path| routes.get(path).cloned(), delay)
}
pub fn start_with(
respond: impl Fn(&str) -> Option<String> + Send + Sync + 'static,
delay: Duration,
) -> Self {
Self::start_bytes(move |path| respond(path).map(String::into_bytes), delay)
}
pub fn start_bytes(
respond: impl Fn(&str) -> Option<Vec<u8>> + Send + Sync + 'static,
delay: Duration,
) -> Self {
let routes = Arc::new(respond);
let in_flight = Arc::new(AtomicUsize::new(0));
let peak_in_flight = Arc::new(AtomicUsize::new(0));
let peak = peak_in_flight.clone();
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let base = format!("http://{}", listener.local_addr().unwrap());
let requests = Arc::new(AtomicUsize::new(0));
let counter = requests.clone();
let bytes_served = Arc::new(AtomicUsize::new(0));
let served = bytes_served.clone();
std::thread::spawn(move || {
for stream in listener.incoming() {
let Ok(mut stream) = stream else {
continue;
};
let routes = routes.clone();
let counter = counter.clone();
let served = served.clone();
let in_flight = in_flight.clone();
let peak = peak.clone();
std::thread::spawn(move || {
let mut reader = BufReader::new(stream.try_clone().unwrap());
let mut request_line = String::new();
if reader.read_line(&mut request_line).is_err() {
return;
}
let path = request_line
.split_whitespace()
.nth(1)
.unwrap_or("/")
.to_string();
loop {
let mut line = String::new();
match reader.read_line(&mut line) {
Ok(0) | Err(_) => break,
Ok(_) if line == "\r\n" => break,
Ok(_) => {}
}
}
counter.fetch_add(1, Ordering::SeqCst);
let now = in_flight.fetch_add(1, Ordering::SeqCst) + 1;
peak.fetch_max(now, Ordering::SeqCst);
std::thread::sleep(delay);
in_flight.fetch_sub(1, Ordering::SeqCst);
let response = match routes(&path) {
Some(body) => {
let mut response = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
)
.into_bytes();
response.extend_from_slice(&body);
response
}
None => b"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
.to_vec(),
};
for chunk in response.chunks(16 * 1024) {
if stream.write_all(chunk).is_err() {
break;
}
served.fetch_add(chunk.len(), Ordering::SeqCst);
}
let _ = stream.flush();
});
}
});
Self {
base,
requests,
bytes_served,
peak_in_flight,
}
}
#[allow(dead_code)]
pub fn peak_in_flight(&self) -> usize {
self.peak_in_flight.load(Ordering::SeqCst)
}
#[allow(dead_code)]
pub fn bytes_served(&self) -> usize {
self.bytes_served.load(Ordering::SeqCst)
}
#[allow(dead_code)]
pub fn requests(&self) -> usize {
self.requests.load(Ordering::SeqCst)
}
}
pub const DELAY: Duration = Duration::from_millis(200);
pub fn fan_out_routes(count: usize) -> (Vec<(String, String)>, i64) {
let mut main = String::new();
let mut sum = Vec::new();
let mut routes = Vec::new();
for index in 0..count {
main.push_str(&format!("import \"Dep{index}.pkl\"\n"));
sum.push(format!("Dep{index}.value"));
routes.push((format!("/Dep{index}.pkl"), format!("value = {index}\n")));
}
main.push_str(&format!("total = {}\n", sum.join(" + ")));
routes.push(("/Main.pkl".to_string(), main));
(routes, (0..count as i64).sum())
}
pub fn borrow_routes(routes: &[(String, String)]) -> Vec<(&str, &str)> {
routes
.iter()
.map(|(path, body)| (path.as_str(), body.as_str()))
.collect()
}
pub fn write_entry(test: &str, name: &str, contents: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("pklr_{test}_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join(name);
std::fs::write(&path, contents).unwrap();
path
}
#[allow(dead_code)]
pub fn endless_chain_server() -> DelayedServer {
DelayedServer::start_with(
|path| {
let index: u64 = path.strip_prefix('/')?.strip_suffix(".pkl")?.parse().ok()?;
Some(format!(
"import \"{}.pkl\" as Next\nvalue = {index}\n",
index + 1
))
},
Duration::ZERO,
)
}