[threading] IJPL-218810: Always run old VFS listeners on EDT for programmatic reparse

GitOrigin-RevId: 5e2ed3f31096b1ce2abab22ecb7ceabc6880ee34
This commit is contained in:
Konstantin Nisht
2025-11-17 11:31:53 +00:00
committed by intellij-monorepo-bot
parent 81a8fedbfa
commit 28097b60e1
9 changed files with 56 additions and 10 deletions
@@ -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<? extends VirtualFile> 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<VFilePropertyChangeEvent> 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<VFileEvent> 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();
@@ -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
@@ -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;
@@ -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
@@ -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;
@@ -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;
@@ -11,7 +11,7 @@
/>
<applicationService
serviceInterface="com.intellij.openapi.application.impl.TransferredWriteActionService"
serviceInterface="com.intellij.util.concurrency.TransferredWriteActionService"
serviceImplementation="com.intellij.openapi.application.impl.TransferredWriteActionServiceImpl"
/>
@@ -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<VFileEvent>) {
assertThat(EDT.isCurrentThreadEdt()).isTrue
counter.incrementAndGet()
}
override fun after(events: List<VFileEvent>) {
assertThat(EDT.isCurrentThreadEdt()).isTrue
counter.incrementAndGet()
}
})
backgroundWriteAction {
FileContentUtilCore.reparseFiles(virtualFile)
}
assertThat(counter.get()).isEqualTo(2)
}
}
@@ -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