diff --git a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/UiThreadPriority.java b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/UiThreadPriority.java
new file mode 100644
index 000000000000..9457628e047c
--- /dev/null
+++ b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/UiThreadPriority.java
@@ -0,0 +1,103 @@
+// 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.platform.ide.bootstrap;
+
+import com.intellij.jna.JnaLoader;
+import com.intellij.util.system.OS;
+import com.sun.jna.Native;
+import com.sun.jna.NativeLibrary;
+import com.sun.jna.platform.win32.Kernel32;
+
+import javax.swing.*;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+final class UiThreadPriority {
+ @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
+ static void adjust() {
+ var os = OS.CURRENT;
+ SwingUtilities.invokeLater(() -> {
+ try {
+ switch (os) {
+ case Windows -> setWindowsThreadPriority();
+ case macOS -> setUserInteractiveQosClassForCurrentThread();
+ // Linux: raising thread priority is generally restricted by process limits; see https://man7.org/linux/man-pages/man7/sched.7.html
+ }
+ }
+ catch (Throwable t) {
+ var buf = new StringWriter();
+ t.printStackTrace(new PrintWriter(buf));
+ logError(buf.toString());
+ }
+ });
+ }
+
+ /**
+ * References:
+ * Scheduling Priorities,
+ * SetThreadPriority.
+ */
+ private static void setWindowsThreadPriority() {
+ var debug = Boolean.getBoolean("ide.set.qos.for.edt.debug");
+ var currentThread = Thread.currentThread();
+
+ int jvmPriorityBefore = currentThread.getPriority();
+ int nativePriorityBefore = debug && JnaLoader.isLoaded() ? Kernel32.INSTANCE.GetThreadPriority(Kernel32.INSTANCE.GetCurrentThread()) : -1;
+
+ currentThread.setPriority(Thread.MAX_PRIORITY); // the actual work
+
+ if (debug) {
+ int nativeThreadId = JnaLoader.isLoaded() ? Kernel32.INSTANCE.GetCurrentThreadId() : -1;
+ int nativePriorityAfter = JnaLoader.isLoaded() ? Kernel32.INSTANCE.GetThreadPriority(Kernel32.INSTANCE.GetCurrentThread()) : -1;
+ int jvmPriorityAfter = currentThread.getPriority();
+
+ /*
+ * Expected output:
+ *
+ * EDT JVM ID = 66, Native ID = 1234, Name = AWT-EventQueue-0
+ * Before: JVM Priority = 6, Native Priority = 0
+ * After: JVM Priority = 10, Native Priority = 2
+ */
+ logDebug(
+ "EDT JVM ID = " + currentThread.getId() + ", Native ID = " + nativeThreadId + ", Name = " + currentThread.getName() +
+ "\n Before: JVM Priority = " + jvmPriorityBefore + ", Native Priority = " + nativePriorityBefore +
+ "\n After: JVM Priority = " + jvmPriorityAfter + ", Native Priority = " + nativePriorityAfter
+ );
+ }
+ }
+
+ /**
+ * Sets the QoS class for a current thread.
+ * References:
+ * documentation archive,
+ * Darwin source code.
+ */
+ private static final class DarwinPThread {
+ static final int QOS_CLASS_USER_INTERACTIVE = 0x21;
+
+ static native int pthread_set_qos_class_self_np(int qosClass, int relPriority);
+ }
+
+ private static void setUserInteractiveQosClassForCurrentThread() {
+ if (!JnaLoader.isLoaded()) return;
+
+ var libc = NativeLibrary.getInstance(null);
+ Native.register(DarwinPThread.class, libc);
+ var ret = DarwinPThread.pthread_set_qos_class_self_np(DarwinPThread.QOS_CLASS_USER_INTERACTIVE, 0);
+ if (ret != 0) {
+ var currentThread = Thread.currentThread();
+ logError("Unable to set QoS class for thread #" + currentThread.getId() + " (" + currentThread.getName() + "): " + ret);
+ }
+ }
+
+ // loggers are not yet initialized
+ @SuppressWarnings("UseOfSystemOutOrSystemErr")
+ private static void logDebug(String message) {
+ System.out.println(UiThreadPriority.class.getSimpleName() + ": " + message);
+ }
+
+ // loggers are not yet initialized
+ @SuppressWarnings("UseOfSystemOutOrSystemErr")
+ private static void logError(String message) {
+ System.err.println(UiThreadPriority.class.getSimpleName() + ": " + message);
+ }
+}
diff --git a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt
index 6eedf1d9f04b..ce5360cc4c78 100644
--- a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt
+++ b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt
@@ -1,4 +1,4 @@
-// Copyright 2000-2024 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.platform.ide.bootstrap
import com.intellij.diagnostic.StartUpMeasurer
@@ -22,12 +22,10 @@ import com.intellij.ui.AppUIUtil
import com.intellij.ui.IconManager
import com.intellij.ui.icons.CoreIconManager
import com.intellij.ui.isWindowIconAlreadyExternallySet
-import com.intellij.ui.mac.setUserInteractiveQosClassForCurrentThread
import com.intellij.ui.scale.JBUIScale
import com.intellij.ui.updateAppWindowIcon
import com.intellij.util.ui.StartupUiUtil
import com.intellij.util.ui.accessibility.ScreenReader
-import com.sun.jna.platform.win32.Kernel32
import kotlinx.coroutines.*
import org.jetbrains.annotations.VisibleForTesting
import java.awt.Font
@@ -37,7 +35,6 @@ import java.awt.dnd.DragSource
import java.lang.invoke.MethodHandles
import javax.swing.JOptionPane
import javax.swing.RepaintManager
-import javax.swing.SwingUtilities
import javax.swing.UIManager
import kotlin.system.exitProcess
@@ -186,49 +183,12 @@ private suspend fun replaceIdeEventQueue(isHeadless: Boolean) {
}
span("set QoS for EDT") {
- if (setUserInteractiveQosForEdt) {
- SwingUtilities.invokeLater {
- when {
- SystemInfoRt.isMac -> setUserInteractiveQosClassForCurrentThread()
- SystemInfoRt.isWindows -> bumpEdtThreadPriority()
- }
- }
+ if (!isHeadless && setUserInteractiveQosForEdt) {
+ UiThreadPriority.adjust()
}
}
}
-/**
- * Sets maximum thread priority for EDT thread to process UI events regardless of Default dispatcher load or other background dispatchers.
- *
- * Note that thread priority is relative to process priority.
- * See [MSDN SetThreadPriority](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadpriority#remarks)
- */
-private fun bumpEdtThreadPriority() {
- val jvmThreadPriorityBefore = Thread.currentThread().priority
- val nativeThreadPriorityBefore = Kernel32.INSTANCE.GetThreadPriority(Kernel32.INSTANCE.GetCurrentThread())
-
- // The actual work
- Thread.currentThread().priority = Thread.MAX_PRIORITY
-
- val nativeThreadPriorityAfter = Kernel32.INSTANCE.GetThreadPriority(Kernel32.INSTANCE.GetCurrentThread())
- val jvmThreadPriorityAfter = Thread.currentThread().priority
-
- if (System.getProperty("ide.set.qos.for.edt.debug", "false").toBoolean()) {
- val nativeThreadId = Kernel32.INSTANCE.GetCurrentThreadId()
-
- /**
- * Expected output:
- *
- * EDT JVM Thread ID = 66, Native Thread ID 47108, Name = AWT-EventQueue-0
- * Before: JVM Thread Priority = 6, Native Thread Priority = 0
- * After: JVM Thread Priority = 10, Native Thread Priority = 2
- */
- println("EDT JVM Thread ID = ${Thread.currentThread().id}, Native Thread ID = ${nativeThreadId}, Name = ${Thread.currentThread().name}\n" +
- " Before: JVM Thread Priority = $jvmThreadPriorityBefore, Native Thread Priority = $nativeThreadPriorityBefore\n" +
- " After: JVM Thread Priority = $jvmThreadPriorityAfter, Native Thread Priority = $nativeThreadPriorityAfter")
- }
-}
-
/*
* The method should be called before `Toolkit#initAssistiveTechnologies`, which is called from `Toolkit#getDefaultToolkit`.
*/
@@ -306,4 +266,4 @@ internal fun CoroutineScope.scheduleUpdateFrameClassAndWindowIconAndPreloadSyste
WeakFocusStackManager.getInstance()
}
}
-}
\ No newline at end of file
+}
diff --git a/platform/platform-impl/bootstrap/src/com/intellij/ui/mac/EdtQoSDarwinFixer.kt b/platform/platform-impl/bootstrap/src/com/intellij/ui/mac/EdtQoSDarwinFixer.kt
deleted file mode 100644
index 91fdfc2813ca..000000000000
--- a/platform/platform-impl/bootstrap/src/com/intellij/ui/mac/EdtQoSDarwinFixer.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
-package com.intellij.ui.mac
-
-import com.intellij.openapi.diagnostic.logger
-import com.sun.jna.Library
-import com.sun.jna.Native
-
-/**
- * The set of QoS classes supported by macOS.
- *
- * Original location:
- * - `/Library/Developer/CommandLineTools/SDKs/Mac0SX$VERSION$.sdk/usr/include/sys/qos.h`
- * - https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/include/sys/qos.h#L130-L143
- */
-private enum class QosClass(internal val priority: Int) {
- UserInteractive(0x21),
- UserInitiated(0x19),
- Default(0x15),
- Utility(0x11),
- Background(0x09),
- Unspecified(0x00),
-}
-
-/**
- * Bridging JNA interface for `/usr/lib/system/libsystem_pthread.dylib` library.
- */
-private interface DarwinPThread : Library {
- companion object {
- val log = logger()
- val instance: DarwinPThread by lazy {
- // Because this library is reexported by `libSystem.B.dylib` which is linked automatically by macOS,
- // we don't need to load any .dylib.
- Native.load(DarwinPThread::class.java)
- }
- }
-
- /**
- * Sets the QoS class for a current thread.
- *
- * @see Darwin libpthread documentation
- */
- fun pthread_set_qos_class_self_np(qosClass: Int, relPriority: Int): Int
-}
-
-internal fun setUserInteractiveQosClassForCurrentThread() {
- runCatching {
- val qos = QosClass.UserInteractive
- val ret = DarwinPThread.instance.pthread_set_qos_class_self_np(qos.priority, 0)
- if (ret != 0) {
- val self = Thread.currentThread()
- DarwinPThread.log.warn("Unable to set QoS class ${qos.name} for thread #${self.id} (${self.name}) (error code: ${ret})")
- }
- }.onFailure {
- DarwinPThread.log.error(it)
- }
-}
\ No newline at end of file