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