diff --git a/lib/model/folder.go b/lib/model/folder.go index dd910494a..9f50fc80c 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -195,19 +195,23 @@ func (f *folder) Serve(ctx context.Context) error { case <-f.pullScheduled: if f.PullerDelayS > 0 { // Wait for incoming updates to settle before doing the - // actual pull. Only set the state to SyncWaiting if we have - // reason to believe there is something to sync, to avoid - // unnecessary flashing in the GUI. - if needCount, err := f.db.CountNeed(f.folderID, protocol.LocalDeviceID); err == nil && needCount.TotalItems() > 0 { - f.setState(FolderSyncWaiting) + // actual pull. + // + // Only set the state to SyncWaiting if we are Idle (and + // not, e.g., errored) and have reason to believe there is + // something to sync, to avoid unnecessary flashing in the + // GUI. + if cur, _, _ := f.getState(); cur == FolderIdle { + if needCount, err := f.db.CountNeed(f.folderID, protocol.LocalDeviceID); err == nil && needCount.TotalItems() > 0 { + f.setState(FolderSyncWaiting) + } } pullTimer.Reset(time.Duration(float64(time.Second) * f.PullerDelayS)) - } else { - _, err = f.pull(ctx) + continue } + _, err = f.pull(ctx) case <-pullTimer.C: - f.setState(FolderIdle) _, err = f.pull(ctx) case <-f.pullFailTimer.C: @@ -238,10 +242,12 @@ func (f *folder) Serve(ctx context.Context) error { case next := <-f.scanDelay: f.sl.DebugContext(ctx, "Delaying scan") f.scanTimer.Reset(next) + continue case <-f.scanScheduled: f.sl.DebugContext(ctx, "Scan was scheduled") f.scanTimer.Reset(0) + continue case fsEvents := <-f.watchChan: f.sl.DebugContext(ctx, "Scan due to watcher") @@ -252,16 +258,20 @@ func (f *folder) Serve(ctx context.Context) error { err = f.restartWatch(ctx) case <-f.versionCleanupTimer.C: + if _, _, healthErr := f.getState(); healthErr != nil { + continue + } f.sl.DebugContext(ctx, "Doing version cleanup") f.versionCleanupTimerFired(ctx) } - if err != nil { - if svcutil.IsFatal(err) { - return err - } - f.setError(ctx, err) + if svcutil.IsFatal(err) { + return err } + + // Set the state to FolderError when err is non-nil, otherwise reset + // it back to FolderIdle. + f.setError(ctx, err) } } @@ -403,6 +413,15 @@ func (f *folder) pull(ctx context.Context) (success bool, err error) { } }() + // Abort early (before acquiring a limiter token) if there's a folder + // error. This must happen before the "nothing to do" check below so + // that up-to-date folders are also periodically health-checked. + err = f.getHealthErrorWithoutIgnores() + if err != nil { + f.sl.DebugContext(ctx, "Skipping pull due to folder error", slogutil.Error(err)) + return false, err + } + // If there is nothing to do, don't even enter sync-waiting state. needCount, err := f.db.CountNeed(f.folderID, protocol.LocalDeviceID) if err != nil { @@ -416,13 +435,6 @@ func (f *folder) pull(ctx context.Context) (success bool, err error) { return true, nil } - // Abort early (before acquiring a token) if there's a folder error - err = f.getHealthErrorWithoutIgnores() - if err != nil { - f.sl.DebugContext(ctx, "Skipping pull due to folder error", slogutil.Error(err)) - return false, err - } - // Send only folder doesn't do any io, it only checks for out-of-sync // items that differ in metadata and updates those. if f.Type != config.FolderTypeSendOnly { @@ -487,7 +499,6 @@ func (f *folder) scanSubdirs(ctx context.Context, subDirs []string) error { }() f.setState(FolderScanWaiting) - defer f.setState(FolderIdle) if err := f.ioLimiter.TakeWithContext(ctx, 1); err != nil { return err @@ -993,7 +1004,6 @@ func (f *folder) scanTimerFired(ctx context.Context) error { func (f *folder) versionCleanupTimerFired(ctx context.Context) { f.setState(FolderCleanWaiting) - defer f.setState(FolderIdle) if err := f.ioLimiter.TakeWithContext(ctx, 1); err != nil { return @@ -1184,8 +1194,13 @@ func (f *folder) setError(ctx context.Context, err error) { default: } - _, _, oldErr := f.getState() - if (err != nil && oldErr != nil && oldErr.Error() == err.Error()) || (err == nil && oldErr == nil) { + state, _, oldErr := f.getState() + switch { + case err != nil && oldErr != nil && oldErr.Error() == err.Error(): + // The error is the same as the old error, no change is required + return + case err == nil && oldErr == nil && state == FolderIdle: + // No error before or now, and we are already Idle return } @@ -1195,7 +1210,7 @@ func (f *folder) setError(ctx context.Context, err error) { } else { f.sl.InfoContext(ctx, "Folder error changed", slogutil.Error(err), slog.Any("previously", oldErr)) } - } else { + } else if oldErr != nil { f.sl.InfoContext(ctx, "Folder error cleared") f.SchedulePull() } @@ -1203,7 +1218,7 @@ func (f *folder) setError(ctx context.Context, err error) { if f.FSWatcherEnabled { if err != nil { f.stopWatch() - } else { + } else if oldErr != nil { f.scheduleWatchRestart() } } diff --git a/lib/model/folder_recvenc.go b/lib/model/folder_recvenc.go index 1c77b39df..802542cdd 100644 --- a/lib/model/folder_recvenc.go +++ b/lib/model/folder_recvenc.go @@ -44,7 +44,6 @@ func (f *receiveEncryptedFolder) revert(ctx context.Context) error { f.sl.InfoContext(ctx, "Reverting unexpected items") f.setState(FolderScanning) - defer f.setState(FolderIdle) batch := NewFileInfoBatch(func(fs []protocol.FileInfo) error { f.updateLocalsFromScanning(fs) diff --git a/lib/model/folder_recvonly.go b/lib/model/folder_recvonly.go index 0b981f376..6dd8335a0 100644 --- a/lib/model/folder_recvonly.go +++ b/lib/model/folder_recvonly.go @@ -74,7 +74,6 @@ func (f *receiveOnlyFolder) revert(ctx context.Context) error { f.sl.InfoContext(ctx, "Reverting folder") f.setState(FolderScanning) - defer f.setState(FolderIdle) scanChan := make(chan string) go f.pullScannerRoutine(ctx, scanChan) diff --git a/lib/model/folder_sendonly.go b/lib/model/folder_sendonly.go index 816edd782..9cc64362d 100644 --- a/lib/model/folder_sendonly.go +++ b/lib/model/folder_sendonly.go @@ -98,7 +98,6 @@ func (f *sendOnlyFolder) override(ctx context.Context) error { f.sl.InfoContext(ctx, "Overriding global state ") f.setState(FolderScanning) - defer f.setState(FolderIdle) batch := NewFileInfoBatch(func(files []protocol.FileInfo) error { f.updateLocalsFromScanning(files) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index c04eb8b56..a4a2cd0e3 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -168,7 +168,6 @@ func (f *sendReceiveFolder) pull(ctx context.Context) (bool, error) { go f.pullScannerRoutine(ctx, scanChan) defer func() { close(scanChan) - f.setState(FolderIdle) }() metricFolderPulls.WithLabelValues(f.ID).Inc()