Compare commits

...

2 Commits

Author SHA1 Message Date
Preston Van Loon
53e33ef559 Changelog fragment 2025-09-10 15:14:59 -07:00
Preston Van Loon
fe96d226ea async: Add RunEveryDynamic function for dynamic interval scheduling 2025-09-10 15:14:59 -07:00
3 changed files with 72 additions and 0 deletions

View File

@@ -29,3 +29,24 @@ func RunEvery(ctx context.Context, period time.Duration, f func()) {
}
}()
}
// RunEveryDynamic runs the provided command periodically with a dynamic interval.
// The interval is determined by calling the intervalFunc before each execution.
// It runs in a goroutine, and can be cancelled by finishing the supplied context.
func RunEveryDynamic(ctx context.Context, intervalFunc func() time.Duration, f func()) {
go func() {
for {
// Get the next interval duration
interval := intervalFunc()
timer := time.NewTimer(interval)
select {
case <-timer.C:
f()
case <-ctx.Done():
timer.Stop()
return
}
}
}()
}

View File

@@ -38,3 +38,51 @@ func TestEveryRuns(t *testing.T) {
t.Error("Counter incremented after stop")
}
}
func TestEveryDynamicRuns(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
i := int32(0)
intervalCount := int32(0)
// Start with 50ms intervals, then increase to 100ms after 2 calls
async.RunEveryDynamic(ctx, func() time.Duration {
count := atomic.LoadInt32(&intervalCount)
atomic.AddInt32(&intervalCount, 1)
if count < 2 {
return 50 * time.Millisecond
}
return 100 * time.Millisecond
}, func() {
atomic.AddInt32(&i, 1)
})
// After 150ms, should have run at least 2 times (at 50ms and 100ms)
time.Sleep(150 * time.Millisecond)
count1 := atomic.LoadInt32(&i)
if count1 < 2 {
t.Errorf("Expected at least 2 runs after 150ms, got %d", count1)
}
// After another 150ms (total 300ms), should have run at least 3 times
// (50ms, 100ms, 150ms, 250ms)
time.Sleep(150 * time.Millisecond)
count2 := atomic.LoadInt32(&i)
if count2 < 3 {
t.Errorf("Expected at least 3 runs after 300ms, got %d", count2)
}
cancel()
// Sleep for a bit to let the cancel take place.
time.Sleep(100 * time.Millisecond)
last := atomic.LoadInt32(&i)
// Sleep for a bit and ensure the value has not increased.
time.Sleep(200 * time.Millisecond)
if atomic.LoadInt32(&i) != last {
t.Error("Counter incremented after stop")
}
}

View File

@@ -0,0 +1,3 @@
### Added
- async: Added a method for periodic execution with dynamic intervals. This is useful for a future progressive slot schedule.