@@ -5,11 +5,10 @@ package config
55
66import (
77 "context"
8- "path/filepath "
8+ "os "
99 "sync"
1010 "time"
1111
12- "github.com/fsnotify/fsnotify"
1312 "github.com/pingcap/tiproxy/lib/config"
1413 "github.com/pingcap/tiproxy/lib/util/errors"
1514 "github.com/pingcap/tiproxy/lib/util/waitgroup"
@@ -22,6 +21,10 @@ const (
2221 pathPrefixConfig = "config"
2322)
2423
24+ const (
25+ checkFileInterval = time .Second
26+ )
27+
2528var (
2629 ErrNoResults = errors .Errorf ("has no results" )
2730 ErrFail2Update = errors .Errorf ("failed to update" )
@@ -39,9 +42,10 @@ type ConfigManager struct {
3942
4043 kv * btree.BTreeG [KVValue ]
4144
42- wch * fsnotify.Watcher
43- overlay []byte
44- sts struct {
45+ lastModTime time.Time
46+ checkFileInterval time.Duration
47+ overlay []byte
48+ sts struct {
4549 sync.Mutex
4650 listeners []chan <- * config.Config
4751 current * config.Config
@@ -50,7 +54,9 @@ type ConfigManager struct {
5054}
5155
5256func NewConfigManager () * ConfigManager {
53- return & ConfigManager {}
57+ return & ConfigManager {
58+ checkFileInterval : checkFileInterval ,
59+ }
5460}
5561
5662func (e * ConfigManager ) Init (ctx context.Context , logger * zap.Logger , configFile string , overlay * config.Config ) error {
@@ -69,69 +75,58 @@ func (e *ConfigManager) Init(ctx context.Context, logger *zap.Logger, configFile
6975 if overlay != nil {
7076 e .overlay , err = overlay .ToBytes ()
7177 if err != nil {
72- return errors . WithStack ( err )
78+ return err
7379 }
7480 }
7581
7682 if configFile != "" {
77- e .wch , err = fsnotify .NewWatcher ()
78- if err != nil {
79- return errors .WithStack (err )
83+ if err := e .checkFileAndLoad (configFile ); err != nil {
84+ return err
8085 }
81-
82- // Watch the parent dir, because vim/k8s or other apps may not edit files in-place:
83- // e.g. k8s configmap is a symlink of a symlink to a file, which will only trigger
84- // a remove event for the file.
85- parentDir := filepath .Dir (configFile )
86-
87- if err := e .reloadConfigFile (configFile ); err != nil {
88- return errors .WithStack (err )
89- }
90- if err := e .wch .Add (parentDir ); err != nil {
91- return errors .WithStack (err )
92- }
93-
9486 e .wg .Run (func () {
95- // Some apps will trigger rename/remove events, which means they will re-create/rename
96- // the new file to the directory. Watch possibly stopped after rename/remove events.
97- // So, we use a tick to repeatedly add the parent dir to re-watch files.
98- ticker := time .NewTicker (200 * time .Millisecond )
87+ var lastErr error
88+ ticker := time .NewTicker (e .checkFileInterval )
9989 for {
10090 select {
10191 case <- nctx .Done ():
10292 return
103- case err := <- e .wch .Errors :
104- e .logger .Warn ("failed to watch config file" , zap .Error (err ))
105- case ev := <- e .wch .Events :
106- e .handleFSEvent (ev , configFile )
10793 case <- ticker .C :
108- // There may be concurrent issues:
109- // 1. Remove the directory and the watcher removes the directory automatically
110- // 2. Create the directory and the file again within a tick
111- // 3. Add it to the watcher again, but the CREATE event is not sent
112- //
113- // Checking the watch list still have a concurrent issue because the watcher may remove the
114- // directory between WatchList and Add. We'll fix it later because it's complex to fix it entirely.
115- exists := len (e .wch .WatchList ()) > 0
116- if err := e .wch .Add (parentDir ); err != nil {
117- e .logger .Warn ("failed to rewatch config file" , zap .Error (err ))
118- } else if ! exists {
119- e .logger .Info ("config file reloaded" , zap .Error (e .reloadConfigFile (configFile )))
94+ // Do not report the same error to avoid log flooding.
95+ if err = e .checkFileAndLoad (configFile ); err != nil && errors .Is (err , lastErr ) {
96+ e .logger .Warn ("reload config file failed" , zap .Error (err ))
12097 }
98+ lastErr = err
12199 }
122100 }
123101 })
124102 } else {
125103 if err := e .SetTOMLConfig (nil ); err != nil {
126- return errors . WithStack ( err )
104+ return err
127105 }
128106 }
129107
130108 return nil
131109}
132110
111+ func (e * ConfigManager ) checkFileAndLoad (filename string ) error {
112+ info , err := os .Stat (filename )
113+ if err != nil {
114+ return errors .WithStack (err )
115+ }
116+ if info .IsDir () {
117+ return errors .New ("config file is a directory" )
118+ }
119+ if info .ModTime () != e .lastModTime {
120+ if err = e .reloadConfigFile (filename ); err != nil {
121+ return err
122+ }
123+ e .logger .Info ("config file reloaded" , zap .Time ("file_modify_time" , info .ModTime ()))
124+ e .lastModTime = info .ModTime ()
125+ }
126+ return nil
127+ }
128+
133129func (e * ConfigManager ) Close () error {
134- var wcherr error
135130 if e .cancel != nil {
136131 e .cancel ()
137132 e .cancel = nil
@@ -143,10 +138,5 @@ func (e *ConfigManager) Close() error {
143138 e .sts .listeners = nil
144139 e .sts .Unlock ()
145140 e .wg .Wait ()
146- // close after all goroutines are done
147- if e .wch != nil {
148- wcherr = e .wch .Close ()
149- e .wch = nil
150- }
151- return wcherr
141+ return nil
152142}
0 commit comments