aboutsummaryrefslogtreecommitdiff
path: root/lib/memfs
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2023-11-08 02:33:45 +0700
committerShulhan <ms@kilabit.info>2023-11-08 03:26:14 +0700
commit5b8a7162b03f2acdfb85c8cd04fa3544503edd36 (patch)
tree3e8c0207520a8bf818d074ec25871bcac73e98ef /lib/memfs
parent0a5043366cbebc07cebf31da0bc5397675bb70d9 (diff)
downloadpakakeh.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.
Diffstat (limited to 'lib/memfs')
-rw-r--r--lib/memfs/watcher.go81
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.
+ }
}