From 6b9443bf2103fcca2174005e775e83d5a15513e5 Mon Sep 17 00:00:00 2001 From: koe Date: Sat, 30 Sep 2023 17:56:48 -0500 Subject: [PATCH] add client config for number of initial connect/reconnect attempts --- CHANGELOG.md | 7 ++++--- src/client.rs | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e855d..08c5cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,9 +22,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Return `Ok(MessageSignal)` from `Client` and `Session` `.binary()/.text()/.close()` endpoints instead of `Ok(())`. The `MessageSignal::state()` method will indicate the current state of the message (sending/sent/failed). - Clients attempt to reconnect immediately instead of after one full reconnect interval. - Incoming user messages are discarded while a client is reconnecting, to better match the usual behavior of a websocket connection. If you want messages to be buffered while reconnecting, you should implement your own buffer. -- rename `socket::Config` -> `socket::SocketConfig` and add a `heartbeat_ping_msg_fn` member variable in order to support custom Ping/Pong protocols - - add `ClientConfig::socket_config()` setter so clients can define their socket's config - - add `ezsockets::axum::Upgrade::on_upgrade_with_config()` that accepts a `SocketConfig` +- Rename `socket::Config` -> `socket::SocketConfig` and add a `heartbeat_ping_msg_fn` member variable in order to support custom Ping/Pong protocols. + - Add `ClientConfig::socket_config()` setter so clients can define their socket's config. + - Add `ezsockets::axum::Upgrade::on_upgrade_with_config()` that accepts a `SocketConfig`. +- Refactor `ezeockets::client::connect()` to use a retry loop for the initial connection. Add `max_initial_connect_attempts` and `max_reconnect_attempts` options to the `ClientConfig` (they default to 'infinite'). Migration guide: diff --git a/src/client.rs b/src/client.rs index 1965b53..506eed9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -71,6 +71,8 @@ pub const DEFAULT_RECONNECT_INTERVAL: Duration = Duration::new(5, 0); #[derive(Debug)] pub struct ClientConfig { url: Url, + max_initial_connect_attempts: usize, + max_reconnect_attempts: usize, reconnect_interval: Duration, headers: http::HeaderMap, socket_config: Option, @@ -87,6 +89,8 @@ impl ClientConfig { let url = url.try_into().expect("invalid URL"); Self { url, + max_initial_connect_attempts: usize::MAX, + max_reconnect_attempts: usize::MAX, reconnect_interval: DEFAULT_RECONNECT_INTERVAL, headers: http::HeaderMap::new(), socket_config: None, @@ -152,6 +156,22 @@ impl ClientConfig { self } + /// Set the maximum number of connection attempts when starting a client. + /// + /// Defaults to infinite. + pub fn max_initial_connect_attempts(mut self, max_initial_connect_attempts: usize) -> Self { + self.max_initial_connect_attempts = max_initial_connect_attempts; + self + } + + /// Set the maximum number of attempts when reconnecting. + /// + /// Defaults to infinite. + pub fn max_reconnect_attempts(mut self, max_reconnect_attempts: usize) -> Self { + self.max_reconnect_attempts = max_reconnect_attempts; + self + } + /// Set the reconnect interval. pub fn reconnect_interval(mut self, reconnect_interval: Duration) -> Self { self.reconnect_interval = reconnect_interval; @@ -335,8 +355,13 @@ pub async fn connect( let mut client = client_fn(handle.clone()); let future = tokio::spawn(async move { tracing::info!("connecting to {}...", config.url); - let Some(socket) = - client_connect(1usize, &config, &mut to_socket_receiver, &mut client).await? + let Some(socket) = client_connect( + config.max_initial_connect_attempts, + &config, + &mut to_socket_receiver, + &mut client, + ) + .await? else { return Ok(()); }; @@ -393,7 +418,7 @@ impl ClientActor { { ClientCloseMode::Reconnect => { let Some(socket) = client_connect( - usize::MAX, + self.config.max_reconnect_attempts, &self.config, &mut self.to_socket_receiver, &mut self.client, @@ -416,7 +441,7 @@ impl ClientActor { { ClientCloseMode::Reconnect => { let Some(socket) = client_connect( - usize::MAX, + self.config.max_reconnect_attempts, &self.config, &mut self.to_socket_receiver, &mut self.client, @@ -454,7 +479,8 @@ async fn client_connect( Ok((socket, _)) => { tracing::info!("successfully connected"); if let Err(err) = client.on_connect().await { - tracing::error!("calling on_connect() failed due to {}", err); + tracing::error!("calling on_connect() failed due to {}, closing client", err); + return Err(err); } let socket = Socket::new(socket, config.socket_config.clone().unwrap_or_default()); return Ok(Some(socket)); @@ -468,7 +494,7 @@ async fn client_connect( } }; - // abort if we have exceeded the max attempts + // abort if we have reached the max attempts if i >= max_attempts { return Err(Error::from(format!( "failed to connect after {} attempt(s), aborting...",