test(watcher): add unit test for server update timer cancellation and immediate reload logic
- Add `TestTriggerServerUpdateCancelsPendingTimerOnImmediate` to verify proper handling of server update debounce and timer cancellation. - Fix logic in `triggerServerUpdate` to prevent duplicate timers and ensure proper cleanup of pending state.
This commit is contained in:
@@ -327,6 +327,11 @@ func (w *Watcher) triggerServerUpdate(cfg *config.Config) {
|
|||||||
w.serverUpdateMu.Lock()
|
w.serverUpdateMu.Lock()
|
||||||
if w.serverUpdateLast.IsZero() || now.Sub(w.serverUpdateLast) >= serverUpdateDebounce {
|
if w.serverUpdateLast.IsZero() || now.Sub(w.serverUpdateLast) >= serverUpdateDebounce {
|
||||||
w.serverUpdateLast = now
|
w.serverUpdateLast = now
|
||||||
|
if w.serverUpdateTimer != nil {
|
||||||
|
w.serverUpdateTimer.Stop()
|
||||||
|
w.serverUpdateTimer = nil
|
||||||
|
}
|
||||||
|
w.serverUpdatePend = false
|
||||||
w.serverUpdateMu.Unlock()
|
w.serverUpdateMu.Unlock()
|
||||||
w.reloadCallback(cfg)
|
w.reloadCallback(cfg)
|
||||||
return
|
return
|
||||||
@@ -344,26 +349,33 @@ func (w *Watcher) triggerServerUpdate(cfg *config.Config) {
|
|||||||
w.serverUpdatePend = true
|
w.serverUpdatePend = true
|
||||||
if w.serverUpdateTimer != nil {
|
if w.serverUpdateTimer != nil {
|
||||||
w.serverUpdateTimer.Stop()
|
w.serverUpdateTimer.Stop()
|
||||||
|
w.serverUpdateTimer = nil
|
||||||
}
|
}
|
||||||
w.serverUpdateTimer = time.AfterFunc(delay, func() {
|
var timer *time.Timer
|
||||||
|
timer = time.AfterFunc(delay, func() {
|
||||||
if w.stopped.Load() {
|
if w.stopped.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.clientsMutex.RLock()
|
w.clientsMutex.RLock()
|
||||||
latestCfg := w.config
|
latestCfg := w.config
|
||||||
w.clientsMutex.RUnlock()
|
w.clientsMutex.RUnlock()
|
||||||
|
|
||||||
|
w.serverUpdateMu.Lock()
|
||||||
|
if w.serverUpdateTimer != timer || !w.serverUpdatePend {
|
||||||
|
w.serverUpdateMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.serverUpdateTimer = nil
|
||||||
|
w.serverUpdatePend = false
|
||||||
if latestCfg == nil || w.reloadCallback == nil || w.stopped.Load() {
|
if latestCfg == nil || w.reloadCallback == nil || w.stopped.Load() {
|
||||||
w.serverUpdateMu.Lock()
|
|
||||||
w.serverUpdatePend = false
|
|
||||||
w.serverUpdateMu.Unlock()
|
w.serverUpdateMu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.serverUpdateMu.Lock()
|
|
||||||
w.serverUpdateLast = time.Now()
|
w.serverUpdateLast = time.Now()
|
||||||
w.serverUpdatePend = false
|
|
||||||
w.serverUpdateMu.Unlock()
|
w.serverUpdateMu.Unlock()
|
||||||
w.reloadCallback(latestCfg)
|
w.reloadCallback(latestCfg)
|
||||||
})
|
})
|
||||||
|
w.serverUpdateTimer = timer
|
||||||
w.serverUpdateMu.Unlock()
|
w.serverUpdateMu.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -441,6 +441,46 @@ func TestRemoveClientRemovesHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTriggerServerUpdateCancelsPendingTimerOnImmediate(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
cfg := &config.Config{AuthDir: tmpDir}
|
||||||
|
|
||||||
|
var reloads int32
|
||||||
|
w := &Watcher{
|
||||||
|
reloadCallback: func(*config.Config) {
|
||||||
|
atomic.AddInt32(&reloads, 1)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
w.SetConfig(cfg)
|
||||||
|
|
||||||
|
w.serverUpdateMu.Lock()
|
||||||
|
w.serverUpdateLast = time.Now().Add(-(serverUpdateDebounce - 100*time.Millisecond))
|
||||||
|
w.serverUpdateMu.Unlock()
|
||||||
|
w.triggerServerUpdate(cfg)
|
||||||
|
|
||||||
|
if got := atomic.LoadInt32(&reloads); got != 0 {
|
||||||
|
t.Fatalf("expected no immediate reload, got %d", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.serverUpdateMu.Lock()
|
||||||
|
if !w.serverUpdatePend || w.serverUpdateTimer == nil {
|
||||||
|
w.serverUpdateMu.Unlock()
|
||||||
|
t.Fatal("expected a pending server update timer")
|
||||||
|
}
|
||||||
|
w.serverUpdateLast = time.Now().Add(-(serverUpdateDebounce + 10*time.Millisecond))
|
||||||
|
w.serverUpdateMu.Unlock()
|
||||||
|
|
||||||
|
w.triggerServerUpdate(cfg)
|
||||||
|
if got := atomic.LoadInt32(&reloads); got != 1 {
|
||||||
|
t.Fatalf("expected immediate reload once, got %d", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
if got := atomic.LoadInt32(&reloads); got != 1 {
|
||||||
|
t.Fatalf("expected pending timer to be cancelled, got %d reloads", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldDebounceRemove(t *testing.T) {
|
func TestShouldDebounceRemove(t *testing.T) {
|
||||||
w := &Watcher{}
|
w := &Watcher{}
|
||||||
path := filepath.Clean("test.json")
|
path := filepath.Clean("test.json")
|
||||||
|
|||||||
Reference in New Issue
Block a user