ssh remoting: Add infrastructure to handle reconnects (#18572)
This restructures the code in `remote` so that it's easier to replace the current SSH connection with a new one in case of disconnects/reconnects. Right now, it successfully reconnects, BUT we're still missing the big piece on the server-side: keeping the server process alive and reconnecting to the same process that keeps the project-state. Release Notes: - N/A --------- Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
parent
527c9097f8
commit
7ce8797d78
11 changed files with 562 additions and 401 deletions
|
@ -54,7 +54,7 @@ use parking_lot::{Mutex, RwLock};
|
|||
use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};
|
||||
pub use prettier_store::PrettierStore;
|
||||
use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
|
||||
use remote::SshSession;
|
||||
use remote::SshRemoteClient;
|
||||
use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode};
|
||||
use search::{SearchInputKind, SearchQuery, SearchResult};
|
||||
use search_history::SearchHistory;
|
||||
|
@ -138,7 +138,7 @@ pub struct Project {
|
|||
join_project_response_message_id: u32,
|
||||
user_store: Model<UserStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
ssh_session: Option<Arc<SshSession>>,
|
||||
ssh_client: Option<Arc<SshRemoteClient>>,
|
||||
client_state: ProjectClientState,
|
||||
collaborators: HashMap<proto::PeerId, Collaborator>,
|
||||
client_subscriptions: Vec<client::Subscription>,
|
||||
|
@ -643,7 +643,7 @@ impl Project {
|
|||
user_store,
|
||||
settings_observer,
|
||||
fs,
|
||||
ssh_session: None,
|
||||
ssh_client: None,
|
||||
buffers_needing_diff: Default::default(),
|
||||
git_diff_debouncer: DebouncedDelay::new(),
|
||||
terminals: Terminals {
|
||||
|
@ -664,7 +664,7 @@ impl Project {
|
|||
}
|
||||
|
||||
pub fn ssh(
|
||||
ssh: Arc<SshSession>,
|
||||
ssh: Arc<SshRemoteClient>,
|
||||
client: Arc<Client>,
|
||||
node: NodeRuntime,
|
||||
user_store: Model<UserStore>,
|
||||
|
@ -682,14 +682,14 @@ impl Project {
|
|||
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
|
||||
|
||||
let worktree_store =
|
||||
cx.new_model(|_| WorktreeStore::remote(false, ssh.clone().into(), 0, None));
|
||||
cx.new_model(|_| WorktreeStore::remote(false, ssh.to_proto_client(), 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.clone().into(),
|
||||
ssh.to_proto_client(),
|
||||
SSH_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
|
@ -698,7 +698,7 @@ impl Project {
|
|||
.detach();
|
||||
|
||||
let settings_observer = cx.new_model(|cx| {
|
||||
SettingsObserver::new_ssh(ssh.clone().into(), worktree_store.clone(), cx)
|
||||
SettingsObserver::new_ssh(ssh.to_proto_client(), worktree_store.clone(), cx)
|
||||
});
|
||||
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
|
||||
.detach();
|
||||
|
@ -709,7 +709,7 @@ impl Project {
|
|||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
languages.clone(),
|
||||
ssh.clone().into(),
|
||||
ssh.to_proto_client(),
|
||||
SSH_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
|
@ -733,7 +733,7 @@ impl Project {
|
|||
user_store,
|
||||
settings_observer,
|
||||
fs,
|
||||
ssh_session: Some(ssh.clone()),
|
||||
ssh_client: Some(ssh.clone()),
|
||||
buffers_needing_diff: Default::default(),
|
||||
git_diff_debouncer: DebouncedDelay::new(),
|
||||
terminals: Terminals {
|
||||
|
@ -751,7 +751,7 @@ impl Project {
|
|||
search_excluded_history: Self::new_search_history(),
|
||||
};
|
||||
|
||||
let client: AnyProtoClient = ssh.clone().into();
|
||||
let client: AnyProtoClient = ssh.to_proto_client();
|
||||
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &cx.handle());
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
|
||||
|
@ -907,7 +907,7 @@ impl Project {
|
|||
user_store: user_store.clone(),
|
||||
snippets,
|
||||
fs,
|
||||
ssh_session: None,
|
||||
ssh_client: None,
|
||||
settings_observer: settings_observer.clone(),
|
||||
client_subscriptions: Default::default(),
|
||||
_subscriptions: vec![cx.on_release(Self::release)],
|
||||
|
@ -1230,7 +1230,7 @@ impl Project {
|
|||
match self.client_state {
|
||||
ProjectClientState::Remote { replica_id, .. } => replica_id,
|
||||
_ => {
|
||||
if self.ssh_session.is_some() {
|
||||
if self.ssh_client.is_some() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
|
@ -1638,7 +1638,7 @@ impl Project {
|
|||
pub fn is_local(&self) -> bool {
|
||||
match &self.client_state {
|
||||
ProjectClientState::Local | ProjectClientState::Shared { .. } => {
|
||||
self.ssh_session.is_none()
|
||||
self.ssh_client.is_none()
|
||||
}
|
||||
ProjectClientState::Remote { .. } => false,
|
||||
}
|
||||
|
@ -1647,7 +1647,7 @@ impl Project {
|
|||
pub fn is_via_ssh(&self) -> bool {
|
||||
match &self.client_state {
|
||||
ProjectClientState::Local | ProjectClientState::Shared { .. } => {
|
||||
self.ssh_session.is_some()
|
||||
self.ssh_client.is_some()
|
||||
}
|
||||
ProjectClientState::Remote { .. } => false,
|
||||
}
|
||||
|
@ -1933,8 +1933,9 @@ impl Project {
|
|||
}
|
||||
BufferStoreEvent::BufferChangedFilePath { .. } => {}
|
||||
BufferStoreEvent::BufferDropped(buffer_id) => {
|
||||
if let Some(ref ssh_session) = self.ssh_session {
|
||||
ssh_session
|
||||
if let Some(ref ssh_client) = self.ssh_client {
|
||||
ssh_client
|
||||
.to_proto_client()
|
||||
.send(proto::CloseBuffer {
|
||||
project_id: 0,
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
|
@ -2139,13 +2140,14 @@ impl Project {
|
|||
} => {
|
||||
let operation = language::proto::serialize_operation(operation);
|
||||
|
||||
if let Some(ssh) = &self.ssh_session {
|
||||
ssh.send(proto::UpdateBuffer {
|
||||
project_id: 0,
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
operations: vec![operation.clone()],
|
||||
})
|
||||
.ok();
|
||||
if let Some(ssh) = &self.ssh_client {
|
||||
ssh.to_proto_client()
|
||||
.send(proto::UpdateBuffer {
|
||||
project_id: 0,
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
operations: vec![operation.clone()],
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
|
||||
|
@ -2825,14 +2827,13 @@ impl Project {
|
|||
) -> Receiver<Model<Buffer>> {
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
|
||||
let (client, remote_id): (AnyProtoClient, _) =
|
||||
if let Some(ssh_session) = self.ssh_session.clone() {
|
||||
(ssh_session.into(), 0)
|
||||
} else if let Some(remote_id) = self.remote_id() {
|
||||
(self.client.clone().into(), remote_id)
|
||||
} else {
|
||||
return rx;
|
||||
};
|
||||
let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.ssh_client {
|
||||
(ssh_client.to_proto_client(), 0)
|
||||
} else if let Some(remote_id) = self.remote_id() {
|
||||
(self.client.clone().into(), remote_id)
|
||||
} else {
|
||||
return rx;
|
||||
};
|
||||
|
||||
let request = client.request(proto::FindSearchCandidates {
|
||||
project_id: remote_id,
|
||||
|
@ -2961,11 +2962,13 @@ impl Project {
|
|||
|
||||
exists.then(|| ResolvedPath::AbsPath(expanded))
|
||||
})
|
||||
} else if let Some(ssh_session) = self.ssh_session.as_ref() {
|
||||
let request = ssh_session.request(proto::CheckFileExists {
|
||||
project_id: SSH_PROJECT_ID,
|
||||
path: path.to_string(),
|
||||
});
|
||||
} 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(),
|
||||
});
|
||||
cx.background_executor().spawn(async move {
|
||||
let response = request.await.log_err()?;
|
||||
if response.exists {
|
||||
|
@ -3035,13 +3038,13 @@ impl Project {
|
|||
) -> Task<Result<Vec<PathBuf>>> {
|
||||
if self.is_local() {
|
||||
DirectoryLister::Local(self.fs.clone()).list_directory(query, cx)
|
||||
} else if let Some(session) = self.ssh_session.as_ref() {
|
||||
} else if let Some(session) = self.ssh_client.as_ref() {
|
||||
let request = proto::ListRemoteDirectory {
|
||||
dev_server_id: SSH_PROJECT_ID,
|
||||
path: query,
|
||||
};
|
||||
|
||||
let response = session.request(request);
|
||||
let response = session.to_proto_client().request(request);
|
||||
cx.background_executor().spawn(async move {
|
||||
let response = response.await?;
|
||||
Ok(response.entries.into_iter().map(PathBuf::from).collect())
|
||||
|
@ -3465,11 +3468,11 @@ impl Project {
|
|||
cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let buffer_store = this.read_with(&cx, |this, cx| {
|
||||
if let Some(ssh) = &this.ssh_session {
|
||||
if let Some(ssh) = &this.ssh_client {
|
||||
let mut payload = envelope.payload.clone();
|
||||
payload.project_id = 0;
|
||||
cx.background_executor()
|
||||
.spawn(ssh.request(payload))
|
||||
.spawn(ssh.to_proto_client().request(payload))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
this.buffer_store.clone()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue