ssh remoting: Fix SSH connection not being closed (#18329)
This fixes the `SshSession` being leaked. There were two leaks: 1. `Arc<SshSession>` itself got leaked into the `SettingsObserver` that lives as long as the application. Fixed with a weak reference. 2. The two tasks spawned by an `SshSession` had a circular dependency and didn't exit while the other one was running. Fixed by fixing (1) and then attaching one of the tasks to the `SshSession`, which means it gets dropped with the session itself, which leads the other task to error and exit. Co-authored-by: Bennet <bennet@zed.dev> Release Notes: - N/A --------- Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
parent
623a6eca75
commit
9d197ddc99
5 changed files with 53 additions and 15 deletions
|
@ -334,18 +334,21 @@ impl SettingsObserver {
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let weak_client = ssh.downgrade();
|
||||||
cx.observe_global::<SettingsStore>(move |_, cx| {
|
cx.observe_global::<SettingsStore>(move |_, cx| {
|
||||||
let new_settings = cx.global::<SettingsStore>().raw_user_settings();
|
let new_settings = cx.global::<SettingsStore>().raw_user_settings();
|
||||||
if &settings != new_settings {
|
if &settings != new_settings {
|
||||||
settings = new_settings.clone()
|
settings = new_settings.clone()
|
||||||
}
|
}
|
||||||
if let Some(content) = serde_json::to_string(&settings).log_err() {
|
if let Some(content) = serde_json::to_string(&settings).log_err() {
|
||||||
|
if let Some(ssh) = weak_client.upgrade() {
|
||||||
ssh.send(proto::UpdateUserSettings {
|
ssh.send(proto::UpdateUserSettings {
|
||||||
project_id: 0,
|
project_id: 0,
|
||||||
content,
|
content,
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
|
@ -509,7 +509,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
SerializedWorkspaceLocation::Ssh(_) => Icon::new(IconName::Screen)
|
SerializedWorkspaceLocation::Ssh(_) => Icon::new(IconName::Server)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
SerializedWorkspaceLocation::DevServer(_) => {
|
SerializedWorkspaceLocation::DevServer(_) => {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use futures::{
|
||||||
future::BoxFuture,
|
future::BoxFuture,
|
||||||
select_biased, AsyncReadExt as _, AsyncWriteExt as _, Future, FutureExt as _, StreamExt as _,
|
select_biased, AsyncReadExt as _, AsyncWriteExt as _, Future, FutureExt as _, StreamExt as _,
|
||||||
};
|
};
|
||||||
use gpui::{AppContext, AsyncAppContext, Model, SemanticVersion};
|
use gpui::{AppContext, AsyncAppContext, Model, SemanticVersion, Task};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, build_typed_envelope, Envelope, EnvelopedMessage, PeerId, RequestMessage},
|
proto::{self, build_typed_envelope, Envelope, EnvelopedMessage, PeerId, RequestMessage},
|
||||||
|
@ -51,6 +51,7 @@ pub struct SshSession {
|
||||||
spawn_process_tx: mpsc::UnboundedSender<SpawnRequest>,
|
spawn_process_tx: mpsc::UnboundedSender<SpawnRequest>,
|
||||||
client_socket: Option<SshSocket>,
|
client_socket: Option<SshSocket>,
|
||||||
state: Mutex<ProtoMessageHandlerSet>, // Lock
|
state: Mutex<ProtoMessageHandlerSet>, // Lock
|
||||||
|
_io_task: Option<Task<Result<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SshClientState {
|
struct SshClientState {
|
||||||
|
@ -173,8 +174,7 @@ impl SshSession {
|
||||||
let mut child_stdout = remote_server_child.stdout.take().unwrap();
|
let mut child_stdout = remote_server_child.stdout.take().unwrap();
|
||||||
let mut child_stdin = remote_server_child.stdin.take().unwrap();
|
let mut child_stdin = remote_server_child.stdin.take().unwrap();
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
let io_task = cx.background_executor().spawn(async move {
|
||||||
executor.clone().spawn(async move {
|
|
||||||
let mut stdin_buffer = Vec::new();
|
let mut stdin_buffer = Vec::new();
|
||||||
let mut stdout_buffer = Vec::new();
|
let mut stdout_buffer = Vec::new();
|
||||||
let mut stderr_buffer = Vec::new();
|
let mut stderr_buffer = Vec::new();
|
||||||
|
@ -264,9 +264,18 @@ impl SshSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).detach();
|
});
|
||||||
|
|
||||||
cx.update(|cx| Self::new(incoming_rx, outgoing_tx, spawn_process_tx, Some(socket), cx))
|
cx.update(|cx| {
|
||||||
|
Self::new(
|
||||||
|
incoming_rx,
|
||||||
|
outgoing_tx,
|
||||||
|
spawn_process_tx,
|
||||||
|
Some(socket),
|
||||||
|
Some(io_task),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server(
|
pub fn server(
|
||||||
|
@ -275,7 +284,7 @@ impl SshSession {
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Arc<SshSession> {
|
) -> Arc<SshSession> {
|
||||||
let (tx, _rx) = mpsc::unbounded();
|
let (tx, _rx) = mpsc::unbounded();
|
||||||
Self::new(incoming_rx, outgoing_tx, tx, None, cx)
|
Self::new(incoming_rx, outgoing_tx, tx, None, None, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -293,6 +302,7 @@ impl SshSession {
|
||||||
client_to_server_tx,
|
client_to_server_tx,
|
||||||
tx.clone(),
|
tx.clone(),
|
||||||
None, // todo()
|
None, // todo()
|
||||||
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
@ -302,6 +312,7 @@ impl SshSession {
|
||||||
server_to_client_tx,
|
server_to_client_tx,
|
||||||
tx.clone(),
|
tx.clone(),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
@ -313,6 +324,7 @@ impl SshSession {
|
||||||
outgoing_tx: mpsc::UnboundedSender<Envelope>,
|
outgoing_tx: mpsc::UnboundedSender<Envelope>,
|
||||||
spawn_process_tx: mpsc::UnboundedSender<SpawnRequest>,
|
spawn_process_tx: mpsc::UnboundedSender<SpawnRequest>,
|
||||||
client_socket: Option<SshSocket>,
|
client_socket: Option<SshSocket>,
|
||||||
|
io_task: Option<Task<Result<()>>>,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Arc<SshSession> {
|
) -> Arc<SshSession> {
|
||||||
let this = Arc::new(Self {
|
let this = Arc::new(Self {
|
||||||
|
@ -322,13 +334,18 @@ impl SshSession {
|
||||||
spawn_process_tx,
|
spawn_process_tx,
|
||||||
client_socket,
|
client_socket,
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
|
_io_task: io_task,
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(|cx| {
|
cx.spawn(|cx| {
|
||||||
let this = this.clone();
|
let this = Arc::downgrade(&this);
|
||||||
async move {
|
async move {
|
||||||
let peer_id = PeerId { owner_id: 0, id: 0 };
|
let peer_id = PeerId { owner_id: 0, id: 0 };
|
||||||
while let Some(incoming) = incoming_rx.next().await {
|
while let Some(incoming) = incoming_rx.next().await {
|
||||||
|
let Some(this) = this.upgrade() else {
|
||||||
|
return anyhow::Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(request_id) = incoming.responding_to {
|
if let Some(request_id) = incoming.responding_to {
|
||||||
let request_id = MessageId(request_id);
|
let request_id = MessageId(request_id);
|
||||||
let sender = this.response_channels.lock().remove(&request_id);
|
let sender = this.response_channels.lock().remove(&request_id);
|
||||||
|
|
|
@ -10,11 +10,29 @@ use proto::{
|
||||||
error::ErrorExt as _, AnyTypedEnvelope, EntityMessage, Envelope, EnvelopedMessage,
|
error::ErrorExt as _, AnyTypedEnvelope, EntityMessage, Envelope, EnvelopedMessage,
|
||||||
RequestMessage, TypedEnvelope,
|
RequestMessage, TypedEnvelope,
|
||||||
};
|
};
|
||||||
use std::{any::TypeId, sync::Arc};
|
use std::{
|
||||||
|
any::TypeId,
|
||||||
|
sync::{Arc, Weak},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AnyProtoClient(Arc<dyn ProtoClient>);
|
pub struct AnyProtoClient(Arc<dyn ProtoClient>);
|
||||||
|
|
||||||
|
impl AnyProtoClient {
|
||||||
|
pub fn downgrade(&self) -> AnyWeakProtoClient {
|
||||||
|
AnyWeakProtoClient(Arc::downgrade(&self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AnyWeakProtoClient(Weak<dyn ProtoClient>);
|
||||||
|
|
||||||
|
impl AnyWeakProtoClient {
|
||||||
|
pub fn upgrade(&self) -> Option<AnyProtoClient> {
|
||||||
|
self.0.upgrade().map(AnyProtoClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ProtoClient: Send + Sync {
|
pub trait ProtoClient: Send + Sync {
|
||||||
fn request(
|
fn request(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -472,7 +472,7 @@ impl Worktree {
|
||||||
disconnected: false,
|
disconnected: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply updates to a separate snapshto in a background task, then
|
// Apply updates to a separate snapshot in a background task, then
|
||||||
// send them to a foreground task which updates the model.
|
// send them to a foreground task which updates the model.
|
||||||
cx.background_executor()
|
cx.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue