diff --git a/services/search/pkg/search/debouncer.go b/services/search/pkg/search/debouncer.go index e7accc1f2b..2849c66a77 100644 --- a/services/search/pkg/search/debouncer.go +++ b/services/search/pkg/search/debouncer.go @@ -24,7 +24,7 @@ type workItem struct { t *time.Timer timeout *time.Timer - trigger func() + work func() } type AckFunc func() error @@ -54,7 +54,8 @@ func (d *SpaceDebouncer) Debounce(id *provider.StorageSpaceId, ack AckFunc) { return } - trigger := func() { + wi := &workItem{} + wi.work = func() { if _, ok := d.inProgress.Load(id.OpaqueId); ok { // Reschedule this run for when the previous run has finished d.mutex.Lock() @@ -66,9 +67,12 @@ func (d *SpaceDebouncer) Debounce(id *provider.StorageSpaceId, ack AckFunc) { } d.mutex.Lock() + wi.timeout.Stop() // stop the timeout timer if it is running delete(d.pending, id.OpaqueId) d.inProgress.Store(id.OpaqueId, true) - defer d.inProgress.Delete(id.OpaqueId) + defer func() { + d.inProgress.Delete(id.OpaqueId) + }() d.mutex.Unlock() // release the lock early to allow other goroutines to debounce d.f(id) @@ -80,16 +84,13 @@ func (d *SpaceDebouncer) Debounce(id *provider.StorageSpaceId, ack AckFunc) { } }() } - t := time.AfterFunc(d.after, trigger) + wi.t = time.AfterFunc(d.after, wi.work) + wi.timeout = time.AfterFunc(d.timeout, func() { + d.log.Debug().Msg("timeout while waiting for space debouncer to finish") + wi.t.Stop() + wi.work() + }) - d.pending[id.OpaqueId] = &workItem{ - trigger: trigger, - t: t, - timeout: time.AfterFunc(d.timeout, func() { - d.log.Debug().Msg("timeout while waiting for space debouncer to finish") - t.Stop() - trigger() - }), - } + d.pending[id.OpaqueId] = wi } diff --git a/services/search/pkg/search/debouncer_test.go b/services/search/pkg/search/debouncer_test.go index 829ebdb5e9..08c26f3936 100644 --- a/services/search/pkg/search/debouncer_test.go +++ b/services/search/pkg/search/debouncer_test.go @@ -115,6 +115,26 @@ var _ = Describe("SpaceDebouncer", func() { }, "300ms").Should(Equal(1)) }) + It("doesn't run the timeout function if the work function has been called", func() { + debouncer = search.NewSpaceDebouncer(100*time.Millisecond, 250*time.Millisecond, func(id *sprovider.StorageSpaceId) { + if id.OpaqueId == "spaceid" { + callCount.Add(1) + } + }, log.NewLogger()) + + // Initial call to start the timers + debouncer.Debounce(spaceid, nil) + + // Wait for the debounce timer to fire + Eventually(func() int { + return int(callCount.Load()) + }, "200ms").Should(Equal(1)) + + // The timeout function should not be called + time.Sleep(300 * time.Millisecond) + Expect(int(callCount.Load())).To(Equal(1)) + }) + It("calls the ack function when the debounce fires", func() { var ackCalled atomic.Bool ackFunc := func() error {