From 569a7033da12e3e159811ca6f9ecea41e8de490e Mon Sep 17 00:00:00 2001 From: Tom Dryer Date: Fri, 19 Dec 2025 14:43:42 -0800 Subject: [PATCH 1/2] Avoid creating unnecessary thread pool Unless the `threadpool` feature is disabled, `Server` always contains a thread pool, even if it is unnecessary because the `Server` is only used with `serve_single_thread`. Fix this by deferring creating the thread pool until it is actually required. I noticed this because I was getting resource exhaustion errors when running a single-threaded server in a podman container, which restricts the number of threads. Disabling the `threadpool` feature also fixes this, but this must be done explicitly since it's a default feature. --- src/server.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/server.rs b/src/server.rs index 582cd7c..a71440d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -120,7 +120,7 @@ where /// A listening HTTP server that accepts HTTP 1 connections. pub struct Server<'a> { #[cfg(feature = "threadpool")] - thread_pool: ThreadPool, + max_threads: usize, incoming: Box + 'a>, } @@ -165,9 +165,10 @@ impl Server<'_> { S: Service, S: Send + Clone + 'static, { + let thread_pool = ThreadPool::new(self.max_threads); for conn in self.incoming { let mut app = service.clone(); - self.thread_pool.execute(move || { + thread_pool.execute(move || { serve(conn, &mut app).ok(); }); } @@ -253,9 +254,10 @@ impl Server<'_> { M: MakeService + 'static, ::Service: Send, { + let thread_pool = ThreadPool::new(self.max_threads); for conn in self.incoming { if let Ok(mut handler) = make_service.call(&conn) { - self.thread_pool.execute(move || { + thread_pool.execute(move || { serve(conn, &mut handler).ok(); }); } @@ -407,7 +409,7 @@ impl ServerBuilder { ) -> Server<'a> { Server { #[cfg(feature = "threadpool")] - thread_pool: ThreadPool::new(self.max_threads), + max_threads: self.max_threads, incoming: Box::new(conns.into_iter().filter_map(move |conn| { let conn = conn.into(); conn.set_read_timeout(self.read_timeout).ok()?; From 4fdda2761669f678496034d3974e3f39eae98420 Mon Sep 17 00:00:00 2001 From: Tom Dryer Date: Fri, 19 Dec 2025 15:04:37 -0800 Subject: [PATCH 2/2] Fix clippy warnings about `io::Error::other` Fix clippy warnings about replacing `io::Error::new(io::ErrorKind::Other, ...)` with `io::Error::other(...)`. --- src/body.rs | 10 ++++------ src/client.rs | 3 +-- src/request.rs | 15 +++------------ src/response.rs | 5 +---- src/server.rs | 6 ++---- src/tls.rs | 10 +++++----- 6 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/body.rs b/src/body.rs index aef02b6..28ccdf0 100644 --- a/src/body.rs +++ b/src/body.rs @@ -56,7 +56,7 @@ impl BodyChannel { pub fn send>>(&self, data: T) -> io::Result<()> { self.0 .send(Ok(data.into().into())) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "body closed")) + .map_err(|_| io::Error::other("body closed")) } /// Send a trailer header. Note that trailers are buffered, and are only sent after the last @@ -82,14 +82,12 @@ impl BodyChannel { pub fn send_trailers(&self, trailers: HeaderMap) -> io::Result<()> { self.0 .send(Ok(Chunk::Trailers(trailers))) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "body closed")) + .map_err(|_| io::Error::other("body closed")) } /// Aborts the body in an abnormal fashion. pub fn abort(self) { - self.0 - .send(Err(io::Error::new(io::ErrorKind::Other, "aborted"))) - .ok(); + self.0.send(Err(io::Error::other("aborted"))).ok(); } } @@ -253,7 +251,7 @@ impl TryFrom for Body { fn try_from(file: File) -> Result { match file.metadata() { Ok(meta) if meta.is_file() => Ok(Body::from_reader(file, meta.len() as usize)), - Ok(_) => Err(io::Error::new(io::ErrorKind::Other, "not a file")), + Ok(_) => Err(io::Error::other("not a file")), Err(err) => Err(err), } } diff --git a/src/client.rs b/src/client.rs index baeb645..4a4beef 100644 --- a/src/client.rs +++ b/src/client.rs @@ -118,8 +118,7 @@ where request::write_request(req, &mut writer)?; writer.flush()?; - let res = response::parse_response(reader) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + let res = response::parse_response(reader).map_err(io::Error::other)?; let asks_for_close = res .headers() diff --git a/src/request.rs b/src/request.rs index da5aa4b..1c6a0a6 100644 --- a/src/request.rs +++ b/src/request.rs @@ -140,10 +140,7 @@ pub(crate) fn write_request( match (content_length, body.len()) { (Some(len), Some(body_len)) => { if len.0 != body_len { - return Err(io::Error::new( - io::ErrorKind::Other, - "content-length doesn't match body length", - )); + return Err(io::Error::other("content-length doesn't match body length")); } Encoding::FixedLength(len.0) } @@ -162,10 +159,7 @@ pub(crate) fn write_request( headers.typed_insert::(headers::TransferEncoding::chunked()); Encoding::Chunked } else { - return Err(io::Error::new( - io::ErrorKind::Other, - "could not determine the size of the body", - )); + return Err(io::Error::other("could not determine the size of the body")); }; let version = if version == Version::HTTP_11 { @@ -173,10 +167,7 @@ pub(crate) fn write_request( } else if version == Version::HTTP_10 { "HTTP/1.0" } else { - return Err(io::Error::new( - io::ErrorKind::Other, - "unsupported http version", - )); + return Err(io::Error::other("unsupported http version")); }; stream.write_all(format!("{method} {uri} {version}\r\n").as_bytes())?; diff --git a/src/response.rs b/src/response.rs index 19a8807..33c3cac 100644 --- a/src/response.rs +++ b/src/response.rs @@ -137,10 +137,7 @@ pub(crate) fn write_response( match (content_length, body.len()) { (Some(len), Some(body_len)) => { if len.0 != body_len { - return Err(io::Error::new( - io::ErrorKind::Other, - "content-length doesn't match body length", - )); + return Err(io::Error::other("content-length doesn't match body length")); } Encoding::FixedLength(len.0) } diff --git a/src/server.rs b/src/server.rs index a71440d..bfa0b54 100644 --- a/src/server.rs +++ b/src/server.rs @@ -507,9 +507,7 @@ fn serve(conn: Connection, app: &mut A) -> io::Result<()> { }; } - let mut res = app - .call(req) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + let mut res = app.call(req).map_err(io::Error::other)?; *res.version_mut() = version; @@ -542,7 +540,7 @@ fn serve(conn: Connection, app: &mut A) -> io::Result<()> { } } Err(ParseError::ConnectionClosed) => break, - Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)), + Err(err) => return Err(io::Error::other(err)), } } diff --git a/src/tls.rs b/src/tls.rs index 98a8b1e..2fefc0a 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -41,7 +41,7 @@ impl RustlsConnection { pub fn peer_addr(&self) -> io::Result { self.0 .lock() - .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? + .map_err(|_err| io::Error::other("Failed to aquire lock"))? .sock .peer_addr() } @@ -49,7 +49,7 @@ impl RustlsConnection { pub fn local_addr(&self) -> io::Result { self.0 .lock() - .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? + .map_err(|_err| io::Error::other("Failed to aquire lock"))? .sock .local_addr() } @@ -59,7 +59,7 @@ impl Read for RustlsConnection { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0 .lock() - .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? + .map_err(|_err| io::Error::other("Failed to aquire lock"))? .read(buf) } } @@ -68,14 +68,14 @@ impl Write for RustlsConnection { fn write(&mut self, buf: &[u8]) -> io::Result { self.0 .lock() - .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? + .map_err(|_err| io::Error::other("Failed to aquire lock"))? .write(buf) } fn flush(&mut self) -> io::Result<()> { self.0 .lock() - .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? + .map_err(|_err| io::Error::other("Failed to aquire lock"))? .flush() } }