Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,4 @@ gitleaks*.tar.gz
*.orig
*.rej
*.xaml.bak
/tmp
20 changes: 20 additions & 0 deletions App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ protected override void OnExit(ExitEventArgs e)
this.DispatcherUnhandledException -= this.OnDispatcherUnhandledException;
TaskScheduler.UnobservedTaskException -= this.OnUnobservedTaskException;

this.DisposeServiceProvider();

if (this.singleInstanceMutex != null)
{
try
Expand All @@ -355,6 +357,24 @@ protected override void OnExit(ExitEventArgs e)
base.OnExit(e);
}

private void DisposeServiceProvider()
{
if (this.ServiceProvider is not IDisposable disposableProvider)
{
return;
}

var logger = this.ServiceProvider.GetService<ILogger<App>>();
try
{
disposableProvider.Dispose();
}
catch (Exception ex)
{
logger?.LogWarning(ex, "Failed to dispose the application service provider during shutdown");
}
}

#if DEBUG
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AllocConsole();
Expand Down
53 changes: 48 additions & 5 deletions MainWindow.Behaviors.partial.cs
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ private async Task InitializeSystemTrayAsync()
// Initialize system tray context menu with current data
await this.UpdateSystemTrayContextMenuAsync();

this.trayPowerPlanService ??= this.serviceProvider.GetRequiredService<IPowerPlanService>();
this.trayPowerPlanService.PowerPlanChanged -= this.OnPowerPlanChangedForTray;
this.trayPowerPlanService.PowerPlanChanged += this.OnPowerPlanChangedForTray;

// Start periodic system tray updates
this.StartSystemTrayUpdateTimer();
}
Expand Down Expand Up @@ -1092,14 +1096,24 @@ private async Task UpdateSystemTrayContextMenuAsync()
{
try
{
await this.systemTrayStatusUpdater.UpdateContextMenuAsync(this.systemTrayService);
await this.systemTrayStatusUpdater.UpdateContextMenuAsync(
this.systemTrayService,
action => this.Dispatcher.InvokeAsync(action).Task);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to update system tray context menu: {ex.Message}");
this.LogDebug($"Failed to update system tray context menu: {ex.Message}");
}
}

private void OnPowerPlanChangedForTray(object? sender, PowerPlanChangedEventArgs e)
{
TaskSafety.FireAndForget(this.UpdateSystemTrayContextMenuAsync(), ex =>
{
this.LogDebug($"Failed to refresh tray menu after power plan change: {ex.Message}");
});
}

private void StartSystemTrayUpdateTimer()
{
try
Expand Down Expand Up @@ -1304,8 +1318,15 @@ private string Localize(string key, string fallback) =>

private void OnMonitoringStatusChanged(object? sender, MonitoringStatusEventArgs e)
{
// Update tray icon and status
this.systemTrayService.UpdateMonitoringStatus(e.IsMonitoring, e.IsWmiAvailable);
if (this.Dispatcher.CheckAccess())
{
this.systemTrayService.UpdateMonitoringStatus(e.IsMonitoring, e.IsWmiAvailable);
}
else
{
this.Dispatcher.InvokeAsync(() =>
this.systemTrayService.UpdateMonitoringStatus(e.IsMonitoring, e.IsWmiAvailable));
}

// Show notification if there's an error
if (e.Error != null && this.settingsService.Settings.EnableErrorNotifications)
Expand Down Expand Up @@ -2045,7 +2066,16 @@ protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
}

e.Cancel = true;
_ = this.HandleWindowCloseAsync();

TaskSafety.FireAndForget(this.HandleWindowCloseAsync(), ex =>
{
this.LogDebug($"Window close handling failed: {ex.Message}");
this.Dispatcher.InvokeAsync(() =>
{
this.isPerformingShutdown = true;
System.Windows.Application.Current?.Shutdown();
});
});
}

protected override void OnClosed(EventArgs e)
Expand All @@ -2060,14 +2090,27 @@ protected override void OnClosed(EventArgs e)
this.processMonitorManagerService.ServiceStatusChanged -= this.OnProcessMonitorManagerStatusChanged;
this.keyboardShortcutService.ShortcutActivated -= this.OnShortcutActivated;

if (this.trayPowerPlanService != null)
{
this.trayPowerPlanService.PowerPlanChanged -= this.OnPowerPlanChangedForTray;
this.trayPowerPlanService = null;
}

this.UnsubscribeSystemTrayEvents();

this.systemTrayUpdateTimer?.Stop();
this.systemTrayUpdateTimer?.Dispose();

this.initializationTimeoutTimer?.Stop();
this.initializationTimeoutTimer?.Dispose();

this.performanceViewModel?.Dispose();
this.settingsViewModel.Dispose();
this.powerPlanViewModel.Dispose();
this.associationViewModel.Dispose();
this.logViewerViewModel.Dispose();
this.systemTweaksViewModel.Dispose();
this.mainWindowViewModel.Dispose();

this.selfResourceManagementService.RestoreForegroundMode();
this.navigationBehavior.Dispose();
Expand Down
1 change: 1 addition & 0 deletions MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
private TaskCompletionSource<MessageBoxResult>? unsavedSettingsDialogCompletionSource;
private bool isSilentStartupMode;
private bool showStartupMinimizedSuggestionOnReady;
private IPowerPlanService? trayPowerPlanService;

