9
9
"os"
10
10
"path/filepath"
11
11
"sync"
12
- "sync/atomic"
13
12
"time"
14
13
15
14
"github.com/fsnotify/fsnotify"
@@ -21,11 +20,8 @@ type File struct {
21
20
w * fsnotify.Watcher
22
21
23
22
// Mutex to protect concurrent access to watcher state
24
- mu sync.Mutex
25
-
26
- // Using Go 1.18 atomic functions for backwards compatibility.
27
- isWatching uint32
28
- isUnwatched uint32
23
+ mu sync.Mutex
24
+ isWatching bool
29
25
}
30
26
31
27
// Provider returns a file provider.
@@ -47,10 +43,10 @@ func (f *File) Read() (map[string]interface{}, error) {
47
43
// blocking function that internally spawns a goroutine to watch for changes.
48
44
func (f * File ) Watch (cb func (event interface {}, err error )) error {
49
45
f .mu .Lock ()
50
- defer f .mu .Unlock ()
51
-
46
+
52
47
// If a watcher already exists, return an error.
53
- if atomic .LoadUint32 (& f .isWatching ) == 1 {
48
+ if f .isWatching {
49
+ f .mu .Unlock ()
54
50
return errors .New ("file is already being watched" )
55
51
}
56
52
@@ -68,10 +64,24 @@ func (f *File) Watch(cb func(event interface{}, err error)) error {
68
64
69
65
f .w , err = fsnotify .NewWatcher ()
70
66
if err != nil {
67
+ f .mu .Unlock ()
71
68
return err
72
69
}
73
70
74
- atomic .StoreUint32 (& f .isWatching , 1 )
71
+ f .isWatching = true
72
+
73
+ // Set up the directory watch before releasing the lock
74
+ err = f .w .Add (fDir )
75
+ if err != nil {
76
+ f .w .Close ()
77
+ f .w = nil
78
+ f .isWatching = false
79
+ f .mu .Unlock ()
80
+ return err
81
+ }
82
+
83
+ // Release the lock before spawning goroutine
84
+ f .mu .Unlock ()
75
85
76
86
var (
77
87
lastEvent string
@@ -84,8 +94,12 @@ func (f *File) Watch(cb func(event interface{}, err error)) error {
84
94
select {
85
95
case event , ok := <- f .w .Events :
86
96
if ! ok {
87
- // Only throw an error if it was not an explicit unwatch.
88
- if atomic .LoadUint32 (& f .isUnwatched ) == 0 {
97
+ // Only throw an error if we were still supposed to be watching.
98
+ f .mu .Lock ()
99
+ stillWatching := f .isWatching
100
+ f .mu .Unlock ()
101
+
102
+ if stillWatching {
89
103
cb (nil , errors .New ("fsnotify watch channel closed" ))
90
104
}
91
105
@@ -133,8 +147,12 @@ func (f *File) Watch(cb func(event interface{}, err error)) error {
133
147
// There's an error.
134
148
case err , ok := <- f .w .Errors :
135
149
if ! ok {
136
- // Only throw an error if it was not an explicit unwatch.
137
- if atomic .LoadUint32 (& f .isUnwatched ) == 0 {
150
+ // Only throw an error if we were still supposed to be watching.
151
+ f .mu .Lock ()
152
+ stillWatching := f .isWatching
153
+ f .mu .Unlock ()
154
+
155
+ if stillWatching {
138
156
cb (nil , errors .New ("fsnotify err channel closed" ))
139
157
}
140
158
@@ -148,27 +166,32 @@ func (f *File) Watch(cb func(event interface{}, err error)) error {
148
166
}
149
167
150
168
f .mu .Lock ()
151
- atomic .StoreUint32 (& f .isWatching , 0 )
152
- atomic .StoreUint32 (& f .isUnwatched , 0 )
169
+ f .isWatching = false
153
170
if f .w != nil {
154
171
f .w .Close ()
155
172
f .w = nil
156
173
}
157
174
f .mu .Unlock ()
158
175
}()
159
176
160
- // Watch the directory for changes.
161
- return f .w .Add (fDir )
177
+ return nil
162
178
}
163
179
164
180
// Unwatch stops watching the files and closes fsnotify watcher.
165
181
func (f * File ) Unwatch () error {
166
182
f .mu .Lock ()
167
183
defer f .mu .Unlock ()
168
184
169
- atomic .StoreUint32 (& f .isUnwatched , 1 )
185
+ if ! f .isWatching {
186
+ return nil // Already unwatched
187
+ }
188
+
189
+ f .isWatching = false
170
190
if f .w != nil {
191
+ // Close the watcher to signal the goroutine to stop
192
+ // The goroutine will handle setting f.w = nil
171
193
return f .w .Close ()
172
194
}
173
- return nil
195
+ // This state should ideally never be reached - it indicates a bug in the synchronization logic
196
+ return errors .New ("file watcher is in an inconsistent state: isWatching is true but watcher is nil" )
174
197
}
0 commit comments