[threading] IJPL-205724: Process events quicker in SuvorovProgress

GitOrigin-RevId: 5a82578094ddbc8029d684a255fcf61c01209b2a
This commit is contained in:
Konstantin Nisht
2025-09-11 13:02:35 +00:00
committed by intellij-monorepo-bot
parent 1d5c3b9e76
commit 143af4abb4
2 changed files with 72 additions and 32 deletions
@@ -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<InputEvent> myInputEvents = new LinkedBlockingQueue<>();
private final LinkedBlockingQueue<InvocationEvent> 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<Object> myPingQueue;
private final @NotNull Consumer<? super InputEvent> myInputEventDispatcher;
private static final Object PING = new Object();
private static final Logger LOG = Logger.getInstance(EventStealer.class);
EventStealer(@NotNull Disposable parent, @NotNull Consumer<? super InputEvent> inputConsumer) {
this(parent, false, inputConsumer);
}
EventStealer(@NotNull Disposable parent, boolean installPingingQueue, @NotNull Consumer<? super InputEvent> 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();
@@ -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<SuvorovProgress>().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<SuvorovProgress>().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()