From 2413cbcd8064af94ceefe6d736202d2314035fc5 Mon Sep 17 00:00:00 2001 From: Shu Xian Date: Mon, 19 Aug 2019 15:28:24 +0800 Subject: [PATCH] fix file watcher event bug on Linux the watcher can not normally get events of file changes on linux. it just can get the first two changes. --- config/default_test.go | 60 ++++++++++++++++++++++++ config/source/file/watcher.go | 2 + config/source/file/watcher_linux.go | 72 +++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 config/source/file/watcher_linux.go diff --git a/config/default_test.go b/config/default_test.go index 8a6adc73..fd1933f4 100644 --- a/config/default_test.go +++ b/config/default_test.go @@ -1,6 +1,7 @@ package config import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -8,10 +9,15 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/micro/go-micro/config/source/env" "github.com/micro/go-micro/config/source/file" ) +var ( + sep = string(os.PathSeparator) +) + func createFileForIssue18(t *testing.T, content string) *os.File { data := []byte(content) path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano())) @@ -116,3 +122,57 @@ func TestConfigMerge(t *testing.T) { actualHost) } } + +func TestFileChange(t *testing.T) { + // create a temp file + fileName := uuid.New().String() + "testWatcher.json" + f, err := os.OpenFile("."+sep+fileName, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Error(err) + } + defer f.Close() + defer os.Remove("." + sep + fileName) + + // load the file + if err := Load(file.NewSource( + file.WithPath("." + sep + fileName), + )); err != nil { + t.Error(err) + } + + // watch changes + watcher, err := Watch() + if err != nil { + t.Error(err) + } + changeTimes := 0 + go func() { + for { + v, err := watcher.Next() + if err != nil { + t.Error(err) + return + } + changeTimes++ + t.Logf("file changeļ¼Œ%s", string(v.Bytes())) + } + }() + + content := map[int]string{} + // change the file + for i := 0; i < 5; i++ { + content[i] = time.Now().String() + bytes, _ := json.Marshal(content) + f.Truncate(0) + f.Seek(0, 0) + if _, err := f.Write(bytes); err != nil { + t.Error(err) + } + + time.Sleep(time.Second) + } + + if changeTimes != 4 { + t.Error(fmt.Errorf("watcher error: change times %d is not enough", changeTimes)) + } +} diff --git a/config/source/file/watcher.go b/config/source/file/watcher.go index de28b07b..76ecd260 100644 --- a/config/source/file/watcher.go +++ b/config/source/file/watcher.go @@ -1,3 +1,5 @@ +//+build !linux + package file import ( diff --git a/config/source/file/watcher_linux.go b/config/source/file/watcher_linux.go new file mode 100644 index 00000000..3f48a00b --- /dev/null +++ b/config/source/file/watcher_linux.go @@ -0,0 +1,72 @@ +//+build linux + +package file + +import ( + "errors" + "os" + + "github.com/fsnotify/fsnotify" + "github.com/micro/go-micro/config/source" +) + +type watcher struct { + f *file + + fw *fsnotify.Watcher + exit chan bool +} + +func newWatcher(f *file) (source.Watcher, error) { + fw, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + fw.Add(f.path) + + return &watcher{ + f: f, + fw: fw, + exit: make(chan bool), + }, nil +} + +func (w *watcher) Next() (*source.ChangeSet, error) { + // is it closed? + select { + case <-w.exit: + return nil, errors.New("watcher stopped") + default: + } + + // try get the event + select { + case event, _ := <-w.fw.Events: + if event.Op == fsnotify.Rename { + // check existence of file, and add watch again + _, err := os.Stat(event.Name) + if err == nil || os.IsExist(err) { + w.fw.Add(event.Name) + } + } + + c, err := w.f.Read() + if err != nil { + return nil, err + } + + // add path again for the event bug of fsnotify + w.fw.Add(w.f.path) + + return c, nil + case err := <-w.fw.Errors: + return nil, err + case <-w.exit: + return nil, errors.New("watcher stopped") + } +} + +func (w *watcher) Stop() error { + return w.fw.Close() +}