@@ -32,15 +32,17 @@ type Watcher struct {
32
32
// distinct from the fsnotify watcher's error channel.
33
33
errs chan error
34
34
35
- // watchers counts the number of active watch registration goroutines,
36
- // including their error handling.
37
- watchers sync.WaitGroup
38
- runners sync.WaitGroup // counts the number of active run goroutines (max 1)
35
+ runners sync.WaitGroup // counts the number of active run goroutines (max 1)
39
36
40
37
watcher * fsnotify.Watcher
41
38
42
39
mu sync.Mutex // guards all fields below
43
40
41
+ // watchers counts the number of active watch registration goroutines,
42
+ // including their error handling.
43
+ // After [Watcher.Close] called, watchers's counter will no longer increase.
44
+ watchers sync.WaitGroup
45
+
44
46
// dirCancel maps a directory path to its cancellation channel.
45
47
// A nil map indicates the watcher is closing and prevents new directory
46
48
// watch registrations.
@@ -157,17 +159,13 @@ func (w *Watcher) WatchDir(path string) error {
157
159
return filepath .SkipDir
158
160
}
159
161
160
- done := w .addWatchHandle (path )
162
+ done , release := w .addWatchHandle (path )
161
163
if done == nil { // file watcher closing
162
164
return filepath .SkipAll
163
165
}
166
+ defer release ()
164
167
165
- errChan := make (chan error , 1 )
166
- w .watchDir (path , done , errChan )
167
-
168
- if err := <- errChan ; err != nil {
169
- return err
170
- }
168
+ return w .watchDir (path , done )
171
169
}
172
170
return nil
173
171
})
@@ -229,8 +227,13 @@ func (w *Watcher) handleEvent(event fsnotify.Event) *protocol.FileEvent {
229
227
// This watch is added asynchronously to prevent a potential
230
228
// deadlock on Windows. See fsnotify/fsnotify#502.
231
229
// Error encountered will be sent to internal error channel.
232
- if done := w .addWatchHandle (path ); done != nil {
233
- go w .watchDir (path , done , w .errs )
230
+ if done , release := w .addWatchHandle (path ); done != nil {
231
+ go func () {
232
+ w .errs <- w .watchDir (path , done )
233
+
234
+ // Only release after the error is sent.
235
+ release ()
236
+ }()
234
237
}
235
238
236
239
return & protocol.FileEvent {
@@ -273,13 +276,10 @@ func (w *Watcher) handleEvent(event fsnotify.Event) *protocol.FileEvent {
273
276
}
274
277
275
278
// watchDir registers a watch for a directory, retrying with backoff if it fails.
276
- // It can be canceled by calling removeWatchHandle. On success or cancellation,
277
- // nil is sent to 'errChan'; otherwise, the last error after all retries is sent.
278
- func (w * Watcher ) watchDir (path string , done chan struct {}, errChan chan error ) {
279
- if errChan == nil {
280
- panic ("input error chan is nil" )
281
- }
282
-
279
+ // It can be canceled by calling removeWatchHandle.
280
+ // Returns nil on success or cancellation; otherwise, the last error after all
281
+ // retries.
282
+ func (w * Watcher ) watchDir (path string , done chan struct {}) error {
283
283
// On darwin, watching a directory will fail if it contains broken symbolic
284
284
// links. This state can occur temporarily during operations like a git
285
285
// branch switch. To handle this, we retry multiple times with exponential
@@ -298,50 +298,49 @@ func (w *Watcher) watchDir(path string, done chan struct{}, errChan chan error)
298
298
err error
299
299
)
300
300
301
- // Watchers wait group becomes done only after errChan send.
302
- w .watchers .Add (1 )
303
- defer func () {
304
- errChan <- err
305
- w .watchers .Done ()
306
- }()
307
-
308
301
for i := range 5 {
309
302
if i > 0 {
310
303
select {
311
304
case <- time .After (delay ):
312
305
delay *= 2
313
306
case <- done :
314
- return // cancelled
307
+ return nil // cancelled
315
308
}
316
309
}
317
310
// This function may block due to fsnotify/fsnotify#502.
318
- err : = w .watcher .Add (path )
311
+ err = w .watcher .Add (path )
319
312
if afterAddHook != nil {
320
313
afterAddHook (path , err )
321
314
}
322
315
if err == nil {
323
- return
316
+ break
324
317
}
325
318
}
319
+
320
+ return err
326
321
}
327
322
328
323
var afterAddHook func (path string , err error )
329
324
330
325
// addWatchHandle registers a new directory watch.
331
- // The returned 'done' channel channel should be used to signal cancellation of
332
- // a pending watch.
326
+ // The returned 'done' channel should be used to signal cancellation of a
327
+ // pending watch, the release function should be called once watch registration
328
+ // is done.
333
329
// It returns nil if the watcher is already closing.
334
- func (w * Watcher ) addWatchHandle (path string ) chan struct {} {
330
+ func (w * Watcher ) addWatchHandle (path string ) ( done chan struct {}, release func ()) {
335
331
w .mu .Lock ()
336
332
defer w .mu .Unlock ()
337
333
338
334
if w .dirCancel == nil { // file watcher is closing.
339
- return nil
335
+ return nil , nil
340
336
}
341
337
342
- done : = make (chan struct {})
338
+ done = make (chan struct {})
343
339
w .dirCancel [path ] = done
344
- return done
340
+
341
+ w .watchers .Add (1 )
342
+
343
+ return done , w .watchers .Done
345
344
}
346
345
347
346
// removeWatchHandle removes the handle for a directory watch and cancels any
@@ -356,19 +355,6 @@ func (w *Watcher) removeWatchHandle(path string) {
356
355
}
357
356
}
358
357
359
- // close removes all handles and cancels all pending watch attempt for that path
360
- // and set dirCancel to nil which prevent any future watch attempts.
361
- func (w * Watcher ) close () {
362
- w .mu .Lock ()
363
- dirCancel := w .dirCancel
364
- w .dirCancel = nil
365
- w .mu .Unlock ()
366
-
367
- for _ , ch := range dirCancel {
368
- close (ch )
369
- }
370
- }
371
-
372
358
// isWatchedDir reports whether the given path has a watch handle, meaning it is
373
359
// a directory the watcher is managing.
374
360
func (w * Watcher ) isWatchedDir (path string ) bool {
@@ -409,13 +395,22 @@ func (w *Watcher) drainEvents() []protocol.FileEvent {
409
395
// Close shuts down the watcher, waits for the internal goroutine to terminate,
410
396
// and returns any final error.
411
397
func (w * Watcher ) Close () error {
398
+ // Set dirCancel to nil which prevent any future watch attempts.
399
+ w .mu .Lock ()
400
+ dirCancel := w .dirCancel
401
+ w .dirCancel = nil
402
+ w .mu .Unlock ()
403
+
412
404
// Cancel any ongoing watch registration.
413
- w .close ()
405
+ for _ , ch := range dirCancel {
406
+ close (ch )
407
+ }
414
408
415
409
// Wait for all watch registration goroutines to finish, including their
416
410
// error handling. This ensures that:
417
- // - All [Watcher.watchDir] goroutines have exited and sent their errors, so
418
- // it is safe to close the internal error channel.
411
+ // - All [Watcher.watchDir] goroutines have exited and it's error is sent
412
+ // to the internal error channel. So it is safe to close the internal
413
+ // error channel.
419
414
// - There are no ongoing [fsnotify.Watcher.Add] calls, so it is safe to
420
415
// close the fsnotify watcher (see fsnotify/fsnotify#704).
421
416
w .watchers .Wait ()
0 commit comments