[terminal] IJPL-215159 Try to fix race of terminal editors disposing on project closing

Editors can be disposed before the terminal tool window tab is hidden. Because terminal coroutine scope is bound to the project and is canceled concurrently with other things, like tool window tabs disposing.
Let's try to hide the tool window tab right when terminal coroutine scope is canceled.

GitOrigin-RevId: 47123bb56714c0717e4e528a8e227bbb76ebbff9
This commit is contained in:
Konstantin Hudyakov
2025-11-21 10:03:34 +00:00
committed by intellij-monorepo-bot
parent c50cb61de5
commit 30fcd456ee
@@ -46,6 +46,7 @@ import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
import org.jetbrains.plugins.terminal.fus.TerminalOpeningWay
import org.jetbrains.plugins.terminal.fus.TerminalStartupFusInfo
import org.jetbrains.plugins.terminal.util.fireListenersAndLogAllExceptions
import java.lang.ref.WeakReference
import kotlin.time.Duration.Companion.seconds
internal class TerminalToolWindowTabsManagerImpl(
@@ -129,6 +130,7 @@ internal class TerminalToolWindowTabsManagerImpl(
return tab
}
@OptIn(AwaitCancellationAndInvoke::class)
private fun doCreateTab(terminal: TerminalView): TerminalToolWindowTab {
val panel = TerminalToolWindowPanel()
panel.setContent(terminal.component)
@@ -163,6 +165,21 @@ internal class TerminalToolWindowTabsManagerImpl(
}
}
// In case of project closing there can be a race between terminal coroutine scope cancellation
// and removing the content from the tool window.
// If the terminal coroutine scope is canceled before the content is removed, the editor may be shown green for a moment.
// The ideal solution is to cancel the terminal scope only after the content is removed.
// However, the terminal component has a broader lifecycle than the tool window tab (to be able to detach it),
// so it can't be tied to the content removal directly.
// Let's try to hide the tool window tab right on terminal scope cancellation,
// but do not store strong reference to the content to avoid leaks.
val tabReference = WeakReference(content)
terminal.coroutineScope.awaitCancellationAndInvoke(Dispatchers.EDT) {
val content = tabReference.get() ?: return@awaitCancellationAndInvoke
val manager = content.manager ?: return@awaitCancellationAndInvoke
manager.removeContent(content, true)
}
return TerminalToolWindowTabImpl(terminal, content)
}