Skip to content

Commit eb63dc7

Browse files
authored
Add find_port retry logic (#39)
* Add find_port retry logic Signed-off-by: Mateus Devino <mdevino@ibm.com> * Update ports range Signed-off-by: Mateus Devino <mdevino@ibm.com> * Add MockServerConfig::new() Signed-off-by: Mateus Devino <mdevino@ibm.com> * Apply changes requested * Make MockServerConfig fields public * Change binding port loop * Edit default values Signed-off-by: Mateus Devino <mdevino@ibm.com> --------- Signed-off-by: Mateus Devino <mdevino@ibm.com>
1 parent 296c6c7 commit eb63dc7

1 file changed

Lines changed: 61 additions & 16 deletions

File tree

mocktail/src/server.rs

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Mock server
22
use std::{
33
cell::OnceCell,
4-
net::{SocketAddr, TcpStream},
4+
net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream},
55
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
66
time::Duration,
77
};
@@ -32,6 +32,7 @@ pub struct MockServer {
3232
addr: OnceCell<SocketAddr>,
3333
base_url: OnceCell<Url>,
3434
state: Arc<MockServerState>,
35+
config: MockServerConfig,
3536
}
3637

3738
impl MockServer {
@@ -43,6 +44,7 @@ impl MockServer {
4344
addr: OnceCell::new(),
4445
base_url: OnceCell::new(),
4546
state: Arc::new(MockServerState::default()),
47+
config: MockServerConfig::default(),
4648
}
4749
}
4850

@@ -58,15 +60,38 @@ impl MockServer {
5860
self
5961
}
6062

63+
/// Sets the server configuration.
64+
pub fn with_config(mut self, config: MockServerConfig) -> Self {
65+
self.config = config;
66+
self
67+
}
68+
6169
pub async fn start(&self) -> Result<(), Error> {
6270
if self.addr().is_some() {
6371
return Err(Error::ServerError("already running".into()));
6472
}
65-
let port = find_available_port().unwrap();
66-
let addr = SocketAddr::from(([0, 0, 0, 0], port));
73+
74+
let mut counter = 0;
75+
let mut rng = rand::rng();
76+
77+
let listener = loop {
78+
let port: u16 =
79+
rng.random_range(self.config.port_range_start..self.config.port_range_end);
80+
let addr = SocketAddr::from((self.config.listen_addr, port));
81+
if let Ok(listener) = TcpListener::bind(&addr).await {
82+
break listener;
83+
}
84+
85+
if counter == self.config.bind_max_retries {
86+
return Err(Error::ServerError("server failed to bind to port".into()));
87+
}
88+
counter += 1;
89+
};
90+
91+
let addr = listener.local_addr()?;
92+
info!("started {} [{}] server on {addr}", self.name(), &self.kind);
6793
let base_url = Url::parse(&format!("http://{}", &addr)).unwrap();
68-
info!("starting {} [{}] server on {addr}", self.name(), &self.kind);
69-
let listener = TcpListener::bind(&addr).await?;
94+
7095
match self.kind {
7196
ServerKind::Http => {
7297
let service = HttpMockService::new(self.state.clone());
@@ -78,10 +103,15 @@ impl MockServer {
78103
}
79104
};
80105
// Wait for server to become ready
81-
for _ in 0..30 {
82-
if TcpStream::connect_timeout(&addr, Duration::from_millis(10)).is_ok() {
106+
let mut counter = 0;
107+
loop {
108+
if TcpStream::connect_timeout(&addr, self.config.ready_connect_timeout).is_ok() {
83109
break;
84110
}
111+
if counter == self.config.ready_connect_max_retries {
112+
return Err(Error::ServerError("server failed to become ready".into()));
113+
}
114+
counter += 1;
85115
tokio::time::sleep(Duration::from_millis(10)).await;
86116
}
87117
info!("{} server ready", self.name());
@@ -227,16 +257,31 @@ where
227257
Ok(())
228258
}
229259

230-
fn find_available_port() -> Option<u16> {
231-
let mut rng = rand::rng();
232-
loop {
233-
let port: u16 = rng.random_range(40000..60000);
234-
if port_is_available(port) {
235-
return Some(port);
236-
}
260+
#[derive(Debug)]
261+
pub struct MockServerConfig {
262+
pub listen_addr: IpAddr,
263+
pub port_range_start: u16,
264+
pub port_range_end: u16,
265+
pub bind_max_retries: usize,
266+
pub ready_connect_max_retries: usize,
267+
pub ready_connect_timeout: Duration,
268+
}
269+
270+
impl MockServerConfig {
271+
pub fn new() -> Self {
272+
Self::default()
237273
}
238274
}
239275

240-
fn port_is_available(port: u16) -> bool {
241-
std::net::TcpListener::bind(("0.0.0.0", port)).is_ok()
276+
impl Default for MockServerConfig {
277+
fn default() -> Self {
278+
Self {
279+
listen_addr: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
280+
port_range_start: 10000,
281+
port_range_end: 30000,
282+
bind_max_retries: 10,
283+
ready_connect_max_retries: 30,
284+
ready_connect_timeout: Duration::from_millis(10),
285+
}
286+
}
242287
}

0 commit comments

Comments
 (0)