From 9fbdb6b30578ae84fdfd170e9325a4d8760acc48 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 13 Nov 2015 15:00:32 +0100 Subject: [PATCH] Cancel a running scan --- lib/model/model.go | 16 ++++++++++----- lib/scanner/blockqueue.go | 42 ++++++++++++++++++++++++++------------- lib/scanner/walk.go | 37 ++++++++++++++++++++++++++++------ 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/lib/model/model.go b/lib/model/model.go index 9cc7e754b..14724eb9c 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -1290,6 +1290,11 @@ nextSub: } subs = unifySubs + // The cancel channel is closed whenever we return (such as from an error), + // to signal the potentially still running walker to stop. + cancel := make(chan struct{}) + defer close(cancel) + w := &scanner.Walker{ Folder: folderCfg.ID, Dir: folderCfg.Path(), @@ -1305,6 +1310,7 @@ nextSub: Hashers: m.numHashers(folder), ShortID: m.shortID, ProgressTickIntervalS: folderCfg.ScanProgressIntervalS, + Cancel: cancel, } runner.setState(FolderScanning) @@ -1714,17 +1720,17 @@ func (m *Model) BringToFront(folder, file string) { // CheckFolderHealth checks the folder for common errors and returns the // current folder error, or nil if the folder is healthy. func (m *Model) CheckFolderHealth(id string) error { + folder, ok := m.cfg.Folders()[id] + if !ok { + return errors.New("folder does not exist") + } + if minFree := m.cfg.Options().MinHomeDiskFreePct; minFree > 0 { if free, err := osutil.DiskFreePercentage(m.cfg.ConfigPath()); err == nil && free < minFree { return errors.New("home disk has insufficient free space") } } - folder, ok := m.cfg.Folders()[id] - if !ok { - return errors.New("folder does not exist") - } - fi, err := os.Stat(folder.Path()) v, ok := m.CurrentLocalVersion(id) diff --git a/lib/scanner/blockqueue.go b/lib/scanner/blockqueue.go index ea5f374d6..9c878733b 100644 --- a/lib/scanner/blockqueue.go +++ b/lib/scanner/blockqueue.go @@ -19,13 +19,13 @@ import ( // workers are used in parallel. The outbox will become closed when the inbox // is closed and all items handled. -func newParallelHasher(dir string, blockSize, workers int, outbox, inbox chan protocol.FileInfo, counter *int64, done chan struct{}) { +func newParallelHasher(dir string, blockSize, workers int, outbox, inbox chan protocol.FileInfo, counter *int64, done, cancel chan struct{}) { wg := sync.NewWaitGroup() wg.Add(workers) for i := 0; i < workers; i++ { go func() { - hashFiles(dir, blockSize, outbox, inbox, counter) + hashFiles(dir, blockSize, outbox, inbox, counter, cancel) wg.Done() }() } @@ -59,19 +59,33 @@ func HashFile(path string, blockSize int, sizeHint int64, counter *int64) ([]pro return Blocks(fd, blockSize, sizeHint, counter) } -func hashFiles(dir string, blockSize int, outbox, inbox chan protocol.FileInfo, counter *int64) { - for f := range inbox { - if f.IsDirectory() || f.IsDeleted() { - panic("Bug. Asked to hash a directory or a deleted file.") - } +func hashFiles(dir string, blockSize int, outbox, inbox chan protocol.FileInfo, counter *int64, cancel chan struct{}) { + for { + select { + case f, ok := <-inbox: + if !ok { + return + } - blocks, err := HashFile(filepath.Join(dir, f.Name), blockSize, f.CachedSize, counter) - if err != nil { - l.Debugln("hash error:", f.Name, err) - continue - } + if f.IsDirectory() || f.IsDeleted() { + panic("Bug. Asked to hash a directory or a deleted file.") + } - f.Blocks = blocks - outbox <- f + blocks, err := HashFile(filepath.Join(dir, f.Name), blockSize, f.CachedSize, counter) + if err != nil { + l.Debugln("hash error:", f.Name, err) + continue + } + + f.Blocks = blocks + select { + case outbox <- f: + case <-cancel: + return + } + + case <-cancel: + return + } } } diff --git a/lib/scanner/walk.go b/lib/scanner/walk.go index b827ed9fa..17a06442f 100644 --- a/lib/scanner/walk.go +++ b/lib/scanner/walk.go @@ -72,6 +72,8 @@ type Walker struct { // Optional progress tick interval which defines how often FolderScanProgress // events are emitted. Negative number means disabled. ProgressTickIntervalS int + // Signals cancel from the outside - when closed, we should stop walking. + Cancel chan struct{} } type TempNamer interface { @@ -121,7 +123,7 @@ func (w *Walker) Walk() (chan protocol.FileInfo, error) { // We're not required to emit scan progress events, just kick off hashers, // and feed inputs directly from the walker. if w.ProgressTickIntervalS < 0 { - newParallelHasher(w.Dir, w.BlockSize, w.Hashers, finishedChan, toHashChan, nil, nil) + newParallelHasher(w.Dir, w.BlockSize, w.Hashers, finishedChan, toHashChan, nil, nil, w.Cancel) return finishedChan, nil } @@ -149,7 +151,7 @@ func (w *Walker) Walk() (chan protocol.FileInfo, error) { realToHashChan := make(chan protocol.FileInfo) done := make(chan struct{}) - newParallelHasher(w.Dir, w.BlockSize, w.Hashers, finishedChan, realToHashChan, &progress, done) + newParallelHasher(w.Dir, w.BlockSize, w.Hashers, finishedChan, realToHashChan, &progress, done, w.Cancel) // A routine which actually emits the FolderScanProgress events // every w.ProgressTicker ticks, until the hasher routines terminate. @@ -168,13 +170,21 @@ func (w *Walker) Walk() (chan protocol.FileInfo, error) { "current": current, "total": total, }) + case <-w.Cancel: + ticker.Stop() + return } } }() + loop: for _, file := range filesToHash { l.Debugln("real to hash:", file.Name) - realToHashChan <- file + select { + case realToHashChan <- file: + case <-w.Cancel: + break loop + } } close(realToHashChan) }() @@ -329,7 +339,11 @@ func (w *Walker) walkAndHashFiles(fchan, dchan chan protocol.FileInfo) filepath. l.Debugln("symlink changedb:", p, f) - dchan <- f + select { + case dchan <- f: + case <-w.Cancel: + return errors.New("cancelled") + } return skip } @@ -363,7 +377,13 @@ func (w *Walker) walkAndHashFiles(fchan, dchan chan protocol.FileInfo) filepath. Modified: mtime.Unix(), } l.Debugln("dir:", p, f) - dchan <- f + + select { + case dchan <- f: + case <-w.Cancel: + return errors.New("cancelled") + } + return nil } @@ -406,7 +426,12 @@ func (w *Walker) walkAndHashFiles(fchan, dchan chan protocol.FileInfo) filepath. CachedSize: info.Size(), } l.Debugln("to hash:", p, f) - fchan <- f + + select { + case fchan <- f: + case <-w.Cancel: + return errors.New("cancelled") + } } return nil