diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index a4ab35d969..f9ada57356 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -916,6 +916,18 @@ pub fn serve(client: ControlClient) -> std::io::Result { serve_with(client, LIMITS) } +/// How long the accepting thread waits after a failed accept. Accepting can keep failing, for +/// example while the process has no file descriptors left, and retrying at once would keep a +/// core busy until it recovers. The single-instance listener waits the same way. +#[cfg(not(target_arch = "wasm32"))] +const ACCEPT_RETRY: std::time::Duration = std::time::Duration::from_millis(100); + +/// The connections `incoming` accepts, calling `wait` after each failed accept. +#[cfg(not(target_arch = "wasm32"))] +fn accepted(incoming: impl Iterator>, mut wait: impl FnMut()) -> impl Iterator { + incoming.filter_map(move |stream| stream.map_err(|_| wait()).ok()) +} + #[cfg(not(target_arch = "wasm32"))] fn serve_with(client: ControlClient, limits: Limits) -> std::io::Result { use std::io::{BufReader, Write}; @@ -926,7 +938,7 @@ fn serve_with(client: ControlClient, limits: Limits) -> std::io::Result std::io::Result { #[cfg(test)] mod transport_tests { + /// A failed accept is followed by a wait before the next one, so a listener that keeps + /// failing does not spin, and the connections around the failures still come through. + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn a_failed_accept_waits_before_the_next() { + let failed = || Err(std::io::Error::other("no file descriptors left")); + let incoming = vec![failed(), Ok(1), failed(), failed(), Ok(2)]; + let mut waits = 0; + let connections: Vec = super::accepted(incoming.into_iter(), || waits += 1).collect(); + assert_eq!(connections, [1, 2]); + assert_eq!(waits, 3, "one wait per failed accept"); + } + /// Before authenticating, a line that isn't JSON gets a parse error and closes the /// connection, as [`super::serve`] documents for any first request other than `auth`. #[cfg(not(target_arch = "wasm32"))]