use std::io;
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::time::Duration;
use crate::error::OsErrorClass;
use crate::sys::{self, AcceptFailure, AsSock};
const BACKOFF_START: Duration = Duration::from_millis(1);
const BACKOFF_MAX: Duration = Duration::from_secs(1);
#[derive(Debug)]
pub(crate) enum Accepted {
Conn(TcpStream, SocketAddr),
Again,
Skipped(OsErrorClass),
Pause(Duration),
Failed(io::Error),
}
#[derive(Debug)]
pub(crate) struct Acceptor {
#[cfg(unix)]
reserve: Option<std::fs::File>,
backoff: Duration,
}
impl Acceptor {
pub(crate) fn new() -> Acceptor {
Acceptor {
#[cfg(unix)]
reserve: open_reserve(),
backoff: BACKOFF_START,
}
}
fn next_backoff(&mut self) -> Duration {
let current = self.backoff;
self.backoff = (self.backoff * 2).min(BACKOFF_MAX);
current
}
pub(crate) fn accept(&mut self, listener: &TcpListener) -> Accepted {
match sys::accept(listener) {
Ok((stream, peer)) => {
self.backoff = BACKOFF_START;
if !usable_peer(peer) {
let _ = sys::set_linger(stream.sock(), Some(Duration::ZERO));
return Accepted::Skipped(OsErrorClass::Other(0));
}
Accepted::Conn(stream, crate::addr::normalize_socket(peer))
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Accepted::Again,
Err(e) if e.kind() == io::ErrorKind::Interrupted => Accepted::Again,
Err(e) => match sys::classify_accept(&e) {
AcceptFailure::PerConnection => Accepted::Skipped(OsErrorClass::from_io(&e)),
AcceptFailure::ListenerFailed => Accepted::Failed(e),
AcceptFailure::Pressure => Accepted::Pause(self.next_backoff()),
AcceptFailure::Descriptors => {
self.shed_one(listener);
Accepted::Pause(self.next_backoff())
}
},
}
}
#[cfg(unix)]
fn shed_one(&mut self, listener: &TcpListener) {
self.reserve = None;
if let Ok((stream, _)) = listener.accept() {
let _ = sys::set_linger(stream.sock(), Some(Duration::ZERO));
drop(stream);
}
self.reserve = open_reserve();
}
#[cfg(windows)]
fn shed_one(&mut self, _listener: &TcpListener) {}
}
fn usable_peer(peer: SocketAddr) -> bool {
!peer.ip().is_unspecified() && peer.port() != 0
}
#[cfg(unix)]
fn open_reserve() -> Option<std::fs::File> {
#[cfg(any(test, net_io_shim))]
if sys::shim::check(sys::shim::Op::Reserve).is_err() {
return None;
}
std::fs::File::open("/dev/null").ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backoff_doubles_to_one_second() {
let mut a = Acceptor::new();
let seq: Vec<_> = (0..13).map(|_| a.next_backoff()).collect();
assert_eq!(seq[0], Duration::from_millis(1));
assert_eq!(seq[1], Duration::from_millis(2));
assert_eq!(*seq.last().unwrap(), BACKOFF_MAX);
}
#[test]
fn test_accept_again_when_nothing_pending() {
let l = sys::tcp_listener(
"127.0.0.1:0".parse().unwrap(),
&crate::opts::ListenOptions::new().params(),
)
.unwrap();
let mut a = Acceptor::new();
assert!(matches!(a.accept(&l), Accepted::Again));
let _c = TcpStream::connect(l.local_addr().unwrap()).unwrap();
let mut got = false;
for _ in 0..200 {
if let Accepted::Conn(..) = a.accept(&l) {
got = true;
break;
}
std::thread::sleep(Duration::from_millis(5));
}
assert!(got);
}
#[test]
fn test_unspecified_or_portless_peer_is_not_usable() {
assert!(usable_peer("10.0.0.1:5000".parse().unwrap()));
assert!(usable_peer("[2001:db8::1]:5000".parse().unwrap()));
assert!(!usable_peer("0.0.0.0:5000".parse().unwrap()));
assert!(!usable_peer("[::]:5000".parse().unwrap()));
assert!(!usable_peer("10.0.0.1:0".parse().unwrap()));
}
#[cfg(unix)]
#[test]
fn test_emfile_sheds_and_retries_the_reserve() {
use crate::sys::shim::{Fault, Op, clear_here, program_here};
let l = sys::tcp_listener(
"127.0.0.1:0".parse().unwrap(),
&crate::opts::ListenOptions::new().params(),
)
.unwrap();
let mut a = Acceptor::new();
assert!(a.reserve.is_some());
let mut c = TcpStream::connect(l.local_addr().unwrap()).unwrap();
c.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
std::thread::sleep(Duration::from_millis(50));
program_here(Op::Accept, 0, 2, Fault::Os(sys::errno::EMFILE));
program_here(Op::Reserve, 0, 1, Fault::Os(sys::errno::EMFILE));
assert!(matches!(a.accept(&l), Accepted::Pause(d) if d == BACKOFF_START));
assert!(a.reserve.is_none(), "the reopen failed");
let mut b = [0u8; 1];
match std::io::Read::read(&mut c, &mut b) {
Ok(0) => {}
Err(e)
if !matches!(
e.kind(),
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
) => {}
other => panic!("the shed connection was not reset: {other:?}"),
}
assert!(matches!(a.accept(&l), Accepted::Pause(d) if d == BACKOFF_START * 2));
assert!(a.reserve.is_some(), "retried at the next EMFILE");
clear_here();
}
}