mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Introduce client-side timeout when trying to connect
This commit is contained in:
parent
a19783919c
commit
cc56fa9ea6
@ -53,6 +53,8 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
||||||
|
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
|
||||||
|
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
actions!(client, [Authenticate]);
|
actions!(client, [Authenticate]);
|
||||||
|
|
||||||
@ -330,7 +332,7 @@ impl Client {
|
|||||||
let reconnect_interval = state.reconnect_interval;
|
let reconnect_interval = state.reconnect_interval;
|
||||||
state._reconnect_task = Some(cx.spawn(|cx| async move {
|
state._reconnect_task = Some(cx.spawn(|cx| async move {
|
||||||
let mut rng = StdRng::from_entropy();
|
let mut rng = StdRng::from_entropy();
|
||||||
let mut delay = Duration::from_millis(100);
|
let mut delay = INITIAL_RECONNECTION_DELAY;
|
||||||
while let Err(error) = this.authenticate_and_connect(true, &cx).await {
|
while let Err(error) = this.authenticate_and_connect(true, &cx).await {
|
||||||
log::error!("failed to connect {}", error);
|
log::error!("failed to connect {}", error);
|
||||||
if matches!(*this.status().borrow(), Status::ConnectionError) {
|
if matches!(*this.status().borrow(), Status::ConnectionError) {
|
||||||
@ -661,33 +663,42 @@ impl Client {
|
|||||||
self.set_status(Status::Reconnecting, cx);
|
self.set_status(Status::Reconnecting, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.establish_connection(&credentials, cx).await {
|
let mut timeout = cx.background().timer(CONNECTION_TIMEOUT).fuse();
|
||||||
Ok(conn) => {
|
futures::select_biased! {
|
||||||
self.state.write().credentials = Some(credentials.clone());
|
connection = self.establish_connection(&credentials, cx).fuse() => {
|
||||||
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
|
match connection {
|
||||||
write_credentials_to_keychain(&credentials, cx).log_err();
|
Ok(conn) => {
|
||||||
}
|
self.state.write().credentials = Some(credentials.clone());
|
||||||
self.set_connection(conn, cx).await;
|
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
|
||||||
Ok(())
|
write_credentials_to_keychain(&credentials, cx).log_err();
|
||||||
}
|
}
|
||||||
Err(EstablishConnectionError::Unauthorized) => {
|
self.set_connection(conn, cx).await;
|
||||||
self.state.write().credentials.take();
|
Ok(())
|
||||||
if read_from_keychain {
|
}
|
||||||
cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
|
Err(EstablishConnectionError::Unauthorized) => {
|
||||||
self.set_status(Status::SignedOut, cx);
|
self.state.write().credentials.take();
|
||||||
self.authenticate_and_connect(false, cx).await
|
if read_from_keychain {
|
||||||
} else {
|
cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
|
||||||
self.set_status(Status::ConnectionError, cx);
|
self.set_status(Status::SignedOut, cx);
|
||||||
Err(EstablishConnectionError::Unauthorized)?
|
self.authenticate_and_connect(false, cx).await
|
||||||
|
} else {
|
||||||
|
self.set_status(Status::ConnectionError, cx);
|
||||||
|
Err(EstablishConnectionError::Unauthorized)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(EstablishConnectionError::UpgradeRequired) => {
|
||||||
|
self.set_status(Status::UpgradeRequired, cx);
|
||||||
|
Err(EstablishConnectionError::UpgradeRequired)?
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.set_status(Status::ConnectionError, cx);
|
||||||
|
Err(error)?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(EstablishConnectionError::UpgradeRequired) => {
|
_ = timeout => {
|
||||||
self.set_status(Status::UpgradeRequired, cx);
|
|
||||||
Err(EstablishConnectionError::UpgradeRequired)?
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
self.set_status(Status::ConnectionError, cx);
|
self.set_status(Status::ConnectionError, cx);
|
||||||
Err(error)?
|
Err(anyhow!("timed out trying to establish connection"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1169,6 +1180,76 @@ mod tests {
|
|||||||
assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
|
assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test(iterations = 10)]
|
||||||
|
async fn test_connection_timeout(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||||
|
deterministic.forbid_parking();
|
||||||
|
|
||||||
|
let user_id = 5;
|
||||||
|
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||||
|
let mut status = client.status();
|
||||||
|
|
||||||
|
// Time out when client tries to connect.
|
||||||
|
client.override_authenticate(move |cx| {
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
Ok(Credentials {
|
||||||
|
user_id,
|
||||||
|
access_token: "token".into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
client.override_establish_connection(|_, cx| {
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
future::pending::<()>().await;
|
||||||
|
unreachable!()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let auth_and_connect = cx.spawn({
|
||||||
|
let client = client.clone();
|
||||||
|
|cx| async move { client.authenticate_and_connect(false, &cx).await }
|
||||||
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
assert!(matches!(status.next().await, Some(Status::Connecting)));
|
||||||
|
|
||||||
|
deterministic.advance_clock(CONNECTION_TIMEOUT);
|
||||||
|
assert!(matches!(
|
||||||
|
status.next().await,
|
||||||
|
Some(Status::ConnectionError { .. })
|
||||||
|
));
|
||||||
|
auth_and_connect.await.unwrap_err();
|
||||||
|
|
||||||
|
// Allow the connection to be established.
|
||||||
|
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||||
|
assert!(matches!(
|
||||||
|
status.next().await,
|
||||||
|
Some(Status::Connected { .. })
|
||||||
|
));
|
||||||
|
|
||||||
|
// Disconnect client.
|
||||||
|
server.forbid_connections();
|
||||||
|
server.disconnect();
|
||||||
|
while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
|
||||||
|
|
||||||
|
// Time out when re-establishing the connection.
|
||||||
|
server.allow_connections();
|
||||||
|
client.override_establish_connection(|_, cx| {
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
future::pending::<()>().await;
|
||||||
|
unreachable!()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
deterministic.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
|
||||||
|
assert!(matches!(
|
||||||
|
status.next().await,
|
||||||
|
Some(Status::Reconnecting { .. })
|
||||||
|
));
|
||||||
|
|
||||||
|
deterministic.advance_clock(CONNECTION_TIMEOUT);
|
||||||
|
assert!(matches!(
|
||||||
|
status.next().await,
|
||||||
|
Some(Status::ReconnectionError { .. })
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 10)]
|
#[gpui::test(iterations = 10)]
|
||||||
async fn test_authenticating_more_than_once(
|
async fn test_authenticating_more_than_once(
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
|
@ -101,10 +101,12 @@ impl FakeServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn disconnect(&self) {
|
pub fn disconnect(&self) {
|
||||||
self.peer.disconnect(self.connection_id());
|
if self.state.lock().connection_id.is_some() {
|
||||||
let mut state = self.state.lock();
|
self.peer.disconnect(self.connection_id());
|
||||||
state.connection_id.take();
|
let mut state = self.state.lock();
|
||||||
state.incoming.take();
|
state.connection_id.take();
|
||||||
|
state.incoming.take();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth_count(&self) -> usize {
|
pub fn auth_count(&self) -> usize {
|
||||||
|
Loading…
Reference in New Issue
Block a user