From d78ac897f4751a045b76ac19204663071d6c7ac2 Mon Sep 17 00:00:00 2001 From: xgfone Date: Thu, 12 Oct 2023 23:08:08 +0800 Subject: [PATCH] use interval poll instead of fsnotify --- go.mod | 3 --- go.sum | 4 --- source_file.go | 72 +++++++++++++------------------------------------- source_test.go | 45 +++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 61 deletions(-) diff --git a/go.mod b/go.mod index 19c41bc..551e49f 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,8 @@ module github.com/xgfone/gconf/v6 require ( - github.com/fsnotify/fsnotify v1.6.0 github.com/xgfone/go-cast v0.8.1 github.com/xgfone/go-defaults v0.13.0 ) -require golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect - go 1.18 diff --git a/go.sum b/go.sum index 75681f8..efe4f30 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,4 @@ -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/xgfone/go-cast v0.8.1 h1:x80Qu+XCUyQoFvCo2j+CFRiKiJydF11jeAJRzRtGY9U= github.com/xgfone/go-cast v0.8.1/go.mod h1:aHO9rXhmN4IZ4d1UG35+6WEVbg5yyISynFQJCVltrsk= github.com/xgfone/go-defaults v0.13.0 h1:aJX/RJSI8yN6Xxn1b1NlFQyClwION2DM5X1NDz3KQ0U= github.com/xgfone/go-defaults v0.13.0/go.mod h1:4qxXP2vvK8n2csVwYmFbhbQAISq5s/2zYZE9CKYj/bw= -golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/source_file.go b/source_file.go index 0a1da9c..5460664 100644 --- a/source_file.go +++ b/source_file.go @@ -21,8 +21,6 @@ import ( "path/filepath" "strings" "time" - - "github.com/fsnotify/fsnotify" ) // ConfigFileOpt is the default option for the configuration file. @@ -47,13 +45,14 @@ func NewFileSource(filename string, defaultFormat ...string) Source { } id := fmt.Sprintf("file:%s", filename) - return fileSource{id: id, filepath: filename, format: format} + return fileSource{id: id, filepath: filename, format: format, timeout: time.Second * 10} } type fileSource struct { id string format string filepath string + timeout time.Duration } func (f fileSource) String() string { return f.id } @@ -94,19 +93,9 @@ func (f fileSource) Watch(exit <-chan struct{}, load func(DataSet, error) bool) } func (f fileSource) watch(exit <-chan struct{}, load func(DataSet, error) bool) { - fw, err := fsnotify.NewWatcher() - if err != nil { - load(DataSet{Source: f.id, Format: f.format}, err) - return - } - defer fw.Close() - - var add bool - if fw.Add(f.filepath) == nil { - add = true - } + lastsize, lasttime, _ := getfileinfo(f.filepath) - ticker := time.NewTicker(time.Minute) + ticker := time.NewTicker(f.timeout) defer ticker.Stop() for { @@ -115,49 +104,24 @@ func (f fileSource) watch(exit <-chan struct{}, load func(DataSet, error) bool) return case <-ticker.C: - if _, err := os.Stat(f.filepath); err != nil { - if os.IsNotExist(err) { - if add { - if err := fw.Remove(f.filepath); err != nil { - load(DataSet{Source: f.id, Format: f.format}, err) - } else { - add = false - } - } - } else { + if size, time, err := getfileinfo(f.filepath); err != nil { + if !os.IsNotExist(err) { load(DataSet{Source: f.id, Format: f.format}, err) } - continue - } - - if !add { - if err := fw.Add(f.filepath); err != nil { - load(DataSet{Source: f.id, Format: f.format}, err) - } else { - add = true - load(f.Read()) - } - continue - } - - case event, ok := <-fw.Events: - if !ok { - return - } - - // BUG: it will be triggered twice continuously by fsnotify on Windows. - if event.Op&fsnotify.Write == fsnotify.Write { + } else if size != lastsize || time != lasttime { load(f.Read()) - } else if event.Op&fsnotify.Remove == fsnotify.Remove { - add = false - } - - case err, ok := <-fw.Errors: - if !ok { - return + lastsize = size + lasttime = time } - - load(DataSet{Source: f.id, Format: f.format}, err) } } } + +func getfileinfo(filepath string) (size, time int64, err error) { + fi, err := os.Stat(filepath) + if err == nil { + time = fi.ModTime().Unix() + size = fi.Size() + } + return +} diff --git a/source_test.go b/source_test.go index 6acb1ff..32a1e64 100644 --- a/source_test.go +++ b/source_test.go @@ -143,6 +143,51 @@ func TestNewFileSource_JSON(t *testing.T) { } } +func TestFileSourceWatch(t *testing.T) { + // Prepare the json file + filename := "_test_file_source_watch_.json" + defer os.Remove(filename) + + source := NewFileSource(filename).(fileSource) + source.timeout = time.Second * 2 + + exit := make(chan struct{}) + go func() { + time.Sleep(time.Second) + file, err := os.OpenFile(filename, testfileflag, os.ModePerm) + if err != nil { + t.Error(err) + } else { + _, _ = file.Write([]byte(`{"opt": 1}`)) + file.Close() + } + time.Sleep(time.Second * 2) + close(exit) + }() + + var data string + start := time.Now() + source.Watch(exit, func(ds DataSet, err error) bool { + if err != nil { + t.Error(err) + } else if data == "" { + data = string(ds.Data) + } else { + t.Fail() + } + return true + }) + + if cost := time.Since(start); cost < time.Second*3 || cost > time.Second*4 { + t.Errorf("not wait for 3~4s") + } + + expect := `{"opt": 1}` + if data != expect { + t.Errorf("expect '%s', but got '%s'", expect, data) + } +} + func TestNewURLSource(t *testing.T) { first := true