From 143af4abb4debb1755fc6c82994064cfc84d7a78 Mon Sep 17 00:00:00 2001 From: Konstantin Nisht Date: Thu, 11 Sep 2025 12:04:36 +0200 Subject: [PATCH] [threading] IJPL-205724: Process events quicker in SuvorovProgress GitOrigin-RevId: 5a82578094ddbc8029d684a255fcf61c01209b2a --- .../openapi/progress/util/EventStealer.java | 40 ++++++++++++ .../openapi/progress/util/SuvorovProgress.kt | 64 +++++++++---------- 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/EventStealer.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/EventStealer.java index 668cd5e14fe3..129909d13af4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/EventStealer.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/EventStealer.java @@ -5,6 +5,7 @@ import com.intellij.ide.IdeEventQueue; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ThreadingSupport; import com.intellij.openapi.application.impl.InternalThreading; +import com.intellij.openapi.diagnostic.Logger; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import sun.awt.SunToolkit; @@ -24,25 +25,50 @@ import java.util.function.Consumer; public class EventStealer { private final LinkedBlockingQueue myInputEvents = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue myInvocationEvents = new LinkedBlockingQueue<>(); + // The ping funcitonality is needed for cases when EDT simply waits for some process that might occasionally send events to EventStealer. + // The EDT needs to react to these events ASAP, but not too eagerly, as we'd like to avoid spinning on EDT + // So the EDT can simply block on the ping queue with the necessary timeout. It will be woken up as soon as any interesting event appears in the event queue. + private final LinkedBlockingQueue myPingQueue; private final @NotNull Consumer myInputEventDispatcher; + private static final Object PING = new Object(); + private static final Logger LOG = Logger.getInstance(EventStealer.class); + EventStealer(@NotNull Disposable parent, @NotNull Consumer inputConsumer) { + this(parent, false, inputConsumer); + } + + EventStealer(@NotNull Disposable parent, boolean installPingingQueue, @NotNull Consumer inputConsumer) { myInputEventDispatcher = inputConsumer; IdeEventQueue.getInstance().addPostEventListener(event -> { if (event instanceof MouseEvent me) { myInputEvents.offer(me); + ping(); return true; } else if (event instanceof KeyEvent ke && event.getID() != KeyEvent.KEY_TYPED) { myInputEvents.offer(ke); + ping(); return true; } if (event instanceof InvocationEvent ie && isUrgentInvocationEvent(event)) { myInvocationEvents.offer(ie); + ping(); return true; } return false; }, parent); + if (installPingingQueue) { + myPingQueue = new LinkedBlockingQueue<>(1); + } else { + myPingQueue = null; + } + } + + private void ping() { + if (myPingQueue != null) { + myPingQueue.offer(PING); + } } @@ -90,6 +116,20 @@ public class EventStealer { } } + @SuppressWarnings("SameParameterValue") + void waitForPing(int timeoutMs) { + if (myPingQueue == null) { + LOG.error("Ping queue must be installed"); + return; + } + try { + myPingQueue.poll(timeoutMs, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) { + // simply resume + } + } + void dispatchAllExistingEvents() { while (true) { InvocationEvent event = myInvocationEvents.poll(); diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/SuvorovProgress.kt b/platform/platform-impl/src/com/intellij/openapi/progress/util/SuvorovProgress.kt index 92ff44e4c8bb..f57cc75ce803 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/SuvorovProgress.kt +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/SuvorovProgress.kt @@ -134,39 +134,38 @@ object SuvorovProgress { val niceOverlay = NiceOverlayUi(rootPane, false) val disposable = Disposer.newDisposable() - val stealer = PotemkinProgress.startStealingInputEvents( - { event -> - var dumpThreads = false - if (event is MouseEvent && event.id == MouseEvent.MOUSE_CLICKED) { - event.consume() - val reaction = niceOverlay.mouseClicked(event.point) - when (reaction) { - NiceOverlayUi.ClickOutcome.DUMP_THREADS -> dumpThreads = true - NiceOverlayUi.ClickOutcome.CLOSED, NiceOverlayUi.ClickOutcome.NOTHING -> Unit - } + val stealer = EventStealer(disposable, true) { event -> + var dumpThreads = false + if (event is MouseEvent && event.id == MouseEvent.MOUSE_CLICKED) { + event.consume() + val reaction = niceOverlay.mouseClicked(event.point) + when (reaction) { + NiceOverlayUi.ClickOutcome.DUMP_THREADS -> dumpThreads = true + NiceOverlayUi.ClickOutcome.CLOSED, NiceOverlayUi.ClickOutcome.NOTHING -> Unit } - if (event is MouseEvent && event.id == MouseEvent.MOUSE_MOVED) { - event.consume() - niceOverlay.mouseMoved(event.point) - } - if (event is KeyEvent && niceOverlay.dumpThreadsButtonShortcut == KeyStrokeAdapter.getDefaultKeyStroke(event)?.let { KeyboardShortcut(it, null) }) { - event.consume() - dumpThreads = true - } - if (dumpThreads) { - ApplicationManager.getApplication().executeOnPooledThread(Runnable { - val dumpFile = PerformanceWatcher.getInstance().dumpThreads("freeze-popup", true, false) - if (dumpFile != null) { - if (Files.exists(dumpFile)) { - RevealFileAction.openFile(dumpFile) - } - else { - getLogger().error { "Failed to dump threads to $dumpFile" } - } + } + if (event is MouseEvent && event.id == MouseEvent.MOUSE_MOVED) { + event.consume() + niceOverlay.mouseMoved(event.point) + } + if (event is KeyEvent && niceOverlay.dumpThreadsButtonShortcut == KeyStrokeAdapter.getDefaultKeyStroke(event)?.let { KeyboardShortcut(it, null) }) { + event.consume() + dumpThreads = true + } + if (dumpThreads) { + ApplicationManager.getApplication().executeOnPooledThread(Runnable { + val dumpFile = PerformanceWatcher.getInstance().dumpThreads("freeze-popup", true, false) + if (dumpFile != null) { + if (Files.exists(dumpFile)) { + RevealFileAction.openFile(dumpFile) } - }) - } - }, disposable) + else { + getLogger().error { "Failed to dump threads to $dumpFile" } + } + } + }) + } + } repostAllEvents() var oldTimestamp = System.currentTimeMillis() @@ -179,7 +178,8 @@ object SuvorovProgress { oldTimestamp = newTimestamp niceOverlay.redrawMainComponent() } - stealer.dispatchEvents(10) + stealer.dispatchEvents(0) + stealer.waitForPing(10) } else { niceOverlay.redrawMainComponent()