From 5e6bcafa17e1ec20509f83424345c1794a9c118f Mon Sep 17 00:00:00 2001 From: marcmy <21000174+marcmy@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:59:21 -0400 Subject: [PATCH] Fix idle watcher start race --- CompactGUI.Watcher/BackgroundCompactor.vb | 38 ++++++++++++++--------- CompactGUI.Watcher/Watcher.vb | 31 +++++++++++++++--- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/CompactGUI.Watcher/BackgroundCompactor.vb b/CompactGUI.Watcher/BackgroundCompactor.vb index 28c9290..f1a05bb 100644 --- a/CompactGUI.Watcher/BackgroundCompactor.vb +++ b/CompactGUI.Watcher/BackgroundCompactor.vb @@ -64,24 +64,33 @@ Public Class BackgroundCompactor End Function - Public Async Function StartCompactingAsync(folders As IEnumerable(Of WatchedFolder)) As Task(Of Boolean) + Public Async Function StartCompactingAsync(folders As IEnumerable(Of WatchedFolder), Optional startCancellationToken As CancellationToken = Nothing) As Task(Of Boolean) If IsCompactorActive Then Return False + If startCancellationToken.IsCancellationRequested Then Return False cancellationTokenSource?.Dispose() Dim runCancellation = New CancellationTokenSource() cancellationTokenSource = runCancellation - WatcherLog.BackgroundCompactingStarted(_logger) - IsCompactorActive = True - isCompacting = True - isCompactingPaused = False - Dim currentProcess As Process = Process.GetCurrentProcess() Dim originalPriority As ProcessPriorityClass = ProcessPriorityClass.Normal + Dim priorityChanged As Boolean = False Try + WatcherLog.BackgroundCompactingStarted(_logger) + IsCompactorActive = True + + 'Publish the initial pause state before marking the run active. If system activity + 'resumes before this point, the start token catches it below; if it resumes after + 'this point, PauseCompacting can safely record the pause for the new compactor. + isCompactingPaused = False + isCompacting = True + + If startCancellationToken.IsCancellationRequested Then Return False + originalPriority = currentProcess.PriorityClass currentProcess.PriorityClass = ProcessPriorityClass.Idle + priorityChanged = True For Each folder In folders.ToList If runCancellation.IsCancellationRequested Then Return False @@ -187,13 +196,14 @@ Public Class BackgroundCompactor End If runCancellation.Dispose() - Try - currentProcess.PriorityClass = originalPriority - Catch ex As Exception - _logger.LogDebug(ex, "Unable to restore CompactGUI process priority.") - Finally - currentProcess.Dispose() - End Try + If priorityChanged Then + Try + currentProcess.PriorityClass = originalPriority + Catch ex As Exception + _logger.LogDebug(ex, "Unable to restore CompactGUI process priority.") + End Try + End If + currentProcess.Dispose() End Try End Function @@ -359,4 +369,4 @@ Public Class BackgroundCompactor isCompactingPaused = False ' Reset pause state on cancellation End Sub -End Class +End Class \ No newline at end of file diff --git a/CompactGUI.Watcher/Watcher.vb b/CompactGUI.Watcher/Watcher.vb index 9d85f24..9851817 100644 --- a/CompactGUI.Watcher/Watcher.vb +++ b/CompactGUI.Watcher/Watcher.vb @@ -26,6 +26,7 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi Private ReadOnly _parseWatchersSemaphore As New SemaphoreSlim(1, 1) Private ReadOnly _runWatcherSemaphore As New SemaphoreSlim(1, 1) Private _runCancellationTokenSource As CancellationTokenSource + Private _runIsIdleTriggered As Boolean Private ReadOnly _runCancellationLock As New Object Private ReadOnly _logger As ILogger(Of Watcher) @@ -107,6 +108,7 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi SyncLock _runCancellationLock _runCancellationTokenSource = ownedCancellation + _runIsIdleTriggered = Not runAll End SyncLock IsRunning = True @@ -128,7 +130,7 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi Return False End If If _parseWatchersSemaphore.CurrentCount <> 0 AndAlso (IsBackgroundCompactingEnabled OrElse runAll) Then - Await BackgroundCompact(runAll) 'The background compactor manages its own cancellation token. + Await BackgroundCompact(runAll, If(runAll, CancellationToken.None, runToken)) 'The background compactor manages its own cancellation token once started. End If If runToken.IsCancellationRequested Then _logger.LogInformation("Watcher run cancelled by user.") @@ -146,6 +148,7 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi SyncLock _runCancellationLock If Object.ReferenceEquals(_runCancellationTokenSource, ownedCancellation) Then _runCancellationTokenSource = Nothing + _runIsIdleTriggered = False End If End SyncLock @@ -173,6 +176,20 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi BGCompactor.CancelCompacting() End Sub + Private Sub CancelIdleTriggeredRun() + Dim cancellation As CancellationTokenSource = Nothing + + SyncLock _runCancellationLock + If _runIsIdleTriggered Then cancellation = _runCancellationTokenSource + End SyncLock + + Try + cancellation?.Cancel() + Catch ex As ObjectDisposedException + 'The idle-triggered run completed between retrieving and cancelling its token source. + End Try + End Sub + Private Sub OnSystemNotIdle(sender As Object, e As EventArgs) @@ -183,6 +200,10 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi Dim bgMode = _settingsService.AppSettings.BackgroundModeSelection If bgMode <> BackgroundMode.IdleOnly Then Return + 'If activity resumes while an idle-triggered analysis is still preparing a background + 'run, cancel that pending transition. An already-running compactor is still paused below + 'so the existing idle resume behaviour remains intact. + CancelIdleTriggeredRun() BGCompactor.PauseCompacting() End Sub @@ -449,13 +470,14 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi End Function - Public Async Function BackgroundCompact(Optional runAll As Boolean = False) As Task + Public Async Function BackgroundCompact(Optional runAll As Boolean = False, Optional startCancellationToken As CancellationToken = Nothing) As Task Dim acquired = Await _parseWatchersSemaphore.WaitAsync(0) If Not acquired Then Return Try + If startCancellationToken.IsCancellationRequested Then Return If BGCompactor.IsCompactorActive Then Return RefreshWatchedFolderAvailability() @@ -473,7 +495,8 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi If foldersToCompress.Any = 0 Then Return - Await BGCompactor.StartCompactingAsync(foldersToCompress) + If startCancellationToken.IsCancellationRequested Then Return + Await BGCompactor.StartCompactingAsync(foldersToCompress, startCancellationToken) OnPropertyChanged(NameOf(TotalSaved)) Finally @@ -547,4 +570,4 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi -End Class \ No newline at end of file +End Class