chore: optimize websocket connect (#352)

* chore: optimize websocket connect

* chore: fix clippy

* chore: fix clippy

* chore: fix test

* chore: fix test
This commit is contained in:
Nathan.fooo 2024-02-26 15:24:54 +08:00 committed by GitHub
parent deffd074b9
commit 375318774a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 71 additions and 71 deletions

View File

@ -156,7 +156,7 @@ impl WSClientHttpSender for Client {
self self
.post_realtime_msg(device_id, message) .post_realtime_msg(device_id, message)
.await .await
.map_err(|err| WSError::Internal(anyhow::Error::from(err))) .map_err(|err| WSError::Http(err.to_string()))
} }
} }

View File

@ -59,6 +59,10 @@ impl ServerFixIntervalPing {
// send ping to server // send ping to server
if let Err(err) = ping_sender.send(Message::Ping(vec![])) { if let Err(err) = ping_sender.send(Message::Ping(vec![])) {
tracing::error!("ping send error: {}", err); tracing::error!("ping send error: {}", err);
if let Some(state) =weak_state.upgrade() {
state.lock().set_state(ConnectState::PingTimeout);
}
break;
} }
if let Some(ping_count) = weak_ping_count.upgrade() { if let Some(ping_count) = weak_ping_count.upgrade() {
let mut lock = ping_count.lock().await; let mut lock = ping_count.lock().await;

View File

@ -71,7 +71,7 @@ pub async fn retry_connect(
) -> Result<WebSocketStream, WSError> { ) -> Result<WebSocketStream, WSError> {
let connecting_addr = addr.to_owned(); let connecting_addr = addr.to_owned();
let stream = RetryIf::spawn( let stream = RetryIf::spawn(
FixedInterval::new(Duration::from_secs(6)), FixedInterval::new(Duration::from_secs(10)),
ConnectAction::new(connecting_addr.clone()), ConnectAction::new(connecting_addr.clone()),
RetryCondition { RetryCondition {
connecting_addr, connecting_addr,

View File

@ -102,6 +102,11 @@ impl WSClient {
} }
pub async fn connect(&self, addr: String, device_id: &str) -> Result<(), WSError> { pub async fn connect(&self, addr: String, device_id: &str) -> Result<(), WSError> {
if self.get_state().is_connecting() {
info!("websocket is connecting, skip connect request");
return Ok(());
}
self.set_state(ConnectState::Connecting).await; self.set_state(ConnectState::Connecting).await;
// stop receiving message from client // stop receiving message from client
@ -136,6 +141,7 @@ impl WSClient {
WSError::LostConnection(_) => state_notify.lock().set_state(ConnectState::Closed), WSError::LostConnection(_) => state_notify.lock().set_state(ConnectState::Closed),
WSError::AuthError(_) => state_notify.lock().set_state(ConnectState::Unauthorized), WSError::AuthError(_) => state_notify.lock().set_state(ConnectState::Unauthorized),
WSError::Internal(_) => {}, WSError::Internal(_) => {},
WSError::Http(_) => {},
}, },
} }
}; };
@ -233,10 +239,12 @@ impl WSClient {
}, },
Message::Close(close) => { Message::Close(close) => {
info!("websocket close: {:?}", close); info!("websocket close: {:?}", close);
break;
}, },
Message::Pong(_) => { Message::Pong(_) => {
if let Err(err) = pong_tx.send(()).await { if let Err(err) = pong_tx.send(()).await {
error!("failed to receive server pong: {}", err); error!("failed to receive server pong: {}", err);
break;
} }
}, },
_ => warn!("received unexpected message from websocket: {:?}", ws_msg), _ => warn!("received unexpected message from websocket: {:?}", ws_msg),
@ -272,9 +280,11 @@ impl WSClient {
error!("The HTTP sender has been dropped, unable to send message."); error!("The HTTP sender has been dropped, unable to send message.");
continue; continue;
} }
} else if let Err(err) = sink.send(msg).await.map_err(WSError::from){ } else if let Err(err) = sink.send(msg).await.map_err(WSError::from) {
handle_ws_error(&err); if err.is_lost_connection() {
break; break;
}
handle_ws_error(&err);
} }
} }
} }

View File

