From 28097b60e1120ffce8e0bee0160018c1b68c9c23 Mon Sep 17 00:00:00 2001 From: Konstantin Nisht Date: Mon, 17 Nov 2025 10:13:19 +0100 Subject: [PATCH] [threading] IJPL-218810: Always run old VFS listeners on EDT for programmatic reparse GitOrigin-RevId: 5e2ed3f31096b1ce2abab22ecb7ceabc6880ee34 --- .../intellij/util/FileContentUtilCore.java | 24 ++++++++++++--- .../TransferredWriteActionService.kt | 2 +- .../com/intellij/psi/impl/PsiManagerImpl.java | 2 +- .../impl/TransferredWriteActionServiceImpl.kt | 1 + .../impl/FileDocumentManagerImpl.java | 2 +- .../vfs/newvfs/impl/VfsThreadingUtil.java | 2 +- .../src/META-INF/PlatformExtensions.xml | 2 +- .../vfs/newvfs/persistent/VfsRefreshTest.kt | 29 +++++++++++++++++++ .../impl/DocumentCommitOnBackgroundTest.kt | 2 +- 9 files changed, 56 insertions(+), 10 deletions(-) rename platform/{core-impl/src/com/intellij/openapi/application/impl => core-api/src/com/intellij/util/concurrency}/TransferredWriteActionService.kt (96%) diff --git a/platform/core-api/src/com/intellij/util/FileContentUtilCore.java b/platform/core-api/src/com/intellij/util/FileContentUtilCore.java index ae9ffed405ed..413223db53ce 100644 --- a/platform/core-api/src/com/intellij/util/FileContentUtilCore.java +++ b/platform/core-api/src/com/intellij/util/FileContentUtilCore.java @@ -1,6 +1,7 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.util; +import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; @@ -8,6 +9,8 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.BulkFileListenerBackgroundable; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent; +import com.intellij.util.concurrency.TransferredWriteActionService; +import com.intellij.util.ui.EDT; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -33,7 +36,8 @@ public final class FileContentUtilCore { * @param files the files to reparse. */ public static void reparseFiles(@NotNull Collection files) { - ApplicationManager.getApplication().runWriteAction(() -> { + Application application = ApplicationManager.getApplication(); + application.runWriteAction(() -> { // files must be processed under one write action to prevent firing event for invalid files. Set events = new HashSet<>(); for (VirtualFile file : files) { @@ -45,9 +49,21 @@ public final class FileContentUtilCore { BulkFileListener publisher = ApplicationManager.getApplication().getMessageBus().syncPublisher(VirtualFileManager.VFS_CHANGES); BulkFileListenerBackgroundable publisherBackgroundable = ApplicationManager.getApplication().getMessageBus().syncPublisher(VirtualFileManager.VFS_CHANGES_BG); List eventList = Collections.unmodifiableList(new ArrayList<>(events)); - publisher.before(eventList); + if (EDT.isCurrentThreadEdt()) { + publisher.before(eventList); + } else { + application.getService(TransferredWriteActionService.class).runOnEdtWithTransferredWriteActionAndWait(() -> { + publisher.before(eventList); + }); + } publisherBackgroundable.before(eventList); - publisher.after(eventList); + if (EDT.isCurrentThreadEdt()) { + publisher.after(eventList); + } else { + application.getService(TransferredWriteActionService.class).runOnEdtWithTransferredWriteActionAndWait(() -> { + publisher.after(eventList); + }); + } publisherBackgroundable.after(eventList); ForcefulReparseModificationTracker.increment(); diff --git a/platform/core-impl/src/com/intellij/openapi/application/impl/TransferredWriteActionService.kt b/platform/core-api/src/com/intellij/util/concurrency/TransferredWriteActionService.kt similarity index 96% rename from platform/core-impl/src/com/intellij/openapi/application/impl/TransferredWriteActionService.kt rename to platform/core-api/src/com/intellij/util/concurrency/TransferredWriteActionService.kt index be6dcece9ca5..ee464c8e315f 100644 --- a/platform/core-impl/src/com/intellij/openapi/application/impl/TransferredWriteActionService.kt +++ b/platform/core-api/src/com/intellij/util/concurrency/TransferredWriteActionService.kt @@ -1,5 +1,5 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.openapi.application.impl +package com.intellij.util.concurrency import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import com.intellij.util.concurrency.annotations.RequiresWriteLock diff --git a/platform/core-impl/src/com/intellij/psi/impl/PsiManagerImpl.java b/platform/core-impl/src/com/intellij/psi/impl/PsiManagerImpl.java index b431c3015ab8..767f545495a0 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiManagerImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiManagerImpl.java @@ -6,7 +6,6 @@ import com.intellij.lang.PsiBuilderFactory; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.impl.TransferredWriteActionService; import com.intellij.openapi.diagnostic.ControlFlowException; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; @@ -28,6 +27,7 @@ import com.intellij.psi.impl.file.impl.FileManagerEx; import com.intellij.psi.impl.file.impl.FileManagerImpl; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.serviceContainer.NonInjectable; +import com.intellij.util.concurrency.TransferredWriteActionService; import com.intellij.util.concurrency.annotations.RequiresReadLock; import com.intellij.util.concurrency.annotations.RequiresWriteLock; import com.intellij.util.containers.ContainerUtil; diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/TransferredWriteActionServiceImpl.kt b/platform/platform-impl/src/com/intellij/openapi/application/impl/TransferredWriteActionServiceImpl.kt index 73e316b98bb9..b18610c9de27 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/TransferredWriteActionServiceImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/TransferredWriteActionServiceImpl.kt @@ -1,6 +1,7 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.openapi.application.impl +import com.intellij.util.concurrency.TransferredWriteActionService import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java index 0abc394c30d2..ae81a6818f2c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java @@ -13,7 +13,6 @@ import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.application.TransactionGuardImpl; import com.intellij.openapi.application.impl.InternalThreading; -import com.intellij.openapi.application.impl.TransferredWriteActionService; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.UndoConfirmationPolicy; import com.intellij.openapi.diagnostic.Logger; @@ -63,6 +62,7 @@ import com.intellij.ui.UIBundle; import com.intellij.ui.components.JBScrollPane; import com.intellij.util.*; import com.intellij.util.concurrency.ThreadingAssertions; +import com.intellij.util.concurrency.TransferredWriteActionService; import com.intellij.util.concurrency.annotations.RequiresBackgroundThread; import com.intellij.util.concurrency.annotations.RequiresEdt; import com.intellij.util.containers.ContainerUtil; diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsThreadingUtil.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsThreadingUtil.java index 5255bdcd2f6d..46172f302067 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsThreadingUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VfsThreadingUtil.java @@ -2,8 +2,8 @@ package com.intellij.openapi.vfs.newvfs.impl; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.impl.TransferredWriteActionService; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.util.concurrency.TransferredWriteActionService; import com.intellij.util.ui.EDT; import org.jetbrains.annotations.ApiStatus; diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index f1ea2e073061..8ef0ab72ee33 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -11,7 +11,7 @@ /> diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/VfsRefreshTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/VfsRefreshTest.kt index f6a044f0d31d..088fce169acf 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/VfsRefreshTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/VfsRefreshTest.kt @@ -21,6 +21,7 @@ import com.intellij.testFramework.common.timeoutRunBlocking import com.intellij.testFramework.junit5.RegistryKey import com.intellij.testFramework.junit5.TestApplication import com.intellij.testFramework.junit5.TestDisposable +import com.intellij.util.FileContentUtilCore import com.intellij.util.application import com.intellij.util.io.delete import com.intellij.util.io.write @@ -244,4 +245,32 @@ class VfsRefreshTest { } // if this test terminates, there was no hanging } + + @Test + fun `listeners in programmatic reparse run on EDT`(@TestDisposable disposable: Disposable): Unit = timeoutRunBlocking { + val file = createTempFile() + val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(file)!! + val counter = AtomicInteger(0) + + writeAction { + virtualFile.writeText("42") + } + + application.messageBus.connect(disposable).subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener { + override fun before(events: List) { + assertThat(EDT.isCurrentThreadEdt()).isTrue + counter.incrementAndGet() + } + + override fun after(events: List) { + assertThat(EDT.isCurrentThreadEdt()).isTrue + counter.incrementAndGet() + } + }) + + backgroundWriteAction { + FileContentUtilCore.reparseFiles(virtualFile) + } + assertThat(counter.get()).isEqualTo(2) + } } \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/psi/impl/DocumentCommitOnBackgroundTest.kt b/platform/platform-tests/testSrc/com/intellij/psi/impl/DocumentCommitOnBackgroundTest.kt index 1667792e49a1..4f9dc2ed8e90 100644 --- a/platform/platform-tests/testSrc/com/intellij/psi/impl/DocumentCommitOnBackgroundTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/psi/impl/DocumentCommitOnBackgroundTest.kt @@ -3,7 +3,6 @@ package com.intellij.psi.impl import com.intellij.openapi.Disposable import com.intellij.openapi.application.backgroundWriteAction -import com.intellij.openapi.application.impl.TransferredWriteActionService import com.intellij.openapi.application.invokeAndWaitIfNeeded import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.CommandProcessor @@ -28,6 +27,7 @@ import com.intellij.testFramework.junit5.fixture.projectFixture import com.intellij.testFramework.junit5.fixture.psiFileFixture import com.intellij.testFramework.junit5.fixture.sourceRootFixture import com.intellij.util.application +import com.intellij.util.concurrency.TransferredWriteActionService import com.intellij.util.ui.EDT import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.DynamicTest