-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fsnotify/fsnotify can't watch a folder that contains a symlink into a socket or named pipe. Use poll-based mechanism to watch the file for the time being until we find a better way or fix the issue in the upstream. Signed-off-by: Fata Nugraha <[email protected]>
- Loading branch information
1 parent
31b50f3
commit ca5d9ab
Showing
6 changed files
with
111 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,84 +1,75 @@ | ||
package utils | ||
|
||
import ( | ||
"fmt" | ||
"path/filepath" | ||
"os" | ||
"time" | ||
|
||
"github.com/fsnotify/fsnotify" | ||
) | ||
|
||
// FileWatcher is an utility that | ||
type FileWatcher struct { | ||
w *fsnotify.Watcher | ||
path string | ||
|
||
writeGracePeriod time.Duration | ||
timer *time.Timer | ||
closeCh chan struct{} | ||
pollInterval time.Duration | ||
changeGracePeriod time.Duration | ||
timer *time.Timer | ||
} | ||
|
||
func NewFileWatcher(path string) (*FileWatcher, error) { | ||
watcher, err := fsnotify.NewWatcher() | ||
if err != nil { | ||
return nil, err | ||
func NewFileWatcher(path string) *FileWatcher { | ||
return &FileWatcher{ | ||
path: path, | ||
changeGracePeriod: 200 * time.Millisecond, | ||
pollInterval: 1 * time.Second, | ||
closeCh: make(chan struct{}), | ||
} | ||
|
||
return &FileWatcher{w: watcher, path: path, writeGracePeriod: 200 * time.Millisecond}, nil | ||
} | ||
|
||
func (fw *FileWatcher) Start(changeHandler func()) error { | ||
// Ensure that the target that we're watching is not a symlink as we won't get any events when we're watching | ||
// a symlink. | ||
fileRealPath, err := filepath.EvalSymlinks(fw.path) | ||
if err != nil { | ||
return fmt.Errorf("adding watcher failed: %s", err) | ||
} | ||
|
||
// watch the directory instead of the individual file to ensure the notification still works when the file is modified | ||
// through moving/renaming rather than writing into it directly (like what most modern editor does by default). | ||
// ref: https://github.com/fsnotify/fsnotify/blob/a9bc2e01792f868516acf80817f7d7d7b3315409/README.md#watching-a-file-doesnt-work-well | ||
if err = fw.w.Add(filepath.Dir(fileRealPath)); err != nil { | ||
return fmt.Errorf("adding watcher failed: %s", err) | ||
} | ||
func (fw *FileWatcher) Start(changeHandler func()) { | ||
prevModTime := fw.fileModTime(fw.path) | ||
|
||
// use polling-based approach to detect file changes | ||
// we can't use fsnotify/fsnotify due to issues with symlink+socket. see #462. | ||
go func() { | ||
for { | ||
select { | ||
case _, ok := <-fw.w.Errors: | ||
if !ok { | ||
return // watcher is closed. | ||
} | ||
case event, ok := <-fw.w.Events: | ||
case _, ok := <-fw.closeCh: | ||
if !ok { | ||
return // watcher is closed. | ||
} | ||
case <-time.After(fw.pollInterval): | ||
} | ||
|
||
if event.Name != fileRealPath { | ||
continue // we don't care about this file. | ||
} | ||
modTime := fw.fileModTime(fw.path) | ||
if modTime.IsZero() { | ||
continue // file does not exists | ||
} | ||
|
||
// Create may not always followed by Write e.g. when we replace the file with mv. | ||
if event.Op.Has(fsnotify.Create) || event.Op.Has(fsnotify.Write) { | ||
// as per the documentation, receiving Write does not mean that the write is finished. | ||
// we try our best here to ignore "unfinished" write by assuming that after [writeGracePeriod] of | ||
// inactivity the write has been finished. | ||
fw.debounce(changeHandler) | ||
} | ||
if !prevModTime.Equal(modTime) { | ||
fw.debounce(changeHandler) | ||
prevModTime = modTime | ||
} | ||
} | ||
|
||
}() | ||
} | ||
|
||
func (fw *FileWatcher) fileModTime(path string) time.Time { | ||
info, err := os.Stat(path) | ||
if err != nil { | ||
return time.Time{} | ||
} | ||
|
||
return nil | ||
return info.ModTime() | ||
} | ||
|
||
func (fw *FileWatcher) debounce(fn func()) { | ||
if fw.timer != nil { | ||
fw.timer.Stop() | ||
} | ||
|
||
fw.timer = time.AfterFunc(fw.writeGracePeriod, fn) | ||
fw.timer = time.AfterFunc(fw.changeGracePeriod, fn) | ||
} | ||
|
||
func (fw *FileWatcher) Stop() error { | ||
return fw.w.Close() | ||
func (fw *FileWatcher) Stop() { | ||
close(fw.closeCh) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters