Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues with High CPU Usage on background #3278

Merged
merged 19 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
68bca9c
Add basic thread sleeper
marticliment Feb 7, 2025
5fcde1a
For log lines displayed on the debugger, print the calling method name
marticliment Feb 8, 2025
5b7ac71
Sleep DWM and XAML process when app is in background, and resume them…
marticliment Feb 8, 2025
5056bc7
Fix packages showing greyed out until mouseover event
marticliment Feb 8, 2025
a56af41
Fix quitting UniGetUI creating a nonexistent process
marticliment Feb 8, 2025
18188e7
Fix efficiency mode being always enabled, fix icon tray not showing w…
marticliment Feb 8, 2025
5686e7c
Cache thread Id and startAdress so they don't have to be loaded each …
marticliment Feb 8, 2025
035c5eb
Merge branch 'main' into cpu-usage-fix
marticliment Feb 8, 2025
b7eea32
Add a kill switch to the Background CPU optimizer
marticliment Feb 8, 2025
aa6b40b
Merge branch 'cpu-usage-fix' of https://github.com/marticliment/UniGe…
marticliment Feb 8, 2025
1d9e025
.NET Tools that can only be installed globally should be installed gl…
marticliment Feb 8, 2025
104de8d
Fix a hard crash happening when UniGetUI in portable mode does not ha…
marticliment Feb 8, 2025
1d5007b
Add a subtitle to the titlebar to show various info, such as "run as …
marticliment Feb 8, 2025
9e3482c
Bootstrap vcpkg the first time it gets updated.
marticliment Feb 8, 2025
671fabf
Merge branch 'main' into cpu-usage-fix
marticliment Feb 8, 2025
8d006ae
Merge branch 'main' into cpu-usage-fix
marticliment Feb 8, 2025
71de91d
Merge branch 'main' into cpu-usage-fix
marticliment Feb 9, 2025
e826281
Remove somehow duplicated function
marticliment Feb 9, 2025
4959a2c
Merge branch 'main' into cpu-usage-fix
marticliment Feb 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions src/UniGetUI.Core.Logger/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,64 +7,64 @@ public static class Logger
private static readonly List<LogEntry> LogContents = [];

// String parameter log functions
public static void ImportantInfo(string s)
public static void ImportantInfo(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(s);
Diagnostics.Debug.WriteLine($"[{caller}] " + s);
LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Success));
}

public static void Debug(string s)
public static void Debug(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(s);
Diagnostics.Debug.WriteLine($"[{caller}] " + s);
LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Debug));
}

public static void Info(string s)
public static void Info(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(s);
Diagnostics.Debug.WriteLine($"[{caller}] " + s);
LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Info));
}

public static void Warn(string s)
public static void Warn(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(s);
Diagnostics.Debug.WriteLine($"[{caller}] " + s);
LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Warning));
}

public static void Error(string s)
public static void Error(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(s);
Diagnostics.Debug.WriteLine($"[{caller}] " + s);
LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Error));
}

// Exception parameter log functions
public static void ImportantInfo(Exception e)
public static void ImportantInfo(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(e.ToString());
Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString());
LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Success));
}

public static void Debug(Exception e)
public static void Debug(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(e.ToString());
Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString());
LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Debug));
}

public static void Info(Exception e)
public static void Info(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(e.ToString());
Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString());
LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Info));
}

public static void Warn(Exception e)
public static void Warn(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(e.ToString());
Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString());
LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Warning));
}

public static void Error(Exception e)
public static void Error(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "")
{
Diagnostics.Debug.WriteLine(e.ToString());
Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString());
LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Error));
}

Expand Down
181 changes: 181 additions & 0 deletions src/UniGetUI.Core.Tools/DWMThreadHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;

namespace UniGetUI;

public class DWMThreadHelper
{
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass,
ref IntPtr ThreadInformation, int ThreadInformationLength, out int ReturnLength);

[DllImport("kernel32.dll")]
private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);

[DllImport("kernel32.dll")]
private static extern uint SuspendThread(IntPtr hThread);

[DllImport("kernel32.dll")]
private static extern uint ResumeThread(IntPtr hThread);

[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);

[Flags]
private enum ThreadAccess : uint
{
QUERY_INFORMATION = 0x0040,
SUSPEND_RESUME = 0x0002
}

private const int ThreadQuerySetWin32StartAddress = 9;
private static bool DWM_IsSuspended;
private static bool XAML_IsSuspended;

private static int? DWMThreadId;
private static IntPtr? DWMThreadAdress;
private static int? XAMLThreadId;
private static IntPtr? XAMLThreadAdress;

public static void ChangeState_DWM(bool suspend)
{
if (Settings.Get("DisableDMWThreadOptimizations")) return;

if (DWM_IsSuspended && suspend)
{
Logger.Debug("DWM Thread was already suspended"); return;
}
else if (!DWM_IsSuspended && !suspend)
{
Logger.Debug("DWM Thread was already running"); return;
}

DWMThreadAdress ??= GetTargetFunctionAddress("dwmcorei.dll", 0x54F70);
if (DWMThreadAdress is null)
{
Logger.Error("Failed to resolve thread start adress."); return;
}

ChangeState(suspend, (IntPtr)DWMThreadAdress, ref DWM_IsSuspended, ref DWMThreadId, "DWM");
}

