From 1834e31be47fdb062ff70817b042779720b267bd Mon Sep 17 00:00:00 2001 From: Ju8z <6261338+Ju8z@users.noreply.github.com> Date: Mon, 20 Apr 2026 02:20:11 +0200 Subject: [PATCH] Fixes an IntelliJ UI freeze when switching between external ACP agents such as OpenCode and Codex in the ProxyAI agent runtime selector. Problem: `closeSession()` was called synchronously from the runtime selector action path. Closing an active ACP session can block while closing stdio transport streams. In practice this can park the Swing UI thread in `AcpProtocolCore.close()` / `StdioTransport.close()`, leaving IntelliJ black-screened until the child ACP process is killed externally. A freeze report captured the EDT stuck under: - `AgentModelComboBoxAction.actionPerformed` - `ExternalAcpAgentService.closeSession` - `AcpProcessState.close` - `AcpProtocolCore.close` - `StdioTransport.close` - `FileDescriptor.close0` Changes: - Close removed ACP process states asynchronously on the service IO scope. - Remove the session setup mutex immediately after removing the state. - Stop the child ACP process before closing the protocol. - Force-kill the child process if it does not exit shortly after `destroy()`. - Log cleanup failures instead of propagating them back through the UI action path. Test-Locally - Reproduced locally with OpenCode -> Codex switching causing IntelliJ to freeze. - Verified the same close-order patch locally stopped the freeze by letting the ACP process terminate before protocol cleanup. --- .../codegpt/agent/external/AcpAgentService.kt | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/agent/external/AcpAgentService.kt b/src/main/kotlin/ee/carlrobert/codegpt/agent/external/AcpAgentService.kt index 57d5d871c..1edb88ea9 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/agent/external/AcpAgentService.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/agent/external/AcpAgentService.kt @@ -46,6 +46,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.* import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit @Service(Service.Level.PROJECT) class ExternalAcpAgentService(private val project: Project) { @@ -134,7 +135,11 @@ class ExternalAcpAgentService(private val project: Project) { } fun closeSession(sessionId: String) { - states.remove(sessionId)?.close() + states.remove(sessionId)?.let { state -> + scope.launch { + state.close() + } + } sessionSetupMutexes.remove(sessionId) } @@ -850,8 +855,23 @@ class ExternalAcpAgentService(private val project: Project) { } fun close() { - protocol.close() - process.destroy() + shutdownProcess() + runCatching { + protocol.close() + }.onFailure { throwable -> + logger.warn("Failed to close ACP protocol for ${preset.displayName}", throwable) + } + } + + private fun shutdownProcess() { + runCatching { + process.destroy() + if (!process.waitFor(500, TimeUnit.MILLISECONDS)) { + process.destroyForcibly() + } + }.onFailure { throwable -> + logger.warn("Failed to stop ACP process for ${preset.displayName}", throwable) + } } }