mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-06 06:21:16 +07:00
[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:
committed by
intellij-monorepo-bot
parent
6bebbdbe1d
commit
bac2bc9232
+103
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user