public MainWindow(
ProcessViewModel processViewModel,
Expand Down
20 changes: 15 additions & 5 deletions Platforms/Windows/ProcessCpuSetHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public class ProcessCpuSetHandler : IProcessCpuSetHandler
private static CpuSetMapping staticCpuSetMapping = CpuSetMapping.Empty;
private static readonly object staticInitLock = new object();
private static bool staticInitialized = false;
private static long lastCpuSetMappingFailureTicks = -1;

private static readonly TimeSpan CpuSetMappingRetryInterval = TimeSpan.FromSeconds(30);

private readonly Queue<CpuTimeTimestamp> cpuTimeMovingAverageBuffer = new();
private readonly string executableName;
Expand Down Expand Up @@ -77,19 +80,26 @@ private static CpuSetMapping EnsureStaticInitialization(IProcessCpuSetNativeApi
return staticCpuSetMapping;
}

if (lastCpuSetMappingFailureTicks >= 0 &&
Environment.TickCount64 - lastCpuSetMappingFailureTicks < CpuSetMappingRetryInterval.TotalMilliseconds)
{
return CpuSetMapping.Empty;
}

try
{
staticCpuSetMapping = GetCpuSetMapping(nativeApi);
lastCpuSetMappingFailureTicks = -1;

staticInitialized = true;
return staticCpuSetMapping;
}
catch (Exception)
{
// If we can't get CPU Set mapping, CPU Sets won't be available
// The handler will still work but ApplyCpuSetMask will return false
staticCpuSetMapping = CpuSetMapping.Empty;
lastCpuSetMappingFailureTicks = Environment.TickCount64;
return CpuSetMapping.Empty;
}

staticInitialized = true;
return staticCpuSetMapping;
}
}

Expand Down
23 changes: 9 additions & 14 deletions Services/AutostartService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public partial class AutostartService : IAutostartService
private readonly ILogger<AutostartService> logger;
private readonly IElevationService elevationService;
private readonly IElevatedTaskService elevatedTaskService;
private bool isAutostartEnabled;
private string? autostartPath;
private volatile bool isAutostartEnabled;
private volatile string? autostartPath;

public event EventHandler<AutostartStatusChangedEventArgs>? AutostartStatusChanged;

Expand Down Expand Up @@ -50,9 +50,6 @@ public async Task<bool> EnableAutostartAsync(bool startMinimized = true)
var arguments = this.GetAutostartArguments(startMinimized);
var fullCommand = $"\"{executablePath}\" {arguments}";

// Clean up legacy registry-based startup to keep a single elevated startup mechanism.
this.TryRemoveLegacyRegistryAutostart();

if (!this.elevationService.IsRunningAsAdministrator())
{
LogAutostartRequiresElevation(this.logger);
Expand All @@ -68,6 +65,11 @@ public async Task<bool> EnableAutostartAsync(bool startMinimized = true)
}

var scheduledTaskCreated = await this.elevatedTaskService.EnsureAutostartTaskAsync(executablePath, arguments);
if (scheduledTaskCreated)
{
this.TryRemoveLegacyRegistryAutostart();
}

if (!scheduledTaskCreated)
{
LogAutostartTaskRegistrationFailed(this.logger);
Expand Down Expand Up @@ -113,15 +115,15 @@ public async Task<bool> DisableAutostartAsync()
return false;
}

this.TryRemoveLegacyRegistryAutostart();

var scheduledTaskRemoved = await this.elevatedTaskService.RemoveAutostartTaskAsync();
if (!scheduledTaskRemoved)
{
LogAutostartTaskRemovalFailed(this.logger);
return false;
}

this.TryRemoveLegacyRegistryAutostart();

LogAutostartDisabled(this.logger);

this.isAutostartEnabled = false;
Expand Down Expand Up @@ -169,13 +171,6 @@ public async Task<bool> CheckAutostartStatusAsync()

public async Task<bool> UpdateAutostartAsync(bool startMinimized = true)
{
if (!this.isAutostartEnabled)
{
return await this.EnableAutostartAsync(startMinimized);
}

// Re-enable with new parameters
await this.DisableAutostartAsync();
return await this.EnableAutostartAsync(startMinimized);
}

Expand Down
12 changes: 11 additions & 1 deletion Services/EnhancedLoggingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class EnhancedLoggingService : IEnhancedLoggingService, IDisposable
private int flushScheduled;
private bool isInitialized;
private bool disposed;
private volatile bool isDebugLoggingEnabled;

// PERFORMANCE IMPROVEMENT: Correlation tracking for better debugging
internal readonly AsyncLocal<string?> CorrelationId = new();
Expand All @@ -31,7 +32,7 @@ public class EnhancedLoggingService : IEnhancedLoggingService, IDisposable

public string LogDirectoryPath => this.logDirectory;

public bool IsDebugLoggingEnabled => this.settingsService.Settings.EnableDebugLogging;
public bool IsDebugLoggingEnabled => this.isDebugLoggingEnabled;

public event EventHandler<CriticalErrorEventArgs>? CriticalErrorOccurred;

Expand All @@ -44,9 +45,17 @@ public EnhancedLoggingService(ILogger<EnhancedLoggingService> logger, IApplicati
this.logDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ThreadPilot", "Logs");
this.currentLogFilePath = this.GetCurrentLogFilePath();

this.isDebugLoggingEnabled = settingsService.Settings.EnableDebugLogging;
this.settingsService.SettingsChanged += this.OnSettingsChanged;

this.flushTimer = new System.Threading.Timer(this.FlushLogs, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
}

private void OnSettingsChanged(object? sender, ApplicationSettingsChangedEventArgs e)
{
this.isDebugLoggingEnabled = e.NewSettings.EnableDebugLogging;
}

public async Task InitializeAsync()
{
if (this.isInitialized)
Expand Down Expand Up @@ -517,6 +526,7 @@ public void Dispose()
return;
}

this.settingsService.SettingsChanged -= this.OnSettingsChanged;
this.flushTimer?.Dispose();
this.FlushLogsAsync().Wait(TimeSpan.FromSeconds(5));
this.fileLock?.Dispose();
Expand Down
Loading