diff options
| author | Shulhan <ms@kilabit.info> | 2023-11-08 02:33:45 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2023-11-08 03:26:14 +0700 |
| commit | 5b8a7162b03f2acdfb85c8cd04fa3544503edd36 (patch) | |
| tree | 3e8c0207520a8bf818d074ec25871bcac73e98ef | |
| parent | 0a5043366cbebc07cebf31da0bc5397675bb70d9 (diff) | |
| download | pakakeh.go-5b8a7162b03f2acdfb85c8cd04fa3544503edd36.tar.xz | |
lib/memfs: use second channel to stop goroutine in Watcher
Calling Ticker.Stop does not stop the "for-range", as shown in this
test:
var (
delay = 100 * time.Millisecond
tick = time.NewTicker(delay)
)
go func() {
time.Sleep(2 * delay)
tick.Stop()
}()
for range tick.C {
t.Logf(`ticking`)
}
t.Logf(`ticker stopped`)
the "ticker stopped" will never be printed.
This will cause the Watcher will leak goroutine.
| -rw-r--r-- | lib/memfs/watcher.go | 81 |
1 files changed, 49 insertions, 32 deletions
diff --git a/lib/memfs/watcher.go b/lib/memfs/watcher.go index 3facfa65..0a191f32 100644 --- a/lib/memfs/watcher.go +++ b/lib/memfs/watcher.go @@ -23,6 +23,8 @@ type Watcher struct { C <-chan NodeState // The channel on which the changes are delivered. qchanges chan NodeState + done chan struct{} + node *Node ticker *time.Ticker @@ -94,6 +96,7 @@ func newWatcher(parent *Node, fi os.FileInfo, d time.Duration, qchanges chan Nod qchanges: qchanges, delay: d, ticker: time.NewTicker(d), + done: make(chan struct{}), node: node, } if w.qchanges == nil { @@ -111,60 +114,74 @@ func newWatcher(parent *Node, fi os.FileInfo, d time.Duration, qchanges chan Nod func (w *Watcher) start() { var ( logp = "Watcher" + ever = true newInfo fs.FileInfo ns NodeState err error ) - for range w.ticker.C { - newInfo, err = os.Stat(w.node.SysPath) - if err != nil { - if !os.IsNotExist(err) { - log.Printf("%s: %s: %s", logp, w.node.SysPath, err) + for ever { + select { + case <-w.ticker.C: + newInfo, err = os.Stat(w.node.SysPath) + if err != nil { + if !os.IsNotExist(err) { + log.Printf("%s: %s: %s", logp, w.node.SysPath, err) + continue + } + + ns.Node = *w.node + ns.State = FileStateDeleted + + select { + case w.qchanges <- ns: + default: + } + ever = false + w.ticker.Stop() continue } - ns.Node = *w.node - ns.State = FileStateDeleted + if w.node.Mode() != newInfo.Mode() { + w.node.SetMode(newInfo.Mode()) - select { - case w.qchanges <- ns: - default: + ns.Node = *w.node + ns.State = FileStateUpdateMode + + select { + case w.qchanges <- ns: + default: + } + continue + } + if w.node.ModTime().Equal(newInfo.ModTime()) { + continue } - w.node = nil - return - } - if w.node.Mode() != newInfo.Mode() { - w.node.SetMode(newInfo.Mode()) + w.node.SetModTime(newInfo.ModTime()) + w.node.SetSize(newInfo.Size()) ns.Node = *w.node - ns.State = FileStateUpdateMode + ns.State = FileStateUpdateContent select { case w.qchanges <- ns: default: } - continue - } - if w.node.ModTime().Equal(newInfo.ModTime()) { - continue - } - - w.node.SetModTime(newInfo.ModTime()) - w.node.SetSize(newInfo.Size()) - - ns.Node = *w.node - ns.State = FileStateUpdateContent - - select { - case w.qchanges <- ns: - default: + case <-w.done: + ever = false + w.ticker.Stop() + w.done <- struct{}{} } } } // Stop watching the file. func (w *Watcher) Stop() { - w.ticker.Stop() + select { + case w.done <- struct{}{}: + <-w.done + default: + // Ticker has been stopped due to file being deleted. + } } |
