Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9cf5415

Browse files
committed
rewrite watcher impl. to watch dirs
1 parent 7edee90 commit 9cf5415

File tree

1 file changed

+117
-31
lines changed

1 file changed

+117
-31
lines changed

agent/agentcontainers/watcher/watcher.go

+117-31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package watcher
88

99
import (
1010
"context"
11+
"path/filepath"
1112
"sync"
1213

1314
"github.com/fsnotify/fsnotify"
@@ -36,32 +37,91 @@ type Watcher interface {
3637

3738
type fsnotifyWatcher struct {
3839
*fsnotify.Watcher
39-
closeOnce sync.Once
40-
closed chan struct{}
40+
41+
mu sync.Mutex // Protects following.
42+
watchedFiles map[string]bool // Files being watched (absolute path -> bool).
43+
watchedDirs map[string]int // Refcount of directories being watched (absolute path -> count).
44+
closed bool // Protects closing of done.
45+
done chan struct{}
4146
}
4247

48+
// NewFSNotify creates a new file system watcher that watches parent directories
49+
// instead of individual files for more reliable event detection.
4350
func NewFSNotify() (Watcher, error) {
4451
w, err := fsnotify.NewWatcher()
4552
if err != nil {
4653
return nil, xerrors.Errorf("create fsnotify watcher: %w", err)
4754
}
4855
return &fsnotifyWatcher{
49-
Watcher: w,
50-
closed: make(chan struct{}),
56+
Watcher: w,
57+
done: make(chan struct{}),
58+
watchedFiles: make(map[string]bool),
59+
watchedDirs: make(map[string]int),
5160
}, nil
5261
}
5362

54-
func (f *fsnotifyWatcher) Add(path string) error {
55-
if err := f.Watcher.Add(path); err != nil {
56-
return xerrors.Errorf("add path to watcher: %w", err)
63+
func (f *fsnotifyWatcher) Add(file string) error {
64+
absPath, err := filepath.Abs(file)
65+
if err != nil {
66+
return xerrors.Errorf("absolute path: %w", err)
67+
}
68+
69+
dir := filepath.Dir(absPath)
70+
71+
f.mu.Lock()
72+
defer f.mu.Unlock()
73+
74+
// Already watching this file.
75+
if f.watchedFiles[absPath] {
76+
return nil
77+
}
78+
79+
// Start watching the parent directory if not already watching.
80+
if f.watchedDirs[dir] == 0 {
81+
if err := f.Watcher.Add(dir); err != nil {
82+
return xerrors.Errorf("add directory to watcher: %w", err)
83+
}
5784
}
85+
86+
// Increment the reference count for this directory.
87+
f.watchedDirs[dir]++
88+
// Mark this file as watched.
89+
f.watchedFiles[absPath] = true
90+
5891
return nil
5992
}
6093

61-
func (f *fsnotifyWatcher) Remove(path string) error {
62-
if err := f.Watcher.Remove(path); err != nil {
63-
return xerrors.Errorf("remove path from watcher: %w", err)
94+
func (f *fsnotifyWatcher) Remove(file string) error {
95+
absPath, err := filepath.Abs(file)
96+
if err != nil {
97+
return xerrors.Errorf("absolute path: %w", err)
98+
}
99+
100+
dir := filepath.Dir(absPath)
101+
102+
f.mu.Lock()
103+
defer f.mu.Unlock()
104+
105+
// Not watching this file.
106+
if !f.watchedFiles[absPath] {
107+
return nil
108+
}
109+
110+
// Remove the file from our watch list.
111+
delete(f.watchedFiles, absPath)
112+
113+
// Decrement the reference count for this directory.
114+
f.watchedDirs[dir]--
115+
116+
// If no more files in this directory are being watched, stop
117+
// watching the directory.
118+
if f.watchedDirs[dir] <= 0 {
119+
if err := f.Watcher.Remove(dir); err != nil {
120+
return xerrors.Errorf("remove directory from watcher: %w", err)
121+
}
122+
delete(f.watchedDirs, dir)
64123
}
124+
65125
return nil
66126
}
67127

@@ -73,31 +133,57 @@ func (f *fsnotifyWatcher) Next(ctx context.Context) (event *fsnotify.Event, err
73133
}
74134
}()
75135

76-
select {
77-
case <-ctx.Done():
78-
return nil, ctx.Err()
79-
case event, ok := <-f.Events:
80-
if !ok {
81-
return nil, ErrWatcherClosed
82-
}
83-
return &event, nil
84-
case err, ok := <-f.Errors:
85-
if !ok {
136+
for {
137+
select {
138+
case <-ctx.Done():
139+
return nil, ctx.Err()
140+
case evt, ok := <-f.Events:
141+
if !ok {
142+
return nil, ErrWatcherClosed
143+
}
144+
145+
// Get the absolute path to match against our watched files.
146+
absPath, err := filepath.Abs(evt.Name)
147+
if err != nil {
148+
continue
149+
}
150+
151+
f.mu.Lock()
152+
isWatched := f.watchedFiles[absPath]
153+
f.mu.Unlock()
154+
if isWatched {
155+
return &evt, nil
156+
}
157+
158+
continue // Ignore events for files not being watched.
159+
160+
case err, ok := <-f.Errors:
161+
if !ok {
162+
return nil, ErrWatcherClosed
163+
}
164+
return nil, xerrors.Errorf("watcher error: %w", err)
165+
case <-f.done:
86166
return nil, ErrWatcherClosed
87167
}
88-
return nil, xerrors.Errorf("watcher error: %w", err)
89-
case <-f.closed:
90-
return nil, ErrWatcherClosed
91168
}
92169
}
93170

94171
func (f *fsnotifyWatcher) Close() (err error) {
95-
err = ErrWatcherClosed
96-
f.closeOnce.Do(func() {
97-
if err = f.Watcher.Close(); err != nil {
98-
err = xerrors.Errorf("close watcher: %w", err)
99-
}
100-
close(f.closed)
101-
})
102-
return err
172+
f.mu.Lock()
173+
f.watchedFiles = nil
174+
f.watchedDirs = nil
175+
closed := f.closed
176+
f.mu.Unlock()
177+
178+
if closed {
179+
return ErrWatcherClosed
180+
}
181+
182+
close(f.done)
183+
184+
if err := f.Watcher.Close(); err != nil {
185+
return xerrors.Errorf("close watcher: %w", err)
186+
}
187+
188+
return nil
103189
}

0 commit comments

Comments
 (0)