ssh remoting: Enable reconnecting after connection losses (#18586)

Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
Thorsten Ball 2024-10-07 11:40:59 +02:00 committed by GitHub
parent 67fbdbbed6
commit c03b8d6c48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 727 additions and 240 deletions

View file

@ -141,7 +141,7 @@ pub struct Project {
join_project_response_message_id: u32,
user_store: Model<UserStore>,
fs: Arc<dyn Fs>,
ssh_client: Option<Arc<SshRemoteClient>>,
ssh_client: Option<Model<SshRemoteClient>>,
client_state: ProjectClientState,
collaborators: HashMap<proto::PeerId, Collaborator>,
client_subscriptions: Vec<client::Subscription>,
@ -667,7 +667,7 @@ impl Project {
}
pub fn ssh(
ssh: Arc<SshRemoteClient>,
ssh: Model<SshRemoteClient>,
client: Arc<Client>,
node: NodeRuntime,
user_store: Model<UserStore>,
@ -684,15 +684,16 @@ impl Project {
let snippets =
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
let ssh_proto = ssh.read(cx).to_proto_client();
let worktree_store =
cx.new_model(|_| WorktreeStore::remote(false, ssh.to_proto_client(), 0, None));
cx.new_model(|_| WorktreeStore::remote(false, ssh_proto.clone(), 0, None));
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
let buffer_store = cx.new_model(|cx| {
BufferStore::remote(
worktree_store.clone(),
ssh.to_proto_client(),
ssh.read(cx).to_proto_client(),
SSH_PROJECT_ID,
cx,
)
@ -701,7 +702,7 @@ impl Project {
.detach();
let settings_observer = cx.new_model(|cx| {
SettingsObserver::new_ssh(ssh.to_proto_client(), worktree_store.clone(), cx)
SettingsObserver::new_ssh(ssh_proto.clone(), worktree_store.clone(), cx)
});
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
.detach();
@ -712,13 +713,24 @@ impl Project {
buffer_store.clone(),
worktree_store.clone(),
languages.clone(),
ssh.to_proto_client(),
ssh_proto.clone(),
SSH_PROJECT_ID,
cx,
)
});
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
cx.on_release(|this, cx| {
if let Some(ssh_client) = this.ssh_client.as_ref() {
ssh_client
.read(cx)
.to_proto_client()
.send(proto::ShutdownRemoteServer {})
.log_err();
}
})
.detach();
let this = Self {
buffer_ordered_messages_tx: tx,
collaborators: Default::default(),
@ -754,20 +766,20 @@ impl Project {
search_excluded_history: Self::new_search_history(),
};
let client: AnyProtoClient = ssh.to_proto_client();
let ssh = ssh.read(cx);
ssh.subscribe_to_entity(SSH_PROJECT_ID, &cx.handle());
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store);
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.lsp_store);
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer);
client.add_model_message_handler(Self::handle_create_buffer_for_peer);
client.add_model_message_handler(Self::handle_update_worktree);
client.add_model_message_handler(Self::handle_update_project);
client.add_model_request_handler(BufferStore::handle_update_buffer);
BufferStore::init(&client);
LspStore::init(&client);
SettingsObserver::init(&client);
ssh_proto.add_model_message_handler(Self::handle_create_buffer_for_peer);
ssh_proto.add_model_message_handler(Self::handle_update_worktree);
ssh_proto.add_model_message_handler(Self::handle_update_project);
ssh_proto.add_model_request_handler(BufferStore::handle_update_buffer);
BufferStore::init(&ssh_proto);
LspStore::init(&ssh_proto);
SettingsObserver::init(&ssh_proto);
this
})
@ -1222,7 +1234,7 @@ impl Project {
pub fn ssh_connection_string(&self, cx: &AppContext) -> Option<SharedString> {
if let Some(ssh_state) = &self.ssh_client {
return Some(ssh_state.connection_string().into());
return Some(ssh_state.read(cx).connection_string().into());
}
let dev_server_id = self.dev_server_project_id()?;
dev_server_projects::Store::global(cx)
@ -1232,8 +1244,8 @@ impl Project {
.clone()
}
pub fn ssh_is_connected(&self) -> Option<bool> {
Some(!self.ssh_client.as_ref()?.is_reconnect_underway())
pub fn ssh_is_connected(&self, cx: &AppContext) -> Option<bool> {
Some(!self.ssh_client.as_ref()?.read(cx).is_reconnect_underway())
}
pub fn replica_id(&self) -> ReplicaId {
@ -1945,6 +1957,7 @@ impl Project {
BufferStoreEvent::BufferDropped(buffer_id) => {
if let Some(ref ssh_client) = self.ssh_client {
ssh_client
.read(cx)
.to_proto_client()
.send(proto::CloseBuffer {
project_id: 0,
@ -2151,7 +2164,8 @@ impl Project {
let operation = language::proto::serialize_operation(operation);
if let Some(ssh) = &self.ssh_client {
ssh.to_proto_client()
ssh.read(cx)
.to_proto_client()
.send(proto::UpdateBuffer {
project_id: 0,
buffer_id: buffer_id.to_proto(),
@ -2838,7 +2852,7 @@ impl Project {
let (tx, rx) = smol::channel::unbounded();
let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.ssh_client {
(ssh_client.to_proto_client(), 0)
(ssh_client.read(cx).to_proto_client(), 0)
} else if let Some(remote_id) = self.remote_id() {
(self.client.clone().into(), remote_id)
} else {
@ -2973,12 +2987,14 @@ impl Project {
exists.then(|| ResolvedPath::AbsPath(expanded))
})
} else if let Some(ssh_client) = self.ssh_client.as_ref() {
let request = ssh_client
.to_proto_client()
.request(proto::CheckFileExists {
project_id: SSH_PROJECT_ID,
path: path.to_string(),
});
let request =
ssh_client
.read(cx)
.to_proto_client()
.request(proto::CheckFileExists {
project_id: SSH_PROJECT_ID,
path: path.to_string(),
});
cx.background_executor().spawn(async move {
let response = request.await.log_err()?;
if response.exists {
@ -3054,7 +3070,7 @@ impl Project {
path: query,
};
let response = session.to_proto_client().request(request);
let response = session.read(cx).to_proto_client().request(request);
cx.background_executor().spawn(async move {
let response = response.await?;
Ok(response.entries.into_iter().map(PathBuf::from).collect())
@ -3482,7 +3498,7 @@ impl Project {
let mut payload = envelope.payload.clone();
payload.project_id = 0;
cx.background_executor()
.spawn(ssh.to_proto_client().request(payload))
.spawn(ssh.read(cx).to_proto_client().request(payload))
.detach_and_log_err(cx);
}
this.buffer_store.clone()