#![cfg_attr(not(feature = "guard"), allow(dead_code, unused_imports))]
mod common;
use common::*;
use simoom::{Limits, Outcome, Reply, Request, Responder, Response, Server, ServerHandle};
use std::io::{Read, Write};
use std::net::TcpStream;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
type Held = Arc<Mutex<Vec<Responder>>>;
fn deferring(limits: Limits) -> (ServerHandle, Held) {
let held: Held = Arc::default();
let h2 = held.clone();
let h = Server::builder()
.listen("127.0.0.1:0")
.threads(1)
.limits(limits)
.service(move |_: &mut Request<'_>, reply: Reply<'_>| -> Outcome {
h2.lock().unwrap().push(reply.defer());
Outcome::Deferred
})
.start()
.unwrap();
(h, held)
}
fn send_get(h: &ServerHandle) -> TcpStream {
let mut c = connect(h);
c.write_all(b"GET / HTTP/1.1\r\nHost: t\r\n\r\n").unwrap();
c
}
fn status_of(c: &mut TcpStream) -> u16 {
let mut got = Vec::new();
let mut b = [0u8; 512];
while !got.windows(4).any(|w| w == b"\r\n\r\n") {
let k = c.read(&mut b).unwrap();
assert!(k > 0, "closed before a response");
got.extend_from_slice(&b[..k]);
}
std::str::from_utf8(&got[9..12]).unwrap().parse().unwrap()
}
fn answer_all(held: &Held) {
for r in held.lock().unwrap().drain(..) {
r.send(Response::new(200).body(&b"ok"[..]));
}
}
#[cfg(feature = "guard")]
#[test]
fn a_loop_with_too_many_replies_outstanding_stops_reading_new_requests() {
let (h, held) = deferring(Limits { max_pending_per_loop: 4, ..Limits::default() });
let mut first: Vec<TcpStream> = (0..4).map(|_| send_get(&h)).collect();
assert!(wait_for(5, || held.lock().unwrap().len() == 4), "{:?}", h.stats());
let mut fifth = send_get(&h);
std::thread::sleep(Duration::from_millis(300));
assert_eq!(held.lock().unwrap().len(), 4, "the fifth request was read");
answer_all(&held);
for c in first.iter_mut() {
assert_eq!(status_of(c), 200);
}
assert!(wait_for(5, || held.lock().unwrap().len() == 1), "the fifth request was never read");
answer_all(&held);
assert_eq!(status_of(&mut fifth), 200);
assert_eq!(h.stats().shed, 0, "backpressure, not shedding");
}
#[cfg(feature = "guard")]
#[test]
fn with_pending_503_new_requests_get_a_fast_503() {
let (h, held) = deferring(Limits { max_pending_per_loop: 2, pending_503: true, ..Limits::default() });
let mut first: Vec<TcpStream> = (0..2).map(|_| send_get(&h)).collect();
assert!(wait_for(5, || held.lock().unwrap().len() == 2));
let mut third = send_get(&h);
let t = Instant::now();
assert_eq!(status_of(&mut third), 503);
assert!(t.elapsed() < Duration::from_secs(1));
assert_eq!(h.stats().shed, 1);
answer_all(&held);
for c in first.iter_mut() {
assert_eq!(status_of(c), 200);
}
}
fn slow_server(limits: Limits) -> ServerHandle {
Server::builder()
.listen("127.0.0.1:0")
.threads(1)
.limits(limits)
.service(|_: &mut Request<'_>, r: Reply<'_>| -> Outcome { r.body_static(b"ok") })
.start()
.unwrap()
}
fn slow_head(h: &ServerHandle) -> TcpStream {
let mut s = connect(h);
s.write_all(b"GET / HTTP/1.1\r\nHost: t\r\nX-Slow:").unwrap();
s.set_read_timeout(Some(Duration::from_millis(50))).unwrap();
s
}
fn is_open(s: &mut TcpStream) -> bool {
let mut b = [0u8; 64];
match s.read(&mut b) {
Err(e) => matches!(e.kind(), std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut),
Ok(_) => false,
}
}
#[cfg(feature = "guard")]
#[test]
fn a_slow_head_stays_open_until_pressure_or_its_limit() {
let l = Limits { head_timeout: Duration::from_millis(200), slow_head_limit: Duration::from_millis(1500), ..Limits::default() };
let h = slow_server(l);
let mut s = slow_head(&h);
std::thread::sleep(Duration::from_millis(700));
assert!(is_open(&mut s), "closed with connections to spare");
assert_eq!(h.stats().timeouts, 0);
assert!(wait_for(3, || !is_open(&mut s)), "still open after its limit");
assert_eq!(h.stats().timeouts, 1);
}
#[test]
fn under_connection_pressure_slow_heads_close_at_their_deadline() {
let l = Limits { head_timeout: Duration::from_millis(200), max_connections: 4, ..Limits::default() };
let h = slow_server(l);
let mut conns: Vec<TcpStream> = (0..4).map(|_| slow_head(&h)).collect();
let t = Instant::now();
for s in conns.iter_mut() {
assert!(wait_for(3, || !is_open(s)));
}
assert!(t.elapsed() < Duration::from_secs(2), "{:?}", t.elapsed());
assert!(h.stats().timeouts >= 4, "{:?}", h.stats());
}
#[cfg(feature = "guard")]
#[test]
fn a_reconnect_rate_turns_away_a_client_that_comes_straight_back() {
let h = slow_server(Limits { reconnect_rate: 5, ..Limits::default() });
let mut served = 0;
for _ in 0..20 {
let mut c = connect(&h);
c.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
if c.write_all(b"GET / HTTP/1.1\r\nHost: t\r\nConnection: close\r\n\r\n").is_err() {
continue;
}
let mut b = Vec::new();
let _ = c.read_to_end(&mut b);
if b.starts_with(b"HTTP/1.1 200") {
served += 1;
}
}
assert!((5..=8).contains(&served), "served {served} of 20 at 5 per second");
assert!(h.stats().rejected >= 12, "{:?}", h.stats());
std::thread::sleep(Duration::from_millis(1100));
assert_eq!(get(&h, "/").status, 200);
}
#[test]
fn connections_accepted_while_the_loops_start_are_served_at_once() {
let h = slow_server(Limits::default());
let mut conns: Vec<TcpStream> = (0..6).map(|_| send_get(&h)).collect();
let t = Instant::now();
for c in conns.iter_mut() {
assert_eq!(status_of(c), 200);
}
assert!(t.elapsed() < Duration::from_secs(2), "{:?}", t.elapsed());
}