Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/periodic"
Package periodic schedules a task function to run repeatedly at a fixed interval, with optional random jitter and a per-invocation context timeout.
New constructs a Periodic scheduler from an interval, a jitter ceiling, a
per-call timeout, and a TaskFn. Periodic.Start runs the task in a dedicated
goroutine and Periodic.Stop shuts it down:
p, err := periodic.New(
30*time.Second, // run every 30 s
5*time.Second, // add up to 5 s of random jitter
10*time.Second, // each call gets a 10 s deadline
myTask,
)
if err != nil {
log.Fatal(err)
}
p.Start(ctx)
defer p.Stop()
Behavior
- Fixed interval with random jitter: the pause between calls is interval + rand(0, jitter), spreading steady-state load across a fleet to avoid a thundering herd (https://en.wikipedia.org/wiki/Thundering_herd_problem).
- Per-call timeout: each
TaskFninvocation receives acontext.Contextderived from the parent with an independent deadline. - Context-aware shutdown:
Periodic.Startaccepts a parent context; canceling it (or callingPeriodic.Stop) stops the loop after the current task invocation returns, without leaking a goroutine. - Eager first execution: by default the first call fires after ~1 ns so the
task runs immediately on start rather than waiting for the first full
interval. This first call is not jittered, so a fleet that starts in
lockstep (a rolling deploy, a simultaneous restart) fires every replica’s
first call together; pass
WithInitialJitterto spread the first call across [0, jitter) as well.
Constraints
- interval must be > 0.
- jitter must be >= 0 (pass 0 to disable jitter entirely).
- timeout must be > 0.
- task must not be nil.
- task must not panic: it runs in the background goroutine with no recovery, so a panic crashes the process (recover inside the task if needed).
When To Use
- A background job refreshes a cache, prunes a table, or emits a heartbeat on a schedule.
- Multiple replicas run the same job and should not all fire at the same instant.
- A single slow run must not stall every subsequent run.
Example
count := make(chan int, 1)
count <- 0
// example task to execute periodically
task := func(_ context.Context) {
v := <-count
count <- (v + 1)
}
interval := 20 * time.Millisecond
jitter := 2 * time.Millisecond
timeout := 2 * time.Millisecond
// create a new periodic job
p, err := periodic.New(interval, jitter, timeout, task)
if err != nil {
close(count)
log.Fatal(err)
}
// start the periodic job
p.Start(context.TODO())
// wait for 3 times the interval
wait := 3 * interval
time.Sleep(wait)
// stop the periodic job
p.Stop()
fmt.Println(<-count)
close(count)
// Output:
// 3
Full source is in example_periodic_test.go. More runnable examples are on pkg.go.dev.
Dependencies
This package reaches no external module: it uses only the Go standard library.