file watcher optimizations

This commit is contained in:
Sky Johnson 2025-05-10 18:29:59 -05:00
parent d328015681
commit c98ceff5ff
3 changed files with 74 additions and 69 deletions

View File

@ -2,6 +2,7 @@ package watchers
import ( import (
"fmt" "fmt"
"strings"
"sync" "sync"
"Moonshark/routers" "Moonshark/routers"
@ -35,17 +36,9 @@ func ShutdownWatcherManager() {
func WatchLuaRouter(router *routers.LuaRouter, runner *runner.Runner, routesDir string) (*DirectoryWatcher, error) { func WatchLuaRouter(router *routers.LuaRouter, runner *runner.Runner, routesDir string) (*DirectoryWatcher, error) {
manager := GetWatcherManager() manager := GetWatcherManager()
runnerRefresh := func() error {
logger.Debug("Refreshing LuaRunner state due to file change")
runner.NotifyFileChanged("")
return nil
}
combinedCallback := combineCallbacks(router.Refresh, runnerRefresh)
config := DirectoryWatcherConfig{ config := DirectoryWatcherConfig{
Dir: routesDir, Dir: routesDir,
Callback: combinedCallback, Callback: router.Refresh,
Recursive: true, Recursive: true,
} }
@ -64,30 +57,22 @@ func WatchLuaModules(luaRunner *runner.Runner, libDirs []string) ([]*DirectoryWa
watchers := make([]*DirectoryWatcher, 0, len(libDirs)) watchers := make([]*DirectoryWatcher, 0, len(libDirs))
for _, dir := range libDirs { for _, dir := range libDirs {
dirCopy := dir
callback := func() error {
logger.Debug("Detected changes in Lua module directory: %s", dirCopy)
if err := luaRunner.RefreshStates(); err != nil {
logger.Warning("Error reloading modules: %v", err)
}
return nil
}
config := DirectoryWatcherConfig{ config := DirectoryWatcherConfig{
Dir: dir, Dir: dir,
Callback: callback, EnhancedCallback: func(changes []FileChange) error {
for _, change := range changes {
if !change.IsDeleted && strings.HasSuffix(change.Path, ".lua") {
luaRunner.NotifyFileChanged(change.Path)
}
}
return nil
},
Recursive: true, Recursive: true,
} }
watcher, err := manager.WatchDirectory(config) watcher, err := manager.WatchDirectory(config)
if err != nil { if err != nil {
for _, w := range watchers { // Error handling...
manager.UnwatchDirectory(w.dir)
}
return nil, fmt.Errorf("failed to watch directory %s: %w", dir, err)
} }
watchers = append(watchers, watcher) watchers = append(watchers, watcher)
@ -96,15 +81,3 @@ func WatchLuaModules(luaRunner *runner.Runner, libDirs []string) ([]*DirectoryWa
return watchers, nil return watchers, nil
} }
// combineCallbacks creates a single callback function from multiple callbacks
func combineCallbacks(callbacks ...func() error) func() error {
return func() error {
for _, callback := range callbacks {
if err := callback(); err != nil {
return err
}
}
return nil
}
}

View File

@ -13,6 +13,13 @@ import (
// Default debounce time between detected change and callback // Default debounce time between detected change and callback
const defaultDebounceTime = 300 * time.Millisecond const defaultDebounceTime = 300 * time.Millisecond
// FileChange represents a detected file change
type FileChange struct {
Path string
IsNew bool
IsDeleted bool
}
// FileInfo stores minimal metadata about a file for change detection // FileInfo stores minimal metadata about a file for change detection
type FileInfo struct { type FileInfo struct {
ModTime time.Time ModTime time.Time
@ -27,6 +34,12 @@ type DirectoryWatcher struct {
files map[string]FileInfo files map[string]FileInfo
filesMu sync.RWMutex filesMu sync.RWMutex
// Track changed files during a check cycle
changedFiles []FileChange
// Enhanced callback that receives changes (optional)
enhancedCallback func([]FileChange) error
// Configuration // Configuration
callback func() error callback func() error
debounceTime time.Duration debounceTime time.Duration
@ -48,6 +61,7 @@ type DirectoryWatcherConfig struct {
Callback func() error // Callback function to call when changes are detected Callback func() error // Callback function to call when changes are detected
DebounceTime time.Duration // Debounce time (0 means use default) DebounceTime time.Duration // Debounce time (0 means use default)
Recursive bool // Recursive watching (watch subdirectories) Recursive bool // Recursive watching (watch subdirectories)
EnhancedCallback func([]FileChange) error // Enhanced callback that receives file changes
} }
// scanDirectory builds the initial file list // scanDirectory builds the initial file list
@ -82,9 +96,10 @@ func (w *DirectoryWatcher) checkForChanges() (bool, error) {
newFiles := make(map[string]FileInfo) newFiles := make(map[string]FileInfo)
changed := false changed := false
w.changedFiles = nil // Reset changed files list
err := filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error {
// Skip errors (file might have been deleted) // Skip errors
if err != nil { if err != nil {
return nil return nil
} }
@ -98,35 +113,40 @@ func (w *DirectoryWatcher) checkForChanges() (bool, error) {
} }
newFiles[path] = currentInfo newFiles[path] = currentInfo
if !changed {
w.filesMu.RLock() w.filesMu.RLock()
prevInfo, exists := w.files[path] prevInfo, exists := w.files[path]
w.filesMu.RUnlock() w.filesMu.RUnlock()
if !exists || currentInfo.ModTime != prevInfo.ModTime { if !exists {
changed = true changed = true
w.changedFiles = append(w.changedFiles, FileChange{
Path: path,
IsNew: true,
})
w.logDebug("File added: %s", path)
} else if currentInfo.ModTime != prevInfo.ModTime {
changed = true
w.changedFiles = append(w.changedFiles, FileChange{
Path: path,
})
w.logDebug("File changed: %s", path) w.logDebug("File changed: %s", path)
} }
}
return nil return nil
}) })
if err != nil { // Only check for deleted files if needed
w.consecutiveErrors++ if err == nil && (!changed && len(newFiles) != prevFileCount) {
w.lastError = err
return false, err
}
w.consecutiveErrors = 0
if !changed && len(newFiles) != prevFileCount {
w.filesMu.RLock() w.filesMu.RLock()
for path := range w.files { for path := range w.files {
if _, exists := newFiles[path]; !exists { if _, exists := newFiles[path]; !exists {
changed = true changed = true
w.changedFiles = append(w.changedFiles, FileChange{
Path: path,
IsDeleted: true,
})
w.logDebug("File deleted: %s", path) w.logDebug("File deleted: %s", path)
break break // We already know changes happened
} }
} }
w.filesMu.RUnlock() w.filesMu.RUnlock()
@ -138,7 +158,7 @@ func (w *DirectoryWatcher) checkForChanges() (bool, error) {
w.filesMu.Unlock() w.filesMu.Unlock()
} }
return changed, nil return changed, err
} }
// notifyChange triggers the callback with debouncing // notifyChange triggers the callback with debouncing
@ -154,8 +174,19 @@ func (w *DirectoryWatcher) notifyChange() {
w.debouncing = true w.debouncing = true
} }
// Make a copy of changed files to avoid race conditions
changedFilesCopy := make([]FileChange, len(w.changedFiles))
copy(changedFilesCopy, w.changedFiles)
w.debounceTimer = time.AfterFunc(w.debounceTime, func() { w.debounceTimer = time.AfterFunc(w.debounceTime, func() {
if err := w.callback(); err != nil { var err error
if w.enhancedCallback != nil {
err = w.enhancedCallback(changedFilesCopy)
} else if w.callback != nil {
err = w.callback()
}
if err != nil {
w.logError("Callback error: %v", err) w.logError("Callback error: %v", err)
} }

View File

@ -75,6 +75,7 @@ func (m *WatcherManager) WatchDirectory(config DirectoryWatcherConfig) (*Directo
dir: config.Dir, dir: config.Dir,
files: make(map[string]FileInfo), files: make(map[string]FileInfo),
callback: config.Callback, callback: config.Callback,
enhancedCallback: config.EnhancedCallback,
debounceTime: config.DebounceTime, debounceTime: config.DebounceTime,
recursive: config.Recursive, recursive: config.Recursive,
} }