[collab] migrate away from Dispatchers.Main

Main does not provide read lock anymore, but it is required for Editor manipulations and some other components.
We migrate most usages to Dispatchers.EDT to get rid of exceptions and a couple to Dispatchers.UI where we are sure about the absence or RA requirements.

GitOrigin-RevId: a7de7a59cf0d9ffc5db8044e06be7d135297ba24
This commit is contained in:
Ivan Semenov
2025-03-13 16:38:25 +01:00
committed by intellij-monorepo-bot
parent 87d3826ba3
commit b71a604412
13 changed files with 42 additions and 30 deletions

View File

@@ -3,6 +3,8 @@ package com.intellij.collaboration.ui
import com.intellij.collaboration.async.launchNow
import com.intellij.collaboration.ui.CollaborationToolsUIUtil.COMPONENT_SCOPE_KEY
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.EdtImmediate
import com.intellij.platform.util.coroutines.childScope
import com.intellij.ui.ClientProperty
import com.intellij.util.containers.toArray
@@ -62,10 +64,10 @@ object ComponentListPanelFactory {
fun <T : Any> createVertical(cs: CoroutineScope, model: ListModel<T>, gap: Int = 0,
componentFactory: CoroutineScope.(T) -> JComponent): JPanel {
val panel = VerticalListPanel(gap)
cs.launchNow(Dispatchers.Main.immediate) {
cs.launchNow(Dispatchers.EdtImmediate) {
val listener = object : ListDataListener {
private fun addComponent(idx: Int, item: T) {
val scope = childScope()
val scope = childScope("Child component scope for $item")
val component = scope.componentFactory(item).also {
ClientProperty.put(it, COMPONENT_SCOPE_KEY, scope)
}
@@ -155,13 +157,13 @@ object ComponentListPanelFactory {
gap: Int = 0,
componentFactory: CoroutineScope.(T) -> JComponent
): JPanel {
val cs = parentCs.childScope(Dispatchers.Main)
val cs = parentCs.childScope("List panel", Dispatchers.EDT)
val panel = panelFactory(gap).apply(panelInitializer)
val currentList = LinkedList<T>()
fun addComponent(idx: Int, item: T) {
currentList.add(idx, item)
val scope = cs.childScope()
val scope = cs.childScope("Child component scope for $item")
val component = scope.componentFactory(item).also {
ClientProperty.put(it, COMPONENT_SCOPE_KEY, scope)
}

View File

@@ -17,6 +17,7 @@ import com.intellij.diff.requests.LoadingDiffRequest
import com.intellij.diff.requests.NoDiffRequest
import com.intellij.diff.tools.combined.*
import com.intellij.openapi.ListSelection
import com.intellij.openapi.application.EdtImmediate
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.runBlockingCancellable
import com.intellij.openapi.project.Project
@@ -47,7 +48,7 @@ object AsyncDiffRequestProcessorFactory {
cs.launchNow(CoroutineName("Code Review Diff UI")) {
diffVmFlow.collectScoped { vm ->
if (vm != null) {
withContext(Dispatchers.Main.immediate) {
withContext(Dispatchers.EdtImmediate) {
val context = createContext(vm)
try {
context.forEach { processor.putData(it) }
@@ -166,7 +167,7 @@ object AsyncDiffRequestProcessorFactory {
cs.launchNow(CoroutineName("Code Review Combined Diff UI")) {
reviewDiffVm.collectLatest { diffVm ->
if (diffVm != null) {
withContext(Dispatchers.Main.immediate) {
withContext(Dispatchers.EdtImmediate) {
val context = createContext(diffVm)
try {
context.forEach { processor.context.putData(it) }

View File

@@ -19,6 +19,7 @@ import com.intellij.diff.tools.util.base.DiffViewerBase
import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy
import com.intellij.openapi.ListSelection
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.application.EdtImmediate
import com.intellij.openapi.progress.checkCanceled
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.changes.actions.diff.PresentableGoToChangePopupAction
@@ -119,7 +120,7 @@ private suspend fun MutableDiffRequestProcessor.applyRequestUpdateable(
request: DiffRequest,
inRequestScope: suspend () -> Unit = {},
) {
withContext(Dispatchers.Main.immediate) {
withContext(Dispatchers.EdtImmediate) {
while (true) {
checkCanceled()
val needReload = supervisorScope {
@@ -175,7 +176,7 @@ private suspend fun MutableDiffRequestProcessor.handleScrolling(producer: DiffVi
private suspend fun DiffViewerBase.executeScroll(cmd: DiffViewerScrollRequest) {
val v = this
withContext(Dispatchers.Main.immediate) {
withContext(Dispatchers.EdtImmediate) {
viewerReadyFlow().first { it }
DiffViewerScrollRequestProcessor.scroll(v, cmd)
}

View File

@@ -15,6 +15,7 @@ import com.intellij.diff.tools.util.base.DiffViewerListener
import com.intellij.diff.tools.util.side.TwosideTextDiffViewer
import com.intellij.diff.util.DiffUserDataKeysEx
import com.intellij.diff.util.Side
import com.intellij.openapi.application.EDT
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.component1
@@ -168,7 +169,7 @@ suspend fun <M, I> DiffViewerBase.showCodeReview(
rendererFactory: RendererFactory<I, JComponent>,
): Nothing where I : CodeReviewInlayModel, M : CodeReviewEditorModel<I> {
val viewer = this
withContext(Dispatchers.Main + CoroutineName("Code review diff UI")) {
withContext(Dispatchers.EDT + CoroutineName("Code review diff UI")) {
supervisorScope {
var prevJob: Job? = null
viewerReadyFlow().collect {
@@ -269,7 +270,7 @@ internal fun <V : DiffViewerBase> V.viewerReadyFlow(): Flow<Boolean> {
awaitClose {
removeListener(listener)
}
}.withInitial(isViewerGood()).flowOn(Dispatchers.Main).distinctUntilChanged()
}.withInitial(isViewerGood()).flowOn(Dispatchers.EDT).distinctUntilChanged()
}
interface DiffMapped {

View File

@@ -11,6 +11,7 @@ import com.intellij.icons.AllIcons
import com.intellij.ide.lightEdit.LightEditCompatible
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.diff.DefaultFlagsProvider
import com.intellij.openapi.diff.LineStatusMarkerColorScheme
@@ -250,7 +251,7 @@ open class CodeReviewEditorGutterChangesRenderer(
}
suspend fun render(model: CodeReviewEditorGutterActionableChangesModel, editor: Editor) : Nothing {
withContext(Dispatchers.Main + CoroutineName("Editor gutter code review changes renderer")) {
withContext(Dispatchers.EDT + CoroutineName("Editor gutter code review changes renderer")) {
val disposable = Disposer.newDisposable("Editor code review changes renderer disposable")
editor.putUserData(CodeReviewEditorGutterActionableChangesModel.KEY, model)
try {

View File

@@ -6,6 +6,7 @@ import com.intellij.collaboration.async.launchNow
import com.intellij.diff.util.DiffDrawUtil
import com.intellij.diff.util.DiffUtil
import com.intellij.icons.AllIcons
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.editor.CustomFoldRegion
@@ -63,7 +64,7 @@ private constructor(
private val hoverHandler = HoverHandler(editor)
suspend fun launch(): Nothing {
withContext(Dispatchers.Main) {
withContext(Dispatchers.EDT) {
val areaDisposable = Disposer.newDisposable()
editor.gutterComponentEx.reserveLeftFreePaintersAreaWidth(areaDisposable, ICON_AREA_WIDTH)
editor.addEditorMouseListener(hoverHandler)
@@ -339,13 +340,13 @@ private constructor(
@ApiStatus.ScheduledForRemoval
@Deprecated("Use a suspending function", ReplaceWith("cs.launch { render(model, editor) }"))
fun setupIn(cs: CoroutineScope, model: CodeReviewEditorGutterControlsModel, editor: EditorEx) {
cs.launchNow(Dispatchers.Main) {
cs.launchNow(Dispatchers.EDT) {
render(model, editor)
}
}
suspend fun render(model: CodeReviewEditorGutterControlsModel, editor: EditorEx): Nothing {
withContext(Dispatchers.Main) {
withContext(Dispatchers.EDT) {
val renderer = CodeReviewEditorGutterControlsRenderer(model, editor)
val highlighter = editor.markupModel.addRangeHighlighter(null, 0, editor.document.textLength,
DiffDrawUtil.LST_LINE_MARKER_LAYER,

View File

@@ -5,6 +5,8 @@ import com.intellij.collaboration.async.collectScoped
import com.intellij.collaboration.async.launchNow
import com.intellij.collaboration.util.ExcludingApproximateChangedRangesShifter
import com.intellij.diff.util.Range
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.EdtImmediate
import com.intellij.openapi.editor.Document
import com.intellij.openapi.vcs.ex.DocumentTracker
import com.intellij.openapi.vcs.ex.LineStatusTrackerBase
@@ -29,7 +31,7 @@ class DocumentTrackerCodeReviewEditorGutterChangesModel(
reviewHeadContent: Flow<CharSequence?>,
reviewChangesRanges: Flow<List<Range>?>
) : CodeReviewEditorGutterChangesModel {
private val cs = parentCs.childScope(javaClass.name, Dispatchers.Main)
private val cs = parentCs.childScope(javaClass.name, Dispatchers.EDT)
private val _reviewRanges = MutableStateFlow<List<LstRange>?>(null)
override val reviewRanges: StateFlow<List<LstRange>?> = _reviewRanges.asStateFlow()
@@ -54,7 +56,7 @@ class DocumentTrackerCodeReviewEditorGutterChangesModel(
}
private suspend fun trackChanges(originalContent: CharSequence, reviewRanges: List<Range>) {
withContext(Dispatchers.Main.immediate) {
withContext(Dispatchers.EdtImmediate) {
val reviewHeadDocument = LineStatusTrackerBase.createVcsDocument(originalContent)
ReviewInEditorUtil.trackDocumentDiffSync(reviewHeadDocument, document) { trackerRanges ->
_postReviewRanges.value = trackerRanges

View File

@@ -6,6 +6,8 @@ import com.intellij.diff.util.Range
import com.intellij.openapi.actionSystem.Constraints
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.Separator
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.EdtImmediate
import com.intellij.openapi.diff.LineStatusMarkerColorScheme
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
@@ -71,7 +73,7 @@ object ReviewInEditorUtil {
}
suspend fun trackDocumentDiffSync(originalDocument: Document, currentDocument: Document, changesCollector: (List<Range>) -> Unit): Nothing {
withContext(Dispatchers.Main.immediate) {
withContext(Dispatchers.EdtImmediate) {
val documentTracker = DocumentTracker(originalDocument, currentDocument)
val trackerHandler = object : DocumentTracker.Handler {
override fun afterBulkRangeChange(isDirty: Boolean) {
@@ -98,7 +100,7 @@ object ReviewInEditorUtil {
* @throws IllegalStateException when the actions were not set up
*/
suspend fun showReviewToolbar(vm: CodeReviewInEditorViewModel, editor: Editor): Nothing {
withContext(Dispatchers.Main) {
withContext(Dispatchers.EDT) {
val toolbarActionGroup = DefaultActionGroup(
CodeReviewInEditorToolbarActionGroup(vm),
Separator.getInstance()

View File

@@ -9,9 +9,7 @@ import com.intellij.collaboration.ui.codereview.CodeReviewChatItemUIUtil
import com.intellij.collaboration.ui.layout.SizeRestrictedSingleComponentLayout
import com.intellij.collaboration.ui.util.DimensionRestrictions
import com.intellij.collaboration.util.HashingUtil
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.application.writeIntentReadAction
import com.intellij.openapi.application.*
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.editor.*
@@ -48,7 +46,7 @@ interface EditorMappedViewModel : EditorMapped {
private val LOG = Logger.getInstance("codereview.editor.inlays")
@Deprecated("Use the suspending function renderInlays for thread safety",
ReplaceWith("cs.launchNow(Dispatchers.Main) {\n" +
ReplaceWith("cs.launchNow(Dispatchers.EDT) {\n" +
" renderInlays(vmsFlow, HashingUtil.mappingStrategy(vmKeyExtractor), rendererFactory)\n" +
"}"))
fun <VM : EditorMapped> EditorEx.controlInlaysIn(
@@ -56,7 +54,7 @@ fun <VM : EditorMapped> EditorEx.controlInlaysIn(
vmsFlow: Flow<Collection<VM>>,
vmKeyExtractor: (VM) -> Any,
rendererFactory: CodeReviewRendererFactory<VM>
): Job = cs.launchNow(Dispatchers.Main) {
): Job = cs.launchNow(Dispatchers.EDT) {
doRenderInlays(vmsFlow, HashingUtil.mappingStrategy(vmKeyExtractor), rendererFactory)
}
@@ -79,7 +77,7 @@ private suspend fun <VM : EditorMapped> EditorEx.doRenderInlays(
rendererFactory: RendererFactory<VM, JComponent>
): Nothing {
val editor = this
withContext(Dispatchers.Main.immediate + CoroutineName("Editor component inlays for $this")) {
withContext(Dispatchers.EdtImmediate + CoroutineName("Editor component inlays for $this")) {
val inlaysCs = this
val controllersByVmKey = createCustomHashingStrategyMap<VM, Job>(vmHashingStrategy)
val positionKeeper = EditorScrollingPositionKeeper(editor)
@@ -120,7 +118,7 @@ private suspend fun <VM : EditorMapped> EditorEx.doRenderInlays(
}
private suspend fun <VM : EditorMapped> controlInlay(vm: VM, editor: EditorEx, rendererFactory: RendererFactory<VM, JComponent>): Nothing {
withContext(Dispatchers.Main.immediate + CoroutineName("Scope for code review component editor inlay for $vm")) {
withContext(Dispatchers.EdtImmediate + CoroutineName("Scope for code review component editor inlay for $vm")) {
var inlay: Inlay<*>? = null
try {
val lineFlow = vm.line

View File

@@ -2,6 +2,7 @@
package com.intellij.collaboration.ui.codereview.list
import com.intellij.collaboration.ui.util.JListHoveredRowMaterialiser
import com.intellij.openapi.application.UI
import com.intellij.ui.ScrollPaneFactory
import com.intellij.ui.ScrollingUtil
import com.intellij.ui.components.JBList
@@ -79,7 +80,7 @@ object ReviewListUtil {
list.model.addListDataListener(object : ListDataListener {
override fun intervalAdded(e: ListDataEvent) {
cs.launch(Dispatchers.Main) {
cs.launch(Dispatchers.UI) {
// yield to let list resize itself
yield()
checkScroll()

View File

@@ -8,6 +8,7 @@ import com.intellij.collaboration.ui.util.bindChildIn
import com.intellij.collaboration.ui.util.bindTextIn
import com.intellij.collaboration.ui.util.popup.awaitClose
import com.intellij.icons.AllIcons
import com.intellij.openapi.application.EDT
import com.intellij.openapi.editor.actions.IncrementalFindAction
import com.intellij.openapi.fileTypes.FileTypes
import com.intellij.openapi.project.Project
@@ -38,7 +39,7 @@ import javax.swing.JPanel
@ApiStatus.Internal
abstract class CodeReviewSubmitPopupHandler<VM : CodeReviewSubmitViewModel> {
suspend fun show(vm: VM, parentComponent: Component, above: Boolean = false) {
withContext(Dispatchers.Main) {
withContext(Dispatchers.EDT) {
val container = createPopupComponent(vm, errorPresenter)
val popup = createPopup(container)
@@ -53,7 +54,7 @@ abstract class CodeReviewSubmitPopupHandler<VM : CodeReviewSubmitViewModel> {
}
suspend fun show(vm: VM, project: Project) {
withContext(Dispatchers.Main) {
withContext(Dispatchers.EDT) {
val container = createPopupComponent(vm, errorPresenter)
val popup = createPopup(container)

View File

@@ -2,6 +2,7 @@
package com.intellij.collaboration.ui.html
import com.intellij.icons.AllIcons
import com.intellij.openapi.application.UI
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.util.containers.ComparatorUtil.min
import com.intellij.util.ui.JBImageToolkit
@@ -241,7 +242,7 @@ private class ImageLoader(
@OptIn(DelicateCoroutinesApi::class)
private fun requestImageAsync(loader: AsyncHtmlImageLoader, baseUrl: URL?, src: String): Job =
GlobalScope.launch(Dispatchers.Main + CoroutineName("HTML image requestor")) {
GlobalScope.launch(Dispatchers.UI + CoroutineName("HTML image requestor")) {
state = State.Loading()
val image = try {
loader.load(baseUrl, src)!!

View File

@@ -450,7 +450,7 @@ private typealias Block = CoroutineScope.() -> Unit
@ApiStatus.Internal
@Deprecated("It is much better to pass a proper scope where needed")
class ActivatableCoroutineScopeProvider(private val context: () -> CoroutineContext = { Dispatchers.Main })
class ActivatableCoroutineScopeProvider(private val context: () -> CoroutineContext = { Dispatchers.EDT })
: Activatable {
private var scope: CoroutineScope? = null