Documentation
ΒΆ
Overview ΒΆ
Package cron schedules context-aware jobs from cron expressions or programmatic Schedule implementations.
Lifecycle ΒΆ
New builds a scheduler from options; Cron.Add and Cron.AddSchedule register jobs and return a stable EntryID; Cron.Start launches the loop. Entries can be added, removed, paused, resumed and updated before or after Start; Cron.Trigger needs a running scheduler. The loop goroutine only pops due entries: Schedule.Next, distributed coordination and jobs each run on their own goroutines, so one slow schedule or job never delays another entry.
Cron.Stop cancels in-flight jobs (context cause ErrCronStopping) and waits for them; Cron.Drain stops scheduling and lets running jobs finish. Both are bounded by the supplied context. A stopped scheduler cannot be restarted.
Schedules ΒΆ
The built-in parser accepts five-field specs (minute hour dom month dow), an optional leading seconds field (see WithSecondsField), descriptors such as "@hourly" and "@every 10s", and a "TZ=" or "CRON_TZ=" prefix. Programmatic schedules include ConstantDelay, AlignedDelay, OnceAt, TriggeredSchedule, Union and Filter. Quartz day tokens (L, W, #) live in the parserext subpackage.
Missed fires ΒΆ
A fire that runs later than WithMissedTolerance (one minute by default) invokes the entry's MissedFirePolicy: MissedRunOnce (the default) runs the most recent missed instant once, MissedRunAll replays the backlog and MissedSkip drops it. Seed WithLastRun from persisted state to catch up across restarts.
Distributed coordination ΒΆ
WithClaimer elects one replica per keyed fire and WithElector restricts automatic fires to the current leader. Fire keys combine WithKey with the UTC scheduled instant, so replicas in different time zones agree. Manual triggers bypass coordination. Redis and PostgreSQL backends are separate modules under coordination/.
Events ΒΆ
WithObservers delivers Event values asynchronously through a bounded, lossy queue; WithRecorder receives the same events inline for metrics. Prometheus and OpenTelemetry integrations live under contrib/.
Index ΒΆ
- Variables
- func Between(s Schedule, start, end time.Time) iter.Seq[time.Time]
- func IsTriggered(s Schedule) bool
- func NextN(s Schedule, from time.Time, n int) []time.Time
- func ValidateSpec(spec string) error
- func ValidateSpecWith(spec string, p Parser) error
- type AlignedDelay
- type CanceledFireEvent
- type Claimer
- type ConstantDelay
- type Cron
- func (c *Cron) Add(spec string, j Job, opts ...EntryOption) (EntryID, error)
- func (c *Cron) AddSchedule(s Schedule, j Job, opts ...EntryOption) (EntryID, error)
- func (c *Cron) Drain(ctx context.Context) error
- func (c *Cron) Entries() iter.Seq[Entry]
- func (c *Cron) Entry(id EntryID) (Entry, bool)
- func (c *Cron) Pause(id EntryID) error
- func (c *Cron) Remove(id EntryID) error
- func (c *Cron) Resume(id EntryID) error
- func (c *Cron) Running() bool
- func (c *Cron) Start() error
- func (c *Cron) Stop(ctx context.Context) error
- func (c *Cron) Trigger(id EntryID) error
- func (c *Cron) TriggerAndWait(ctx context.Context, id EntryID) error
- func (c *Cron) TriggerByName(name string) (int, error)
- func (c *Cron) Update(id EntryID, spec string) error
- func (c *Cron) UpdateSchedule(id EntryID, s Schedule) error
- type Elector
- type Entry
- type EntryID
- type EntryInfo
- type EntryOption
- func WithEntryChain(wrappers ...Wrapper) EntryOption
- func WithEntryClaimer(claimer Claimer) EntryOption
- func WithEntryJitter(max time.Duration) EntryOption
- func WithEntryMissedFire(p MissedFirePolicy) EntryOption
- func WithEntryRetry(p RetryPolicy) EntryOption
- func WithKey(key string) EntryOption
- func WithLastRun(t time.Time) EntryOption
- func WithName(name string) EntryOption
- func WithTimeout(d time.Duration) EntryOption
- type EntryRef
- type Event
- type Job
- type JobCompleteEvent
- type JobFunc
- type JobStartEvent
- type MissedFireEvent
- type MissedFirePolicy
- type Observer
- type ObserverDropEvent
- type ObserverFunc
- type Option
- func WithBaseContext(ctx context.Context) Option
- func WithChain(wrappers ...Wrapper) Option
- func WithClaimer(claimer Claimer) Option
- func WithElector(e Elector) Option
- func WithJitter(max time.Duration) Option
- func WithLocation(loc *time.Location) Option
- func WithLogger(l *slog.Logger) Option
- func WithMaxConcurrent(n int) Option
- func WithMaxEntries(n int) Option
- func WithMissedFire(p MissedFirePolicy) Option
- func WithMissedTolerance(d time.Duration) Option
- func WithObserverBuffer(n int) Option
- func WithObservers(observers ...Observer) Option
- func WithParser(p Parser) Option
- func WithRecorder(r Recorder) Option
- func WithRetry(p RetryPolicy) Option
- func WithSecondsField() Option
- func WithoutRecover() Option
- type ParseError
- type Parser
- type ParserOption
- type QueueDepthEvent
- type Recorder
- type RecorderFunc
- type RejectReason
- type RejectedFireEvent
- type RetryOption
- type RetryPolicy
- type Schedule
- type ScheduleEvent
- type SkipReason
- type SkippedFireEvent
- type SpecAnalysis
- type SpecSchedule
- type StandardParser
- type Upcoming
- type Wrapper
Examples ΒΆ
Constants ΒΆ
This section is empty.
Variables ΒΆ
var ( // ErrCapacityReached is returned by Add and AddSchedule when the // WithMaxEntries limit is reached. ErrCapacityReached = errors.New("cron: capacity reached") // ErrAlreadyRunning is returned by wrap.SkipIfRunning when an invocation // overlaps one still in progress. ErrAlreadyRunning = errors.New("cron: job already running") // ErrJobTimeout is the context cancellation cause when WithTimeout or // wrap.Timeout expires; read it with context.Cause. ErrJobTimeout = errors.New("cron: job timeout") // ErrCronStopping is the context cancellation cause for jobs cancelled by // Stop; read it with context.Cause. ErrCronStopping = errors.New("cron: scheduler stopping") // ErrEntryNotFound is returned by Remove, Pause, Resume, Update, // UpdateSchedule, Trigger and TriggerAndWait for an unknown or removed id. ErrEntryNotFound = errors.New("cron: entry not found") // ErrSchedulerNotRunning is returned by Trigger, TriggerAndWait and // TriggerByName before Start or after Stop and Drain. ErrSchedulerNotRunning = errors.New("cron: scheduler not running") // ErrConcurrencyLimit is returned by Trigger when WithMaxConcurrent has no // free slot. Automatic fires publish RejectedFireEvent instead. ErrConcurrencyLimit = errors.New("cron: max concurrent reached") // ErrSchedulerStopped is returned by Start after Stop or Drain. ErrSchedulerStopped = errors.New("cron: scheduler stopped") // ErrNilJob is returned by Add and AddSchedule for a nil Job, including a // typed nil, or when the wrapper chain produced one. ErrNilJob = errors.New("cron: nil job") // ErrNilSchedule is returned when a Parser produces no Schedule or when // AddSchedule or UpdateSchedule receives a nil one. ErrNilSchedule = errors.New("cron: nil schedule") // ErrJobPanic wraps the recovered panic value in a job's result unless // WithoutRecover is set. ErrJobPanic = errors.New("cron: job panicked") // ErrClaimerRequiresKey is returned by Add and AddSchedule when a Claimer // applies to an entry registered without WithKey. ErrClaimerRequiresKey = errors.New("cron: distributed claimer requires WithKey") // ErrDuplicateKey is returned, wrapped with the key, when WithKey repeats // a key already registered in this scheduler. ErrDuplicateKey = errors.New("cron: duplicate entry key") // ErrNilContext is returned by Stop, Drain and TriggerAndWait for a nil // context. ErrNilContext = errors.New("cron: nil context") // ErrInvalidOption is wrapped by New, Add and AddSchedule when an Option, // an EntryOption, or its argument is invalid. ErrInvalidOption = errors.New("cron: invalid option") )
Sentinel errors. They may be returned wrapped; match them with errors.Is.
Functions ΒΆ
func Between ΒΆ
Between lazily yields every firing of s in (start, end], in order. The sequence is empty when s is nil or end is not after start. Iteration stops at the first firing after end, so an unbounded schedule is safe to query.
Example ΒΆ
package main
import (
"fmt"
"time"
"github.com/libtnb/cron"
)
func main() {
s, err := cron.NewStandardParser(cron.WithDefaultLocation(time.UTC)).Parse("0 */6 * * *")
if err != nil {
fmt.Println(err)
return
}
start := time.Date(2026, time.March, 1, 0, 0, 0, 0, time.UTC)
for t := range cron.Between(s, start, start.Add(24*time.Hour)) {
fmt.Println(t.Format(time.RFC3339))
}
}
Output: 2026-03-01T06:00:00Z 2026-03-01T12:00:00Z 2026-03-01T18:00:00Z 2026-03-02T00:00:00Z
func IsTriggered ΒΆ
IsTriggered reports whether s came from TriggeredSchedule.
func NextN ΒΆ
NextN returns up to n firings of s strictly after from, in order; fewer are returned when the schedule exhausts first. It returns nil when s is nil or n is not positive. Schedules implementing Upcoming are iterated lazily; others are walked with repeated Next calls.
Example ΒΆ
package main
import (
"fmt"
"time"
"github.com/libtnb/cron"
)
func main() {
s, err := cron.NewStandardParser(cron.WithDefaultLocation(time.UTC)).Parse("0 9 * * MON-FRI")
if err != nil {
fmt.Println(err)
return
}
from := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) // a Thursday
for _, t := range cron.NextN(s, from, 3) {
fmt.Println(t.Format("Mon 2006-01-02 15:04"))
}
}
Output: Thu 2026-01-01 09:00 Fri 2026-01-02 09:00 Mon 2026-01-05 09:00
func ValidateSpec ΒΆ
ValidateSpec checks spec against the built-in five-field parser in time.Local; use ValidateSpecWith to match a scheduler configured with WithSecondsField or WithParser. Returns a *ParseError describing the fault, or nil.
Example ΒΆ
package main
import (
"fmt"
"github.com/libtnb/cron"
)
func main() {
fmt.Println(cron.ValidateSpec("*/15 * * * *"))
fmt.Println(cron.ValidateSpec("61 * * * *"))
}
Output: <nil> cron: parse "61 * * * *": field "minute": 61 above maximum 59
func ValidateSpecWith ΒΆ
ValidateSpecWith checks spec with p. Returns p's error (a *ParseError for the built-in parsers), ErrNilSchedule when p returns no schedule, or an error for a nil p.
Types ΒΆ
type AlignedDelay ΒΆ added in v0.5.1
AlignedDelay fires at multiples of d since the Unix epoch, so replicas evaluating the same interval compute identical instants and can share a Claimer key. Alignment follows time.Truncate, which works on absolute time: a 24h AlignedDelay fires at 00:00 UTC, not local midnight. Non-positive intervals never fire.
func (AlignedDelay) Next ΒΆ added in v0.5.1
func (d AlignedDelay) Next(now time.Time) time.Time
Next returns the next multiple of the interval strictly after now, or zero for non-positive intervals.
func (AlignedDelay) String ΒΆ added in v0.5.1
func (d AlignedDelay) String() string
String renders the interval in "@aligned" form, for example "@aligned 5m".
type CanceledFireEvent ΒΆ added in v0.5.3
CanceledFireEvent reports a fire that held a concurrency slot but was cancelled before its job started: the scheduler stopped while the fire was waiting out its jitter. Cause is the run context's cancellation cause, typically ErrCronStopping.
type Claimer ΒΆ added in v0.5.3
type Claimer interface {
// Claim reserves fireKey for this instance. It returns true when the
// caller may run the fire, false when another instance already holds it,
// and a non-nil error only for backend failures.
Claim(ctx context.Context, fireKey string) (bool, error)
}
Claimer elects one scheduler instance per fire across replicas. The scheduler calls Claim on the fire goroutine, after jitter and any Elector check, with a key unique to the entry key and the UTC scheduled instant ("report@2026-01-02T15:04:05Z"), so replicas in different time zones agree.
Implementations must be safe for concurrent use and should keep a claim reserved long enough that a delayed replica cannot run the same fire later; they own their timeout and retention policy. A false, nil result is the normal "another instance won" outcome and produces SkipAlreadyClaimed; a non-nil error fails closed, skipping the fire with SkipClaimError. Manual triggers never claim.
type ConstantDelay ΒΆ
ConstantDelay fires every d, measured from each evaluation: the scheduler computes the next fire from the current time, so the phase is anchored to this process and drifts with job lateness. Whole-second periods snap to the second boundary; sub-second periods keep their exact length. Non-positive intervals never fire. "@every 10s" parses to ConstantDelay.
Because the phase is process-local, replicas sharing a Claimer key never agree on fire instants; use AlignedDelay or a cron expression instead.
func (ConstantDelay) Next ΒΆ
func (d ConstantDelay) Next(now time.Time) time.Time
Next returns now plus the interval, snapped to a whole second for whole-second intervals, or zero for non-positive intervals.
func (ConstantDelay) String ΒΆ
func (d ConstantDelay) String() string
String renders the interval in "@every" form, for example "@every 1m30s".
type Cron ΒΆ
type Cron struct {
// contains filtered or unexported fields
}
Cron is a job scheduler. Construct one with New, register jobs with Add or AddSchedule, then call Start. All methods are safe for concurrent use.
The loop goroutine only pops due entries. Schedule.Next, distributed coordination and jobs all run on their own goroutines, so one slow schedule or job never delays another entry.
func MustNew ΒΆ added in v0.5.3
MustNew is New that panics instead of returning an error, for configuration that is fixed at build time.
func New ΒΆ
New constructs a Cron from opts; nothing fires until Start is called.
Defaults: time.Local, slog.Default(), the five-field standard parser, MissedRunOnce with a one-minute tolerance, no jitter, no retry, unlimited concurrency and entries, and job panics recovered into ErrJobPanic.
Returns an error wrapping ErrInvalidOption when an option is nil or rejects its argument. WithLocation is ignored, with a warning, when WithParser is also set: a custom parser owns time-zone resolution.
Example ΒΆ
ExampleNew registers a job, starts the scheduler, runs the job on demand and shuts down.
package main
import (
"context"
"fmt"
"time"
"github.com/libtnb/cron"
)
func main() {
c, err := cron.New(cron.WithLocation(time.UTC))
if err != nil {
fmt.Println("new:", err)
return
}
id, err := c.Add("0 9 * * MON-FRI", cron.JobFunc(func(ctx context.Context) error {
info, _ := cron.EntryInfoFromContext(ctx)
fmt.Println("running", info.Name)
return nil
}), cron.WithName("digest"))
if err != nil {
fmt.Println("add:", err)
return
}
if err := c.Start(); err != nil {
fmt.Println("start:", err)
return
}
// Run the entry now instead of waiting for 09:00.
if err := c.TriggerAndWait(context.Background(), id); err != nil {
fmt.Println("trigger:", err)
}
if err := c.Stop(context.Background()); err != nil {
fmt.Println("stop:", err)
}
}
Output: running digest
func (*Cron) Add ΒΆ
Add parses spec with the configured parser and registers j. The first fire is computed from now, or from WithLastRun when set. Parsed specs are memoized, so repeated Add calls with the same expression share one Schedule.
Returns a *ParseError for an invalid spec, ErrNilSchedule if the parser returned no schedule, ErrNilJob, an error wrapping ErrInvalidOption for a rejected entry option, ErrCapacityReached when WithMaxEntries is exhausted, ErrClaimerRequiresKey when a Claimer applies without WithKey, and an error wrapping ErrDuplicateKey when the key is already registered.
func (*Cron) AddSchedule ΒΆ
AddSchedule is Add for a programmatic Schedule; Entry.Spec stays empty for such entries. It returns the same errors as Add except *ParseError.
func (*Cron) Drain ΒΆ added in v0.3.0
Drain is Stop without cancelling in-flight jobs: it stops scheduling new fires and waits for running jobs to finish naturally, capped by ctx. Returns ErrNilContext or ctx.Err() like Stop. Do not call it from inside a Job.
func (*Cron) Entries ΒΆ
Entries yields a snapshot of every registered entry ordered by Next, with exhausted, paused and triggered-only entries (zero Next) last. The snapshot is taken when iteration starts; changes made during iteration are not reflected.
func (*Cron) Entry ΒΆ
Entry returns the current snapshot for id. ok is false after Remove or for an id that was never registered. Reading a snapshot never blocks the scheduler.
func (*Cron) Pause ΒΆ added in v0.3.0
Pause suspends automatic fires for id, keeping the entry and its Prev. Trigger still works while paused. Pausing a paused entry is a no-op; an unknown id yields ErrEntryNotFound.
func (*Cron) Remove ΒΆ
Remove deregisters id. Invocations already running continue; no further automatic fires happen and Trigger rejects the id. Returns ErrEntryNotFound for an unknown or already removed id.
func (*Cron) Resume ΒΆ added in v0.3.0
Resume re-enables automatic fires for id, computing the next fire from now rather than from the pause instant, so nothing is replayed. Resuming an entry that is not paused is a no-op; an unknown id yields ErrEntryNotFound.
func (*Cron) Running ΒΆ
Running reports whether the scheduler loop is active. It is observational only; use Trigger's returned error for race-free dispatch decisions.
func (*Cron) Start ΒΆ
Start launches the scheduler loop; entries registered earlier fire once it runs. Start is idempotent while running and returns ErrSchedulerStopped once Stop or Drain has been called, even before any Start: a Cron cannot be restarted.
func (*Cron) Stop ΒΆ
Stop halts the scheduler, cancels in-flight jobs with ErrCronStopping as the context cause, and waits for the loop, the jobs and the observer queue to drain, capped by ctx. Returns ErrNilContext for a nil ctx and ctx.Err() when the wait times out; jobs still running at that point keep their cancelled context. Calling Stop before Start marks the Cron stopped. Do not call it from inside a Job: it waits for that job.
func (*Cron) Trigger ΒΆ
Trigger fires id immediately on this process, bypassing jitter and distributed coordination; paused entries can be triggered. The invocation runs asynchronously and does not advance Entry.Prev. Returns ErrSchedulerNotRunning, ErrEntryNotFound, or ErrConcurrencyLimit when WithMaxConcurrent has no free slot (a RejectedFireEvent is published too).
func (*Cron) TriggerAndWait ΒΆ added in v0.3.0
TriggerAndWait fires id like Trigger and blocks until the invocation returns, yielding the job's error (including ErrJobPanic and retry aggregates). ctx bounds only the wait: on cancellation it returns ctx.Err() while the job keeps running under the scheduler's context. Returns ErrNilContext for a nil ctx and the same dispatch errors as Trigger.
func (*Cron) TriggerByName ΒΆ
TriggerByName fires every entry whose Name equals name; names are not unique. It returns the number of successful dispatches and the errors.Join of the failed ones. No match yields (0, nil); a scheduler that is not running yields (0, ErrSchedulerNotRunning).
func (*Cron) Update ΒΆ added in v0.3.0
Update re-parses spec and swaps id's schedule in place, keeping the job, entry options, ID and Prev. The next fire is recomputed from now; a paused entry stays paused; a fire already being planned under the old schedule is discarded. Returns a *ParseError for an invalid spec, ErrNilSchedule, or ErrEntryNotFound.
type Elector ΒΆ added in v0.3.0
type Elector interface {
// IsLeader reports whether this instance currently leads. A non-nil error
// means the answer is unknown, not that the instance is a follower.
IsLeader(ctx context.Context) (bool, error)
}
Elector gates automatic fires on leadership. IsLeader is called on every fire goroutine, so it must be safe for concurrent use and cheap enough to run once per fire; lease-renewing implementations fit well. A false, nil result is the normal follower state and produces SkipNotLeader; a non-nil error fails closed with SkipElectionError. Manual triggers bypass the elector.
type Entry ΒΆ
type Entry struct {
ID EntryID
Name string // display label from WithName; not unique
Key string // stable identity for distributed fire claims (WithKey)
Spec string // source expression; empty for AddSchedule entries
Schedule Schedule
Prev time.Time // last automatic fire or the WithLastRun seed; zero if never fired
Next time.Time // zero if exhausted, paused, or TriggeredSchedule
Paused bool
}
Entry is a point-in-time snapshot of a registered entry, as returned by Cron.Entry and Cron.Entries. It is a plain value: copying it or holding it across scheduler changes is safe, but it does not update.
type EntryID ΒΆ
type EntryID uint64
EntryID identifies one registration for the lifetime of a Cron. IDs are process-local, allocated sequentially from 1 and never reused, so a stale ID yields ErrEntryNotFound rather than another entry. Zero is never a valid ID; see Entry.Valid.
type EntryInfo ΒΆ added in v0.4.0
EntryInfo identifies the invocation a job is serving; retrieve it with EntryInfoFromContext. ScheduledAt is the fire instant the job was dispatched for, or the wall-clock time for manual triggers.
func EntryInfoFromContext ΒΆ added in v0.4.0
EntryInfoFromContext returns the identity of the entry whose job is running under ctx. The scheduler injects it for every dispatch, including manual triggers, so wrappers and jobs can tell which entry and which fire they serve. ok is false when ctx did not come from the scheduler, for example when a Job is run directly in tests.
type EntryOption ΒΆ
type EntryOption func(*entryConfig) error
EntryOption configures one entry at Add or AddSchedule time. Options are applied in order; one that rejects its argument makes Add fail with an error wrapping ErrInvalidOption.
func WithEntryChain ΒΆ
func WithEntryChain(wrappers ...Wrapper) EntryOption
WithEntryChain installs wrappers for this entry, first outermost, inside the WithChain wrappers and outside the retry policy. A nil wrapper is rejected.
func WithEntryClaimer ΒΆ added in v0.5.3
func WithEntryClaimer(claimer Claimer) EntryOption
WithEntryClaimer overrides the scheduler's Claimer for one entry. A nil claimer disables distributed claims for the entry, so it fires on every replica; a non-nil claimer requires WithKey.
func WithEntryJitter ΒΆ added in v0.3.0
func WithEntryJitter(max time.Duration) EntryOption
WithEntryJitter overrides the scheduler's jitter for one entry. Zero disables jitter for the entry; a negative max is rejected.
func WithEntryMissedFire ΒΆ added in v0.3.0
func WithEntryMissedFire(p MissedFirePolicy) EntryOption
WithEntryMissedFire overrides the scheduler's missed-fire policy for one entry. An unknown policy value is rejected.
func WithEntryRetry ΒΆ
func WithEntryRetry(p RetryPolicy) EntryOption
WithEntryRetry overrides the scheduler's RetryPolicy for one entry. A zero policy (MaxRetries == 0) disables retry for the entry even when WithRetry is set. An invalid policy is rejected.
func WithKey ΒΆ added in v0.5.3
func WithKey(key string) EntryOption
WithKey sets the stable identity used for distributed fire claims. Keys are trimmed, must be non-empty, must be unique within the scheduler (Add returns ErrDuplicateKey otherwise) and should be the same on every replica for the same logical job. Key is independent from the display-oriented Name.
func WithLastRun ΒΆ added in v0.3.0
func WithLastRun(t time.Time) EntryOption
WithLastRun seeds the entry's schedule anchor, usually the persisted time of the last run before a restart. The first fire is computed from t instead of now, so an instant already in the past is popped immediately and, when it is later than WithMissedTolerance, handed to the missed-fire policy, which catches up work missed while the process was down. It also seeds Entry.Prev. A zero t means "anchor at now".
func WithName ΒΆ
func WithName(name string) EntryOption
WithName labels an entry for Entry.Name, events, logs and TriggerByName. Names need not be unique; use WithKey for identity.
func WithTimeout ΒΆ
func WithTimeout(d time.Duration) EntryOption
WithTimeout caps one invocation's runtime; the job context is cancelled with ErrJobTimeout as its cause. The clock starts after jitter and coordination. Zero (the default) disables the timeout; a negative d is rejected.
type EntryRef ΒΆ added in v0.5.3
type EntryRef struct {
ID EntryID
Key string // WithKey value; empty when unset
Name string // WithName value; empty when unset
}
EntryRef identifies the entry an event concerns. It is a copy taken at publication; the entry may already have been removed when the event is observed.
type Event ΒΆ added in v0.5.3
type Event interface {
// contains filtered or unexported methods
}
Event is one scheduler notification, delivered to Observers and the Recorder. The set of concrete types is closed to this package, so consumers can switch on them exhaustively:
switch ev := ev.(type) {
case cron.JobCompleteEvent:
// ...
}
type Job ΒΆ
type Job interface {
// Run performs the work. The returned error is reported through
// JobCompleteEvent and, for TriggerAndWait, to the caller; a panic is
// recovered into ErrJobPanic unless WithoutRecover is set.
Run(ctx context.Context) error
}
Job is the unit of work the scheduler runs. Run receives a context that is cancelled by Stop (cause ErrCronStopping), by WithTimeout (cause ErrJobTimeout) or by the WithBaseContext parent, and that carries EntryInfo (see EntryInfoFromContext). Implementations must be safe for concurrent use: overlapping fires of one entry run concurrently unless a wrapper such as wrap.SkipIfRunning prevents it.
type JobCompleteEvent ΒΆ added in v0.5.3
type JobCompleteEvent struct {
Entry EntryRef
ScheduledAt time.Time
StartedAt time.Time
Duration time.Duration
Err error
}
JobCompleteEvent reports the result of an invocation's wrapper chain. Err is the job's error, nil on success, including ErrJobPanic and retry aggregates.
type JobFunc ΒΆ
JobFunc adapts a plain function to Job.
Example ΒΆ
package main
import (
"context"
"fmt"
"github.com/libtnb/cron"
)
func main() {
j := cron.JobFunc(func(ctx context.Context) error {
fmt.Println("hello")
return nil
})
_ = j.Run(context.Background())
}
Output: hello
type JobStartEvent ΒΆ added in v0.5.3
JobStartEvent reports an invocation about to enter its wrapper chain, after jitter, coordination and concurrency admission. ScheduledAt is the fire instant; StartedAt is the wall-clock start.
type MissedFireEvent ΒΆ added in v0.5.3
type MissedFireEvent struct {
Entry EntryRef
ScheduledAt time.Time
Lateness time.Duration
Policy MissedFirePolicy
}
MissedFireEvent reports a fire that ran later than WithMissedTolerance and therefore invoked the entry's missed-fire policy. It is published once per late pop, whatever the policy, including MissedSkip.
type MissedFirePolicy ΒΆ
type MissedFirePolicy uint8
MissedFirePolicy decides what happens when an entry fires later than WithMissedTolerance allows: after a scheduler stall, a suspended process, or a restart seeded with WithLastRun. A MissedFireEvent is published whatever the policy. Set it per scheduler with WithMissedFire or per entry with WithEntryMissedFire.
const ( // MissedRunOnce runs the job once for the most recent missed firing (the // latest Schedule.Next result not after now), then resumes normally. It // is the default: late work still happens, but a backlog never turns into // a burst. MissedRunOnce MissedFirePolicy = iota // MissedSkip drops the missed firings and resumes from the next scheduled // time. MissedSkip // MissedRunAll runs the job once per missed firing, keeping only the // newest 1000 when the backlog is larger. The replays are dispatched // oldest first as separate concurrent invocations, each subject to // WithMaxConcurrent. MissedRunAll )
func (MissedFirePolicy) String ΒΆ
func (p MissedFirePolicy) String() string
String returns "run-once", "skip", "run-all", or "unknown".
type Observer ΒΆ added in v0.5.3
type Observer interface {
// Observe handles one event.
Observe(Event)
}
Observer receives scheduler events asynchronously, in publication order, on one goroutine shared by all observers of a Cron. Observe therefore need not be safe for concurrent use, but it must not block for long: while one observer is slow the shared queue (WithObserverBuffer) fills and new events are dropped. Panics are recovered and logged. Stop and Drain wait for queued events to be delivered.
type ObserverDropEvent ΒΆ added in v0.5.3
type ObserverDropEvent struct {
Dropped int64
}
ObserverDropEvent reports that the observer queue was full and an event was dropped. Dropped is the cumulative count for this Cron. It reaches only the Recorder: observers cannot receive it because their queue is the one that overflowed.
type ObserverFunc ΒΆ added in v0.5.3
type ObserverFunc func(Event)
ObserverFunc adapts a function to Observer.
func (ObserverFunc) Observe ΒΆ added in v0.5.3
func (f ObserverFunc) Observe(event Event)
Observe calls f.
type Option ΒΆ
type Option func(*config) error
Option configures a Cron at construction. New applies options in order; an option that rejects its argument makes New fail with an error wrapping ErrInvalidOption.
func WithBaseContext ΒΆ added in v0.3.0
WithBaseContext sets the parent of the run context that jobs inherit. Cancelling it stops the loop and cancels in-flight jobs, like Stop but without waiting; still call Stop or Drain to wait for them. A nil ctx is rejected.
func WithChain ΒΆ
WithChain installs wrappers applied to every job, first outermost; repeated calls append. Entry wrappers (WithEntryChain) and the retry policy sit inside them. A nil wrapper is rejected.
func WithClaimer ΒΆ added in v0.5.3
WithClaimer sets the Claimer consulted before every automatic fire. Entries then require WithKey (Add returns ErrClaimerRequiresKey) and may opt out or switch backends with WithEntryClaimer. Trigger bypasses claims. A nil claimer is rejected.
func WithElector ΒΆ added in v0.3.0
WithElector gates automatic fires on leadership. A follower answer and a backend failure both skip the fire, with SkipNotLeader and SkipElectionError respectively. Trigger bypasses the elector. A nil e is rejected.
func WithJitter ΒΆ
WithJitter delays each automatic fire by a random duration in [0, max) so a fleet of schedulers does not fire in lockstep. The delay is waited on the run context, not the job timeout, and Trigger skips it. Zero (the default) disables jitter; a negative max is rejected. Override per entry with WithEntryJitter.
func WithLocation ΒΆ
WithLocation sets the time zone for specs without a TZ=/CRON_TZ= prefix. The default is time.Local; a nil loc is rejected. It is ignored, with a warning from New, when WithParser is set, because a custom parser resolves time zones itself.
func WithLogger ΒΆ
WithLogger sets the logger for warnings and recovered panics. The default is slog.Default(); a nil l is rejected.
func WithMaxConcurrent ΒΆ
WithMaxConcurrent caps in-flight jobs across all entries. Zero (the default) means unlimited; a negative n is rejected. Automatic fires above the cap publish RejectedFireEvent and are not retried; Trigger returns ErrConcurrencyLimit.
func WithMaxEntries ΒΆ
WithMaxEntries caps registered entries; Add returns ErrCapacityReached beyond it. Zero (the default) means unlimited; a negative n is rejected.
func WithMissedFire ΒΆ
func WithMissedFire(p MissedFirePolicy) Option
WithMissedFire sets the scheduler-wide missed-fire policy. The default is MissedRunOnce; an unknown policy value is rejected. Override per entry with WithEntryMissedFire.
func WithMissedTolerance ΒΆ
WithMissedTolerance sets how late a fire may run before the missed-fire policy applies. The default is one minute, the same threshold Quartz uses, so a brief scheduling stall runs the job late instead of invoking the policy. A non-positive d is rejected.
func WithObserverBuffer ΒΆ added in v0.5.3
WithObserverBuffer sets the observer queue capacity. Zero selects the default of 1024; a negative n is rejected. When the queue is full new events are dropped for observers and an ObserverDropEvent is recorded, while the Recorder still receives every event.
func WithObservers ΒΆ added in v0.5.3
WithObservers installs asynchronous event observers, notified in the order given through one bounded queue (see Observer). Repeated calls append. A nil observer is rejected.
func WithParser ΒΆ
WithParser replaces the built-in parser used by Add and Update, for example with parserext.NewQuartzParser. The parser then owns time-zone and seconds handling, so WithLocation and WithSecondsField no longer apply. Results are memoized per spec, so p must be deterministic and safe for concurrent use. A nil p is rejected.
func WithRecorder ΒΆ
WithRecorder installs the inline event Recorder, typically a metrics adapter such as contrib/prometheus; see Recorder for the concurrency contract. A nil r is rejected.
func WithRetry ΒΆ
func WithRetry(p RetryPolicy) Option
WithRetry sets the default RetryPolicy, applied innermost in every job's wrapper chain. A policy with negative delays or a jitter fraction outside [0, 1] is rejected. Override per entry with WithEntryRetry.
func WithSecondsField ΒΆ added in v0.2.2
func WithSecondsField() Option
WithSecondsField makes the built-in parser accept an optional leading seconds field (six fields) alongside five-field specs, so seconds and WithLocation compose without WithParser. It has no effect when WithParser is set.
func WithoutRecover ΒΆ added in v0.3.0
func WithoutRecover() Option
WithoutRecover disables the built-in job panic recovery. By default a panicking job is recovered into an ErrJobPanic-wrapped error and logged with its stack; with this option the panic propagates and crashes the process.
type ParseError ΒΆ
type ParseError struct {
Spec string // the specification being parsed
Field string // "second", "minute", "hour", "dom", "month", "dow", "@every", "TZ" or "CRON_TZ"; "" if not applicable
Pos int // 0-based byte offset; -1 if unknown
Reason string // human-readable cause
Err error // underlying error, if any (for example from time.LoadLocation)
}
ParseError describes why a cron specification was rejected. It is returned as *ParseError by StandardParser.Parse, the parserext parsers, and through them by Add, Update, ValidateSpec and AnalyzeSpec; match it with errors.As.
func (*ParseError) Error ΒΆ
func (e *ParseError) Error() string
Error formats the spec together with whichever of Field and Pos are known, for example: cron: parse "61 * * * *": field "minute": 61 above maximum 59.
func (*ParseError) Unwrap ΒΆ
func (e *ParseError) Unwrap() error
Unwrap exposes the underlying error, such as a time.LoadLocation failure, to errors.Is and errors.As.
type Parser ΒΆ
type Parser interface {
// Parse compiles spec. Implementations should return *ParseError for
// rejected specs so callers can inspect the fault.
Parse(spec string) (Schedule, error)
}
Parser turns a textual spec into a Schedule; see WithParser and ValidateSpecWith. Cron memoizes results per spec, so Parse must be deterministic and safe for concurrent use, and the returned Schedule may back several entries. Parse must return a non-nil Schedule or an error; a nil, nil result is reported as ErrNilSchedule.
type ParserOption ΒΆ
type ParserOption func(*parserConfig)
ParserOption configures NewStandardParser.
func WithDefaultLocation ΒΆ
func WithDefaultLocation(loc *time.Location) ParserOption
WithDefaultLocation sets the time zone for specs without a TZ=/CRON_TZ= prefix. nil (the default) means time.Local.
func WithOptionalSeconds ΒΆ added in v0.5.3
func WithOptionalSeconds() ParserOption
WithOptionalSeconds accepts both five- and six-field specs; a five-field spec is parsed with second 0. Without it six-field specs are rejected.
func WithParserExt ΒΆ
func WithParserExt(ext Parser) ParserOption
WithParserExt consults ext before the standard grammar, for custom descriptors or syntax. ext returning (nil, nil) falls through to standard parsing; a non-nil error is returned as is. A nil ext is ignored.
func WithRequiredSeconds ΒΆ added in v0.5.3
func WithRequiredSeconds() ParserOption
WithRequiredSeconds requires exactly six fields with a leading seconds field, rejecting five-field specs. It takes precedence over WithOptionalSeconds.
type QueueDepthEvent ΒΆ added in v0.5.3
type QueueDepthEvent struct {
Depth int
}
QueueDepthEvent reports the number of entries waiting in the scheduling heap, that is, entries with a non-zero Next. It is published after Add, Remove, Pause, Resume, Update and each committed fire.
type Recorder ΒΆ added in v0.5.3
type Recorder interface {
// Record handles one event synchronously.
Record(Event)
}
Recorder receives every scheduler event inline on the publishing goroutine, before the observers, and never misses one. Record is called concurrently from the loop, planner and job goroutines, so it must be safe for concurrent use and fast: it sits on the dispatch path. A panic is recovered and logged. One Recorder can be installed with WithRecorder.
type RecorderFunc ΒΆ added in v0.5.3
type RecorderFunc func(Event)
RecorderFunc adapts a function to Recorder.
func (RecorderFunc) Record ΒΆ added in v0.5.3
func (f RecorderFunc) Record(event Event)
Record calls f.
type RejectReason ΒΆ added in v0.5.3
type RejectReason uint8
RejectReason classifies a fire refused before job execution; it is carried by RejectedFireEvent.
const ( // RejectUnknown is the zero value; the scheduler never publishes it. RejectUnknown RejectReason = iota // RejectConcurrencyLimit reports that WithMaxConcurrent had no free slot. RejectConcurrencyLimit )
func (RejectReason) String ΒΆ added in v0.5.3
func (r RejectReason) String() string
String returns "concurrency-limit" or "unknown".
type RejectedFireEvent ΒΆ added in v0.5.3
type RejectedFireEvent struct {
Entry EntryRef
ScheduledAt time.Time
Reason RejectReason
}
RejectedFireEvent reports a fire refused before job execution, for both automatic fires and Trigger calls (which also return ErrConcurrencyLimit). The instant is not retried.
type RetryOption ΒΆ
type RetryOption func(*RetryPolicy)
RetryOption configures a RetryPolicy built by Retry.
func RetryInitial ΒΆ
func RetryInitial(d time.Duration) RetryOption
RetryInitial sets the first retry delay. The default is one second.
func RetryJitterFrac ΒΆ
func RetryJitterFrac(f float64) RetryOption
RetryJitterFrac sets uniform jitter as a fraction of the delay, e.g. 0.1 for Β±10%. Valid values are in [0, 1].
func RetryMaxDelay ΒΆ
func RetryMaxDelay(d time.Duration) RetryOption
RetryMaxDelay caps the backoff delay. Zero, the default, is uncapped.
func RetryMultiplier ΒΆ
func RetryMultiplier(m float64) RetryOption
RetryMultiplier sets the per-attempt growth factor. Values of 1 or less keep the delay constant.
type RetryPolicy ΒΆ
type RetryPolicy struct {
MaxRetries int // retries after the initial attempt; 0 disables, negative is unlimited
Initial time.Duration // first retry delay; non-positive means one second
MaxDelay time.Duration // backoff cap; zero means uncapped
Multiplier float64 // per-attempt growth factor; 1 or less keeps the delay constant
JitterFrac float64 // uniform jitter fraction in [0, 1], e.g. 0.1 for Β±10%
}
RetryPolicy describes exponential backoff with optional jitter for jobs that return an error. MaxRetries == 0 disables retry; negative means retry until the context is cancelled. Fields are exported for config-driven assembly; use Retry for programmatic construction. Install a policy with WithRetry, WithEntryRetry, wrap.Retry or workflow.WithRetry.
func Retry ΒΆ
func Retry(maxRetries int, opts ...RetryOption) RetryPolicy
Retry builds a RetryPolicy. maxRetries is the number of retries after the initial attempt; negative retries until the context is cancelled. Values are not validated here: WithRetry and WithEntryRetry reject invalid ones.
func (RetryPolicy) IsZero ΒΆ
func (p RetryPolicy) IsZero() bool
IsZero reports whether the policy disables retry. It is keyed only on MaxRetries, so a half-filled policy (for example only Initial set) does not produce a useless wrapper.
func (RetryPolicy) Wrapper ΒΆ
func (p RetryPolicy) Wrapper() Wrapper
Wrapper returns a Wrapper that re-runs the job on error according to p. The returned error joins every attempt's error with errors.Join, keeping at most the first and the 15 most recent when retries are unlimited. Context cancellation aborts before the next attempt and appends context.Cause, so ErrJobTimeout and ErrCronStopping survive into the joined error and remain matchable with errors.Is. A zero policy runs the job exactly once.
type Schedule ΒΆ
type Schedule interface {
// Next returns the first firing strictly after now, or zero if none.
Next(now time.Time) time.Time
}
Schedule yields successive firing times. Next must return the first firing strictly after now, or the zero time when the schedule is exhausted, and must be monotone: a later argument never yields an earlier result. Implementations must be safe for concurrent use, because the scheduler evaluates schedules on per-fire planner goroutines and one parsed schedule may back several entries. Next should be cheap: besides one call per fire it runs at Add, Update and Resume, and missed-fire catch-up bisects over it (about 60 calls per late fire).
A Next that returns a non-zero time not after its argument breaks the contract; the scheduler logs the fault and treats the entry as exhausted rather than spinning.
func Filter ΒΆ added in v0.3.0
Filter wraps s, skipping firings for which keep returns false, for example a holiday calendar. A nil keep passes everything through. The search gives up and reports exhaustion (zero time) after 100000 consecutive rejections, so a filter that rejects everything still terminates.
func OnceAt ΒΆ added in v0.3.0
OnceAt fires exactly once, at t, then exhausts. If t is already past when the entry is added it never fires, unless WithLastRun seeds an anchor before t, in which case the missed-fire policy decides.
func TriggeredSchedule ΒΆ
func TriggeredSchedule() Schedule
TriggeredSchedule never fires on its own; the entry runs only through Trigger, TriggerAndWait or TriggerByName. Entry.Next stays zero for such entries and AnalyzeSpec reports IsTriggered.
type ScheduleEvent ΒΆ added in v0.5.3
ScheduleEvent reports an entry's newly computed next fire: on Add, Update, Resume and after every committed fire. Next is zero only on Add, when the schedule has no future fire; later recomputations that exhaust an entry publish no ScheduleEvent.
type SkipReason ΒΆ added in v0.3.0
type SkipReason uint8
SkipReason classifies why distributed coordination suppressed a fire; it is carried by SkippedFireEvent.
const ( // SkipUnknown is the zero value; the scheduler never publishes it. SkipUnknown SkipReason = iota // SkipNotLeader reports that the Elector answered false, nil. SkipNotLeader // SkipElectionError reports that the Elector returned an error. SkipElectionError // SkipAlreadyClaimed reports that the Claimer answered false, nil. SkipAlreadyClaimed // SkipClaimError reports that the Claimer returned an error. SkipClaimError )
func (SkipReason) String ΒΆ added in v0.3.0
func (r SkipReason) String() string
String returns a kebab-case label suitable for metric labels, or "unknown".
type SkippedFireEvent ΒΆ added in v0.5.3
type SkippedFireEvent struct {
Entry EntryRef
ScheduledAt time.Time
Reason SkipReason
Err error
}
SkippedFireEvent reports a fire suppressed by distributed coordination. Err is non-nil only for SkipElectionError and SkipClaimError.
type SpecAnalysis ΒΆ
type SpecAnalysis struct {
Spec string
Valid bool
Err error // *ParseError or ErrNilSchedule when Valid is false
IsTriggered bool // the spec parsed to TriggeredSchedule (custom parsers only)
Descriptor string // "@every", "@hourly", ... or "" for field specs
Interval time.Duration // set when Descriptor == "@every"
Location *time.Location // schedule time zone when the Schedule exposes one; nil otherwise
NextRun time.Time // first firing after the now passed in; zero if none or IsTriggered
}
SpecAnalysis is the result of AnalyzeSpec. When Valid is false only Spec and Err are meaningful.
func AnalyzeSpec ΒΆ
func AnalyzeSpec(spec string, now time.Time) SpecAnalysis
AnalyzeSpec describes spec relative to now using the built-in five-field parser in time.Local. It never fails: a rejected spec is reported through SpecAnalysis.Valid and Err. Use AnalyzeSpecWith to match a custom parser.
Example ΒΆ
package main
import (
"fmt"
"time"
"github.com/libtnb/cron"
)
func main() {
now := time.Date(2026, time.January, 1, 12, 0, 0, 0, time.UTC)
a := cron.AnalyzeSpec("CRON_TZ=UTC 30 8 * * *", now)
fmt.Println(a.Valid, a.Location, a.NextRun.Format(time.RFC3339))
a = cron.AnalyzeSpec("@every 90s", now)
fmt.Println(a.Valid, a.Descriptor, a.Interval)
}
Output: true UTC 2026-01-02T08:30:00Z true @every 1m30s
func AnalyzeSpecWith ΒΆ
func AnalyzeSpecWith(spec string, p Parser, now time.Time) SpecAnalysis
AnalyzeSpecWith describes spec relative to now using p. Location is set when the Schedule has a Location() *time.Location method, as SpecSchedule and parserext.QuartzSchedule do.
type SpecSchedule ΒΆ
type SpecSchedule struct {
// contains filtered or unexported fields
}
SpecSchedule is a compiled cron expression as produced by StandardParser. It is immutable and safe for concurrent use; one instance may back several entries. The zero value never fires.
func (*SpecSchedule) Location ΒΆ
func (s *SpecSchedule) Location() *time.Location
Location returns the time zone the expression is evaluated in: the TZ= prefix if present, otherwise the parser's default. AnalyzeSpec reports it.
func (*SpecSchedule) LogValue ΒΆ
func (s *SpecSchedule) LogValue() slog.Value
LogValue renders the schedule's kind and location for slog.
func (*SpecSchedule) Next ΒΆ
func (s *SpecSchedule) Next(t time.Time) time.Time
Next returns the first firing strictly after t, evaluated in the schedule's Location and returned in t's location, or zero when nothing matches within the next five years.
The hour branch reconstructs the wall clock at the target hour instead of adding an absolute (h-hour)*time.Hour, which would overshoot a DST spring-forward gap and skip the day. The minute/second branches keep their absolute jumps: they never straddle a whole-hour DST boundary, and stepping through the repeated hour on fall-back keeps both firings observable.
type StandardParser ΒΆ
type StandardParser struct {
// contains filtered or unexported fields
}
StandardParser parses the classic cron grammar. It is immutable after construction and safe for concurrent use. New builds one from WithLocation and WithSecondsField unless WithParser is set; ValidateSpec and AnalyzeSpec use a five-field instance in time.Local.
func NewStandardParser ΒΆ
func NewStandardParser(opts ...ParserOption) *StandardParser
NewStandardParser builds a parser; without options it accepts five-field specs and descriptors in time.Local.
func (*StandardParser) Parse ΒΆ
func (p *StandardParser) Parse(spec string) (Schedule, error)
Parse compiles spec into a *SpecSchedule, or a ConstantDelay for "@every". Surrounding whitespace is ignored. It returns a *ParseError for an empty spec, a wrong field count, an out-of-range or malformed field, an unknown descriptor, an unknown or empty TZ= zone, or an "@every" interval below one millisecond.
type Upcoming ΒΆ
type Upcoming interface {
// Upcoming yields every firing strictly after from, in order.
Upcoming(from time.Time) iter.Seq[time.Time]
}
Upcoming is an optional Schedule capability used by NextN and Between to iterate firings lazily. Upcoming must yield strictly increasing times after from and stop when the schedule exhausts.
type Wrapper ΒΆ
Wrapper decorates a Job, for example with a timeout, retries or tracing. Wrappers are applied once per entry at Add time, so a wrapper that keeps state (such as wrap.SkipIfRunning) gets one instance per entry.
func Chain ΒΆ
Chain composes wrappers so the first one is outermost: Chain(a, b)(j) runs a around b around j. Chain with no wrappers returns j unchanged. Cron builds each entry's chain as WithChain, then WithEntryChain, then the retry policy, from the outside in.
Example ΒΆ
package main
import (
"context"
"fmt"
"github.com/libtnb/cron"
)
func main() {
mk := func(name string) cron.Wrapper {
return func(j cron.Job) cron.Job {
return cron.JobFunc(func(ctx context.Context) error {
fmt.Println("enter", name)
err := j.Run(ctx)
fmt.Println("leave", name)
return err
})
}
}
core := cron.JobFunc(func(ctx context.Context) error {
fmt.Println("run core")
return nil
})
_ = cron.Chain(mk("outer"), mk("inner"))(core).Run(context.Background())
}
Output: enter outer enter inner run core leave inner leave outer
Source Files
ΒΆ
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
_examples
|
|
|
hello
command
|
|
|
quartz
command
|
|
|
seconds
command
|
|
|
slog
command
|
|
|
workflow
command
|
|
|
contrib
|
|
|
otel
module
|
|
|
prometheus
module
|
|
|
coordination
|
|
|
postgres
module
|
|
|
redis
module
|
|
|
internal
|
|
|
bitmask
Package bitmask provides the bit-set scan shared by the schedule implementations, which store each cron field as a uint64 mask.
|
Package bitmask provides the bit-set scan shared by the schedule implementations, which store each cron field as a uint64 mask. |
|
heap
Package heap provides a typed min-heap with addressable items, so the scheduler can re-key or remove an entry in O(log n) without searching.
|
Package heap provides a typed min-heap with addressable items, so the scheduler can re-key or remove an entry in O(log n) without searching. |
|
parsecache
Package parsecache memoizes parser results so repeated registrations of the same spec share one parsed value and pay the parse cost once.
|
Package parsecache memoizes parser results so repeated registrations of the same spec share one parsed value and pay the parse cost once. |
|
Package parserext provides cron.Parser extensions beyond the standard grammar.
|
Package parserext provides cron.Parser extensions beyond the standard grammar. |
|
Package workflow builds typed directed acyclic graphs of cron jobs and runs them as one cron.Job.
|
Package workflow builds typed directed acyclic graphs of cron jobs and runs them as one cron.Job. |
|
Package wrap supplies reusable cron.Wrapper decorators: panic recovery, timeouts, retry and overlap control.
|
Package wrap supplies reusable cron.Wrapper decorators: panic recovery, timeouts, retry and overlap control. |