mirror of
https://github.com/containers/gvisor-tap-vsock.git
synced 2025-10-19 07:05:35 +08:00
Use poll based file watcher
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 <fatanugraha@outlook.com>
This commit is contained in:
@@ -1,84 +1,63 @@
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
pollInterval: 5 * time.Second, // 5s is the default inode cache timeout in linux for most systems.
|
||||
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) {
|
||||
changeHandler()
|
||||
prevModTime = modTime
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) debounce(fn func()) {
|
||||
if fw.timer != nil {
|
||||
fw.timer.Stop()
|
||||
func (fw *FileWatcher) fileModTime(path string) time.Time {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
fw.timer = time.AfterFunc(fw.writeGracePeriod, fn)
|
||||
return info.ModTime()
|
||||
}
|
||||
|
||||
func (fw *FileWatcher) Stop() error {
|
||||
return fw.w.Close()
|
||||
func (fw *FileWatcher) Stop() {
|
||||
close(fw.closeCh)
|
||||
}
|
||||
|
Reference in New Issue
Block a user