@ -12,10 +12,19 @@ pub enum WSError {
#[error("Auth error: {0}")] #[error("Auth error: {0}")]
AuthError(String), AuthError(String),
#[error("Fail to send message via http: {0}")]
Http(String),
#[error(transparent)] #[error(transparent)]
Internal(#[from] anyhow::Error), Internal(#[from] anyhow::Error),
} }
impl WSError {
pub fn is_lost_connection(&self) -> bool {
matches!(self, WSError::LostConnection(_))
}
}
impl From<Error> for WSError { impl From<Error> for WSError {
fn from(value: Error) -> Self { fn from(value: Error) -> Self {
match &value { match &value {

View File

@ -22,32 +22,6 @@ async fn realtime_connect_test() {
} }
} }
#[tokio::test]
async fn realtime_connect_after_token_exp_test() {
let (c, _user) = generate_unique_registered_user_client().await;
// Set the token to be expired
c.token().write().as_mut().unwrap().expires_at = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let ws_client = WSClient::new(WSClientConfig::default(), c.clone());
let mut state = ws_client.subscribe_connect_state();
let device_id = "fake_device_id";
loop {
tokio::select! {
_ = ws_client.connect(c.ws_url(device_id).await.unwrap(), device_id) => {},
value = state.recv() => {
let new_state = value.unwrap();
if new_state == ConnectState::Connected {
break;
}
},
}
}
}
#[tokio::test] #[tokio::test]
async fn realtime_disconnect_test() { async fn realtime_disconnect_test() {
let (c, _user) = generate_unique_registered_user_client().await; let (c, _user) = generate_unique_registered_user_client().await;
@ -71,19 +45,3 @@ async fn realtime_disconnect_test() {
} }
} }
} }
// use std::time::Duration;
// use tokio_tungstenite::tungstenite::Message;
// #[tokio::test]
// async fn max_frame_size() {
// let (c, _user) = generate_unique_registered_user_client().await;
// let ws_client = WSClient::new(WSClientConfig::default(), c.clone());
// let device_id = "fake_device_id";
// ws_client
// .connect(c.ws_url(device_id).unwrap(), device_id)
// .await
// .unwrap();
//
// ws_client.send(Message::Binary(vec![0; 65536])).unwrap();
// tokio::time::sleep(Duration::from_secs(5)).await;
// }

View File

@ -440,9 +440,9 @@ async fn multiple_user_with_read_and_write_permission_edit_same_collab_test() {
sleep(Duration::from_secs(5)).await; sleep(Duration::from_secs(5)).await;
// all the clients should have the same collab object // all the clients should have the same collab object
assert_json_include!( assert_json_eq!(
actual: json!(expected_json), json!(expected_json),
expected: arc_owner arc_owner
.collab_by_object_id .collab_by_object_id
.get(&object_id) .get(&object_id)
.unwrap() .unwrap()
@ -452,8 +452,8 @@ async fn multiple_user_with_read_and_write_permission_edit_same_collab_test() {
for client in clients { for client in clients {
assert_json_include!( assert_json_include!(
expected: json!(expected_json), actual: json!(expected_json),
actual: client expected: client
.collab_by_object_id .collab_by_object_id
.get(&object_id) .get(&object_id)
.unwrap() .unwrap()

View File

@ -1,4 +1,5 @@
use std::time::SystemTime; use std::time::{Duration, SystemTime};
use tokio::time::timeout;
use client_api::ws::{ConnectState, WSClient, WSClientConfig}; use client_api::ws::{ConnectState, WSClient, WSClientConfig};
use client_api_test_util::generate_unique_registered_user_client; use client_api_test_util::generate_unique_registered_user_client;
@ -9,16 +10,25 @@ async fn realtime_connect_test() {
let ws_client = WSClient::new(WSClientConfig::default(), c.clone()); let ws_client = WSClient::new(WSClientConfig::default(), c.clone());
let mut state = ws_client.subscribe_connect_state(); let mut state = ws_client.subscribe_connect_state();
let device_id = "fake_device_id"; let device_id = "fake_device_id";
loop {
tokio::select! { tokio::spawn(async move {
_ = ws_client.connect(c.ws_url(device_id).await.unwrap(), device_id) => {}, ws_client
value = state.recv() => { .connect(c.ws_url(device_id).await.unwrap(), device_id)
let new_state = value.unwrap(); .await
if new_state == ConnectState::Connected { });
break;
} let connect_future = async {
}, while let Ok(state) = state.recv().await {
if state == ConnectState::Connected {
break;
}
} }
};
// Apply the timeout
match timeout(Duration::from_secs(10), connect_future).await {
Ok(_) => {},
Err(_) => panic!("Connection timeout."),
} }
} }
@ -35,16 +45,25 @@ async fn realtime_connect_after_token_exp_test() {
let ws_client = WSClient::new(WSClientConfig::default(), c.clone()); let ws_client = WSClient::new(WSClientConfig::default(), c.clone());
let mut state = ws_client.subscribe_connect_state(); let mut state = ws_client.subscribe_connect_state();
let device_id = "fake_device_id"; let device_id = "fake_device_id";
loop {
tokio::select! { tokio::spawn(async move {
_ = ws_client.connect(c.ws_url(device_id).await.unwrap(), device_id) => {}, ws_client
value = state.recv() => { .connect(c.ws_url(device_id).await.unwrap(), device_id)
let new_state = value.unwrap(); .await
if new_state == ConnectState::Connected { });
break;
} let connect_future = async {
}, while let Ok(state) = state.recv().await {
if state == ConnectState::Connected {
break;
}
} }
};
// Apply the timeout
match timeout(Duration::from_secs(10), connect_future).await {
Ok(_) => {},
Err(_) => panic!("Connection timeout."),
} }
} }