Documentation
¶
Overview ¶
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 TaskFn invocation receives a context.Context derived from the parent with an independent deadline.
- Context-aware shutdown: Periodic.Start accepts a parent context; canceling it (or calling Periodic.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 WithInitialJitter to 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).
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Option ¶ added in v1.153.0
type Option func(*Periodic)
Option configures optional Periodic behavior. Pass options as the trailing arguments to New.
func WithInitialJitter ¶ added in v1.153.0
func WithInitialJitter() Option
WithInitialJitter delays the first invocation by a random duration in [0, jitter) instead of firing it immediately. Use it when many instances of a service start at once (a rolling deploy, a fleet-wide restart): with the eager default every replica's first call lands on the target simultaneously, which is the very thundering herd the per-tick jitter exists to prevent. It costs up to jitter of extra startup latency, once, and is a no-op when jitter is 0.
type Periodic ¶
type Periodic struct {
// contains filtered or unexported fields
}
Periodic schedules a TaskFn to run repeatedly with configurable interval and jitter. Create one with New, start it with Periodic.Start, and stop it with Periodic.Stop. The zero value is not usable; always use New.
func New ¶
func New(interval time.Duration, jitter time.Duration, timeout time.Duration, task TaskFn, opts ...Option) (*Periodic, error)
New constructs a Periodic scheduler with constraints on interval, jitter, timeout, and task validation. Optional Option values configure non-default behavior (see WithInitialJitter). Returns error if any parameter violates its constraint; call Start() to begin execution.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/tecnickcom/nurago/pkg/periodic"
)
func main() {
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
func (*Periodic) Start ¶
Start begins periodic task execution in a background goroutine. The first invocation fires almost immediately by default (or after up to jitter when constructed with WithInitialJitter); subsequent calls are at interval+rand(0,jitter) after each completion. The loop exits when ctx is canceled or Stop() is called. A second Start on an already-started or already-stopped instance is a no-op, so it never leaks a goroutine; the instance is single-use.
If ctx is canceled just before the first tick, the task may still run once (with the already-canceled context); a well-behaved task returns promptly.
func (*Periodic) Stop ¶
func (p *Periodic) Stop()
Stop cancels the execution loop and waits for the current task invocation to complete. Safe to call multiple times, before Start(), or concurrently with Start(): whatever the ordering, once Stop returns the scheduler is stopped and will not (re)start. A Stop that wins the race before Start prevents the loop from ever launching. The instance is single-use and cannot be restarted after Stop.
type TaskFn ¶
TaskFn is the function signature executed on each scheduler tick. The context passed in carries the deadline configured via the timeout parameter of New. Implementations should respect context cancellation and return promptly when ctx.Done() is closed.
The task runs in the scheduler's background goroutine with no panic recovery: a panic in the task crashes the process. Recover inside the task if it may panic and the scheduler must survive.