From 356eb9cec1eb17fbc57a3fd81565560b5a281b70 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sun, 5 Apr 2026 02:04:03 +0000 Subject: [PATCH] fix(proxy): use mtime-based config watching Co-Authored-By: Virgil --- configwatcher_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ core_impl.go | 33 +++++++++++---------------------- proxy.go | 7 ++----- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/configwatcher_test.go b/configwatcher_test.go index a79a8fb..5207694 100644 --- a/configwatcher_test.go +++ b/configwatcher_test.go @@ -48,6 +48,10 @@ func TestConfigWatcher_Start_Good(t *testing.T) { if err := os.WriteFile(path, updated, 0o644); err != nil { t.Fatalf("write updated config file: %v", err) } + now := time.Now() + if err := os.Chtimes(path, now, now.Add(2*time.Second)); err != nil { + t.Fatalf("touch updated config file: %v", err) + } select { case cfg := <-updates: @@ -61,3 +65,42 @@ func TestConfigWatcher_Start_Good(t *testing.T) { t.Fatal("expected watcher to reload updated config") } } + +func TestConfigWatcher_Start_Ugly(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.json") + initial := []byte(`{"mode":"nicehash","workers":"false","bind":[{"host":"127.0.0.1","port":3333}],"pools":[{"url":"pool.example:3333","enabled":true}]}`) + if err := os.WriteFile(path, initial, 0o644); err != nil { + t.Fatalf("write initial config file: %v", err) + } + + updates := make(chan *Config, 1) + watcher := NewConfigWatcher(path, func(cfg *Config) { + select { + case updates <- cfg: + default: + } + }) + if watcher == nil { + t.Fatal("expected watcher") + } + watcher.Start() + defer watcher.Stop() + + now := time.Now() + if err := os.Chtimes(path, now, now.Add(2*time.Second)); err != nil { + t.Fatalf("touch config file: %v", err) + } + + select { + case cfg := <-updates: + if cfg == nil { + t.Fatal("expected config update") + } + if got := cfg.Mode; got != "nicehash" { + t.Fatalf("expected unchanged mode, got %q", got) + } + case <-time.After(5 * time.Second): + t.Fatal("expected watcher to reload touched config") + } +} diff --git a/core_impl.go b/core_impl.go index 4dd9367..83c269d 100644 --- a/core_impl.go +++ b/core_impl.go @@ -364,9 +364,6 @@ func NewConfigWatcher(configPath string, onChange func(*Config)) *ConfigWatcher onChange: onChange, done: make(chan struct{}), } - if data, err := os.ReadFile(configPath); err == nil { - watcher.lastSum = sha256.Sum256(data) - } if info, err := os.Stat(configPath); err == nil { watcher.lastMod = info.ModTime() } @@ -392,28 +389,20 @@ func (w *ConfigWatcher) Start() { for { select { case <-ticker.C: - data, err := os.ReadFile(w.path) - if err != nil { - continue - } - sum := sha256.Sum256(data) - w.mu.Lock() - changed := sum != w.lastSum - if changed { - w.lastSum = sum - } - w.mu.Unlock() - if !changed { - continue - } if info, err := os.Stat(w.path); err == nil { w.mu.Lock() - w.lastMod = info.ModTime() + changed := info.ModTime() != w.lastMod + if changed { + w.lastMod = info.ModTime() + } w.mu.Unlock() - } - config, result := LoadConfig(w.path) - if result.OK && config != nil { - w.onChange(config) + if !changed { + continue + } + config, result := LoadConfig(w.path) + if result.OK && config != nil { + w.onChange(config) + } } case <-w.done: return diff --git a/proxy.go b/proxy.go index 7ec9281..1f9f30e 100644 --- a/proxy.go +++ b/proxy.go @@ -110,16 +110,13 @@ type CloseEvent struct { Miner *Miner } -// ConfigWatcher polls a config file for changes. +// ConfigWatcher polls a config file every second and reloads on modification. // -// watcher := proxy.NewConfigWatcher("config.json", func(cfg *proxy.Config) { -// p.Reload(cfg) -// }) +// watcher := proxy.NewConfigWatcher("config.json", func(cfg *proxy.Config) { p.Reload(cfg) }) type ConfigWatcher struct { path string onChange func(*Config) lastMod time.Time - lastSum [32]byte done chan struct{} mu sync.Mutex started bool