Skip to content

Commit

Permalink
Merge pull request #3278 from marticliment/cpu-usage-fix
Browse files Browse the repository at this point in the history
Fix issues with High CPU Usage on background
  • Loading branch information
marticliment authored Feb 9, 2025
2 parents 840df8b + 4959a2c commit f4e466f
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 49 deletions.
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 @@ private async Task CheckForMissingDependencies()
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 @@ public async Task ShowMainWindowFromRedirectAsync(AppActivationArguments rawArgs
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

0 comments on commit f4e466f

Please sign in to comment.