[platform] refactoring EDT priority setting code

(extracting; skipping in headless mode; using appropriate logging; checking JNA availability)

GitOrigin-RevId: 119030109c2a867685cf5ca8ca68ab3e35623a26
This commit is contained in:
Roman Shevchenko
2025-01-08 14:34:18 +00:00
committed by intellij-monorepo-bot
parent 6bebbdbe1d
commit bac2bc9232
3 changed files with 107 additions and 100 deletions
@@ -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:
* <a href="https://learn.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities">Scheduling Priorities</a>,
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadpriority">SetThreadPriority</a>.
*/
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:
* <a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html">documentation archive</a>,
* <a href="https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/include/pthread/qos.h#L118-L156">Darwin source code</a>.
*/
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);
}
}
@@ -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()
}
}
}
}
@@ -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<DarwinPThread>()
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 <a href="https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/include/pthread/qos.h#L118-L156">Darwin libpthread documentation</a>
*/
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)
}
}