Fix ACP connection and thread leak (#35670)

When you switched away from an ACP thread, the `AcpThreadView` entity
(and thus thread, and subprocess) was leaked. This happened because we
were using `cx.processor` for the `list` state callback, which uses a
strong reference.

This PR changes the callback so that it holds a weak reference, and adds
some tests and assertions at various levels to make sure we don't
reintroduce the leak in the future.

Release Notes:

- N/A
This commit is contained in:
Agus Zubiaga 2025-08-05 19:10:51 -03:00 committed by GitHub
parent f27dc7dec7
commit b7469f5bc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 73 additions and 27 deletions

View file

@ -169,12 +169,13 @@ impl AcpThreadView {
let mention_set = mention_set.clone();
let list_state = ListState::new(
0,
gpui::ListAlignment::Bottom,
px(2048.0),
cx.processor({
move |this: &mut Self, index: usize, window, cx| {
let list_state = ListState::new(0, gpui::ListAlignment::Bottom, px(2048.0), {
let this = cx.entity().downgrade();
move |index: usize, window, cx| {
let Some(this) = this.upgrade() else {
return Empty.into_any();
};
this.update(cx, |this, cx| {
let Some((entry, len)) = this.thread().and_then(|thread| {
let entries = &thread.read(cx).entries();
Some((entries.get(index)?, entries.len()))
@ -182,9 +183,9 @@ impl AcpThreadView {
return Empty.into_any();
};
this.render_entry(index, len, entry, window, cx)
}
}),
);
})
}
});
Self {
agent: agent.clone(),
@ -2719,6 +2720,16 @@ mod tests {
use super::*;
#[gpui::test]
async fn test_drop(cx: &mut TestAppContext) {
init_test(cx);
let (thread_view, _cx) = setup_thread_view(StubAgentServer::default(), cx).await;
let weak_view = thread_view.downgrade();
drop(thread_view);
assert!(!weak_view.is_upgradable());
}
#[gpui::test]
async fn test_notification_for_stop_event(cx: &mut TestAppContext) {
init_test(cx);

View file

@ -970,13 +970,7 @@ impl AgentPanel {
)
});
this.set_active_view(
ActiveView::ExternalAgentThread {
thread_view: thread_view.clone(),
},
window,
cx,
);
this.set_active_view(ActiveView::ExternalAgentThread { thread_view }, window, cx);
})
})
.detach_and_log_err(cx);
@ -1477,6 +1471,7 @@ impl AgentPanel {
let current_is_special = current_is_history || current_is_config;
let new_is_special = new_is_history || new_is_config;
let mut old_acp_thread = None;
match &self.active_view {
ActiveView::Thread { thread, .. } => {
@ -1488,6 +1483,9 @@ impl AgentPanel {
});
}
}
ActiveView::ExternalAgentThread { thread_view } => {
old_acp_thread.replace(thread_view.downgrade());
}
_ => {}
}
@ -1518,6 +1516,11 @@ impl AgentPanel {
self.active_view = new_view;
}
debug_assert!(
old_acp_thread.map_or(true, |thread| !thread.is_upgradable()),
"AcpThreadView leaked"
);
self.acp_message_history.borrow_mut().reset_position();
self.focus_handle(cx).focus(window);