public static void ChangeState_XAML(bool suspend)
{
if (Settings.Get("DisableDMWThreadOptimizations")) return;

if (XAML_IsSuspended && suspend)
{
Logger.Debug("XAML Thread was already suspended"); return;
}
else if (!XAML_IsSuspended && !suspend)
{
Logger.Debug("XAML Thread was already running"); return;
}

// The reported offset on ProcessExplorer seems to be missing 0x6280 somehow
// 0x54F70 + 0x6280 = 0x5B1F0
XAMLThreadAdress ??= GetTargetFunctionAddress("Microsoft.UI.Xaml.dll", 0x5B1F0);
if (XAMLThreadAdress is null)
{
Logger.Error("Failed to resolve thread start adress."); return;
}

ChangeState(suspend, (IntPtr)XAMLThreadAdress, ref XAML_IsSuspended, ref XAMLThreadId, "XAML");
}

private static IntPtr GetThreadStartAdress(int threadId)
{
IntPtr hThread = OpenThread(ThreadAccess.QUERY_INFORMATION | ThreadAccess.SUSPEND_RESUME, false, (uint)threadId);
if (hThread == IntPtr.Zero) return IntPtr.Zero;

IntPtr adress = 0x00;
int status = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, ref adress, Marshal.SizeOf(typeof(IntPtr)), out _);
if(status != 0) Logger.Warn($"NtQueryInformationThread returned non-zero status code 0x{(uint)status:X}");
CloseHandle(hThread);
return adress;
}

private static void ChangeState(bool suspend, IntPtr threadAdress, ref bool IsSuspended, ref int? threadId,
string loggerName, bool canRetry = true)
{
if (threadId is null)
{
foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
{
if (GetThreadStartAdress(thread.Id) == threadAdress)
{
threadId = thread.Id;
Logger.Info($"Thread with Id {threadId} was assigned as {loggerName} thread");
}
}
}

if (threadId is null)
{
Logger.Error($"No thread matching {loggerName} with start adress {threadAdress:X} was found");
return;
}

IntPtr hThread = OpenThread(ThreadAccess.QUERY_INFORMATION | ThreadAccess.SUSPEND_RESUME, false, (uint)threadId);
if (hThread == IntPtr.Zero)
{ // When the thread cannot be opened
if (canRetry)
{
threadId = null; // On first try, reset argument threadId so it does get loaded again.
ChangeState(suspend, threadAdress, ref IsSuspended, ref threadId, loggerName, false);
return;
}
// The threadId was already reloaded
Logger.Warn($"Thread with id={threadId} and assigned as {loggerName} exists but could not be opened!");
return;
}

Check warning on line 134 in src/UniGetUI.Core.Tools/DWMThreadHelper.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check warning on line 134 in src/UniGetUI.Core.Tools/DWMThreadHelper.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

if (suspend)
{
uint res = SuspendThread(hThread);
if (res == 0)
{
IsSuspended = true;
Logger.Info($"{loggerName} Thread was suspended successfully");
CloseHandle(hThread);
return;
}
else
{
Logger.Warn($"Could not suspend {loggerName} Thread with NTSTATUS = 0x{res:X}");
}
}
else
{
int res = (int)ResumeThread(hThread);
if (res >= 0)
{
IsSuspended = false;
Logger.Info($"{loggerName} Thread was resumed successfully");
CloseHandle(hThread);
return;
}
else
{
Logger.Error($"Could not resume {loggerName} Thread with NTSTATUS = 0x{res:X}");
}
}

CloseHandle(hThread);
}

private static IntPtr? GetTargetFunctionAddress(string moduleName, int offset)
{
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
if (module.ModuleName.Equals(moduleName, StringComparison.OrdinalIgnoreCase))
{
return module.BaseAddress + offset;
}
}
return null;
}
}
26 changes: 8 additions & 18 deletions src/UniGetUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Text.RegularExpressions;
using Windows.ApplicationModel.Activation;
using CommunityToolkit.WinUI.Helpers;
using H.NotifyIcon;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
Expand Down Expand Up @@ -349,14 +350,9 @@
await MainWindow.HandleMissingDependencies(missing_deps);
}

protected override async void OnLaunched(LaunchActivatedEventArgs args)
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
if (!CoreData.IsDaemon)
{
await ShowMainWindowFromLaunchAsync();
}

CoreData.IsDaemon = false;
MainWindow?.Activate();
}

public async Task ShowMainWindowFromRedirectAsync(AppActivationArguments rawArgs)
Expand Down Expand Up @@ -390,22 +386,16 @@
MainWindow.DispatcherQueue.TryEnqueue(MainWindow.Activate);
}

public async Task ShowMainWindowFromLaunchAsync()
{
while (MainWindow is null)
await Task.Delay(100);

MainWindow.DispatcherQueue.TryEnqueue(MainWindow.Activate);
}

public async void DisposeAndQuit(int outputCode = 0)

Check warning on line 389 in src/UniGetUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / test-codebase

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
Logger.Warn("Quitting...");
Logger.Warn("Quitting UniGetUI");
DWMThreadHelper.ChangeState_DWM(false);
DWMThreadHelper.ChangeState_XAML(false);
MainWindow?.Close();
BackgroundApi?.Stop();
Exit();
await Task.Delay(100);
Environment.Exit(outputCode);
// await Task.Delay(100);
// Environment.Exit(outputCode);
}

public void KillAndRestart()
Expand Down
Loading
Loading