mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-19 04:40:52 +08:00
feat(watcher): debounce config reloads to prevent redundant operations
Introduce `scheduleConfigReload` with debounce functionality for config reloads, ensuring efficient handling of frequent changes. Added `stopConfigReloadTimer` for stopping timers during watcher shutdown.
This commit is contained in:
@@ -45,6 +45,8 @@ type Watcher struct {
|
|||||||
authDir string
|
authDir string
|
||||||
config *config.Config
|
config *config.Config
|
||||||
clientsMutex sync.RWMutex
|
clientsMutex sync.RWMutex
|
||||||
|
configReloadMu sync.Mutex
|
||||||
|
configReloadTimer *time.Timer
|
||||||
reloadCallback func(*config.Config)
|
reloadCallback func(*config.Config)
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
lastAuthHashes map[string]string
|
lastAuthHashes map[string]string
|
||||||
@@ -114,6 +116,7 @@ const (
|
|||||||
// replaceCheckDelay is a short delay to allow atomic replace (rename) to settle
|
// replaceCheckDelay is a short delay to allow atomic replace (rename) to settle
|
||||||
// before deciding whether a Remove event indicates a real deletion.
|
// before deciding whether a Remove event indicates a real deletion.
|
||||||
replaceCheckDelay = 50 * time.Millisecond
|
replaceCheckDelay = 50 * time.Millisecond
|
||||||
|
configReloadDebounce = 150 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWatcher creates a new file watcher instance
|
// NewWatcher creates a new file watcher instance
|
||||||
@@ -172,9 +175,19 @@ func (w *Watcher) Start(ctx context.Context) error {
|
|||||||
// Stop stops the file watcher
|
// Stop stops the file watcher
|
||||||
func (w *Watcher) Stop() error {
|
func (w *Watcher) Stop() error {
|
||||||
w.stopDispatch()
|
w.stopDispatch()
|
||||||
|
w.stopConfigReloadTimer()
|
||||||
return w.watcher.Close()
|
return w.watcher.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) stopConfigReloadTimer() {
|
||||||
|
w.configReloadMu.Lock()
|
||||||
|
if w.configReloadTimer != nil {
|
||||||
|
w.configReloadTimer.Stop()
|
||||||
|
w.configReloadTimer = nil
|
||||||
|
}
|
||||||
|
w.configReloadMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// SetConfig updates the current configuration
|
// SetConfig updates the current configuration
|
||||||
func (w *Watcher) SetConfig(cfg *config.Config) {
|
func (w *Watcher) SetConfig(cfg *config.Config) {
|
||||||
w.clientsMutex.Lock()
|
w.clientsMutex.Lock()
|
||||||
@@ -476,6 +489,42 @@ func (w *Watcher) handleEvent(event fsnotify.Event) {
|
|||||||
// Handle config file changes
|
// Handle config file changes
|
||||||
if isConfigEvent {
|
if isConfigEvent {
|
||||||
log.Debugf("config file change details - operation: %s, timestamp: %s", event.Op.String(), now.Format("2006-01-02 15:04:05.000"))
|
log.Debugf("config file change details - operation: %s, timestamp: %s", event.Op.String(), now.Format("2006-01-02 15:04:05.000"))
|
||||||
|
w.scheduleConfigReload()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle auth directory changes incrementally (.json only)
|
||||||
|
fmt.Printf("auth file changed (%s): %s, processing incrementally\n", event.Op.String(), filepath.Base(event.Name))
|
||||||
|
if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
w.addOrUpdateClient(event.Name)
|
||||||
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||||
|
// Atomic replace on some platforms may surface as Remove+Create for the target path.
|
||||||
|
// Wait briefly; if the file exists again, treat as update instead of removal.
|
||||||
|
time.Sleep(replaceCheckDelay)
|
||||||
|
if _, statErr := os.Stat(event.Name); statErr == nil {
|
||||||
|
// File exists after a short delay; handle as an update.
|
||||||
|
w.addOrUpdateClient(event.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.removeClient(event.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) scheduleConfigReload() {
|
||||||
|
w.configReloadMu.Lock()
|
||||||
|
defer w.configReloadMu.Unlock()
|
||||||
|
if w.configReloadTimer != nil {
|
||||||
|
w.configReloadTimer.Stop()
|
||||||
|
}
|
||||||
|
w.configReloadTimer = time.AfterFunc(configReloadDebounce, func() {
|
||||||
|
w.configReloadMu.Lock()
|
||||||
|
w.configReloadTimer = nil
|
||||||
|
w.configReloadMu.Unlock()
|
||||||
|
w.reloadConfigIfChanged()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) reloadConfigIfChanged() {
|
||||||
data, err := os.ReadFile(w.configPath)
|
data, err := os.ReadFile(w.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to read config file for hash check: %v", err)
|
log.Errorf("failed to read config file for hash check: %v", err)
|
||||||
@@ -510,24 +559,6 @@ func (w *Watcher) handleEvent(event fsnotify.Event) {
|
|||||||
w.clientsMutex.Unlock()
|
w.clientsMutex.Unlock()
|
||||||
w.persistConfigAsync()
|
w.persistConfigAsync()
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle auth directory changes incrementally (.json only)
|
|
||||||
fmt.Printf("auth file changed (%s): %s, processing incrementally\n", event.Op.String(), filepath.Base(event.Name))
|
|
||||||
if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
|
|
||||||
w.addOrUpdateClient(event.Name)
|
|
||||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
|
||||||
// Atomic replace on some platforms may surface as Remove+Create for the target path.
|
|
||||||
// Wait briefly; if the file exists again, treat as update instead of removal.
|
|
||||||
time.Sleep(replaceCheckDelay)
|
|
||||||
if _, statErr := os.Stat(event.Name); statErr == nil {
|
|
||||||
// File exists after a short delay; handle as an update.
|
|
||||||
w.addOrUpdateClient(event.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.removeClient(event.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reloadConfig reloads the configuration and triggers a full reload
|
// reloadConfig reloads the configuration and triggers a full reload
|
||||||
|
|||||||
Reference in New Issue
Block a user