Documentation
¶
Overview ¶
Package tasks is an in-process, cron-like scheduler that runs Go functions periodically using a small, human-friendly schedule syntax.
A Task pairs a Schedule (interval, weekday, or daily time) with a callback bound via Do. A Scheduler owns a set of tasks and a single ticker goroutine that, on every tick, starts every task whose NextRunAt has passed in its own goroutine. A task never overlaps with itself: Run acquires a per-task lock and gives up (returning false) if the lock cannot be acquired within the task's run timeout. Panics raised by a callback are recovered and logged, and the task is rescheduled.
Schedule formats accepted by NewTask and ParseSchedule (case-insensitive):
"every 1 second" "every 61 minutes" "every 2 hours" "every day" "every day 11:15" "16:18" (daily at 16:18) "monday" "saturday 23:13" (weekly on that day)
Usage:
s := tasks.NewScheduler(tasks.WithTickerInterval(time.Second))
s.Add(tasks.NewTaskAtIntervals(30, tasks.Seconds).Do("cleanup", cleanup))
s.Add(tasks.NewTaskAtIntervals(1, tasks.Minutes).Do("report", report, 1, "hello"))
s.Add(tasks.NewTaskOnWeekday(time.Monday, 23, 59).Do("weekly", weekly))
s.Add(tasks.NewTaskDaily(10, 30).Do("daily", daily))
t, err := tasks.NewTask("every day 11:15", tasks.WithID("nightly"))
if err != nil {
return err
}
s.Add(t.Do("nightly", nightly))
if err := s.Start(); err != nil { // spawns the ticker goroutine
return err
}
defer s.Stop() // signals the ticker to exit; does not wait for running tasks
Scheduler.List returns a copy of the task slice, and Task.Schedule returns a snapshot of schedule state. New copies its input Schedule. Use Add/Clear and SetNextRun/UpdateSchedule to change live state. Stop is idempotent, and a stopped scheduler may be started again.
The constructors NewTaskOnWeekday, NewTaskDaily and Task.Do panic on invalid input (out-of-range time, non-function callback, wrong parameter count); NewTask and ParseSchedule return an error for an invalid format string.
Package-level state: TimeNow (the clock, overridable in tests) and the time location set by SetGlobalLocation are process-global and not synchronized.
Index ¶
- Constants
- Variables
- func SetGlobalLocation(newLocation *time.Location)
- type Option
- type Publisher
- type Schedule
- type Scheduler
- type Task
- func New(s *Schedule, ops ...Option) Task
- func NewTask(format string, ops ...Option) (Task, error)
- func NewTaskAtIntervals(interval uint64, unit TimeUnit, ops ...Option) Task
- func NewTaskDaily(hour, minute int, ops ...Option) Task
- func NewTaskOnWeekday(startDay time.Weekday, hour, minute int, ops ...Option) Task
- type TimeUnit
Constants ¶
const DefaultRunTimeoutInterval = time.Second
DefaultRunTimeoutInterval is how long Run waits for the task's run lock before reporting "already running" (override with WithRunTimeout).
const DefaultTickerInterval = time.Second
DefaultTickerInterval is the upper bound for the scheduler tick when WithTickerInterval is not given; Start lowers it to 1/10 of the shortest task interval if that is smaller.
Variables ¶
var TimeNow = time.Now
TimeNow is the clock used by the package to compute and compare run times. It is a process-global variable intended to be overridden in tests.
Functions ¶
func SetGlobalLocation ¶
SetGlobalLocation sets the process-global time location used when computing daily and weekly run times (NewTaskDaily, NewTaskOnWeekday, "hh:mm" formats). It is not synchronized; call it before creating tasks.
Types ¶
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option configures a Scheduler (NewScheduler) or a Task (New, NewTask*). Options not applicable to the receiver are silently ignored: WithTickerInterval applies only to schedulers, WithID and WithRunTimeout only to tasks, and WithPublisher to both.
func WithPublisher ¶ added in v0.16.0
WithPublisher sets the Publisher for a scheduler (propagated to its tasks) or for an individual task.
func WithRunTimeout ¶ added in v0.13.0
WithRunTimeout sets how long Task.Run waits to acquire the task's run lock before giving up (default DefaultRunTimeoutInterval). Task-only.
func WithTickerInterval ¶
WithTickerInterval sets a fixed scheduler tick interval instead of the computed default (see DefaultTickerInterval). Scheduler-only.
type Publisher ¶ added in v0.16.0
type Publisher interface {
// Publish is called with the task whose status changed.
Publish(task Task)
}
Publisher receives task status notifications: Scheduler.Start publishes every task once, and each task publishes itself right before and right after every run (see Task.IsRunning). Implementations must be safe for concurrent use because tasks run in their own goroutines.
type Schedule ¶ added in v0.13.0
type Schedule struct {
// Format is the original string given to ParseSchedule, if any.
Format string
// Interval is the number of Unit between runs.
Interval uint64
// Unit is the time unit of Interval.
Unit TimeUnit
// StartDay is the weekday for Weeks schedules (ignored otherwise).
StartDay time.Weekday
// LastRunAt is when the task last started; nil until first run or
// until an "hh:mm" anchor is applied.
LastRunAt *time.Time
// NextRunAt is when the task is next due.
NextRunAt time.Time
// RunCount is the number of runs; updated atomically by Run.
RunCount uint32
// contains filtered or unexported fields
}
Schedule describes when a task runs. Fields are exported for inspection (e.g. by a Publisher). Task.Schedule returns a snapshot of these fields.
func ParseSchedule ¶ added in v0.13.0
ParseSchedule parses a case-insensitive, space-separated schedule string:
[every] [N] (second|minute|hour|day|week)[s] [hh:mm] <weekday> [hh:mm]
"hh:mm" alone means daily at that time; with a weekday it means weekly. It returns an error when the format is ambiguous or the unit is missing.
func (*Schedule) Duration ¶ added in v0.13.0
Duration returns Interval*Unit as a time.Duration (0 for Never). The value is cached on first call, so later changes to Interval/Unit are not reflected.
func (*Schedule) Equal ¶ added in v0.24.557
Equal reports whether the two schedules have the same Interval, Unit, StartDay and Format; run state is ignored.
func (*Schedule) GetLastRun ¶ added in v0.18.1
GetLastRun returns LastRunAt, or nil if the task has never actually run (RunCount == 0), even when LastRunAt was set as a schedule anchor.
func (*Schedule) ShouldRun ¶ added in v0.13.0
ShouldRun reports whether TimeNow() is past NextRunAt.
func (*Schedule) UpdateNextRun ¶ added in v0.13.0
UpdateNextRun sets NextRunAt to LastRunAt+Duration and returns it. When LastRunAt is nil it is first anchored to now (or, for Weeks, to midnight of the most recent StartDay).
type Scheduler ¶
type Scheduler interface {
// SetPublisher sets the publisher on the scheduler and on every task
// already added; tasks added later inherit it in Add.
SetPublisher(Publisher) Scheduler
// Add appends a task to the pool. It may be called while the scheduler
// is running; the task is picked up on the next tick.
Add(Task) Scheduler
// Get returns the task with the given ID, or nil if not found.
Get(id string) Task
// List returns a snapshot of the registered task slice. Changes to the
// returned slice do not change scheduler membership.
List() []Task
// Clear removes all tasks from the pool. Tasks already started keep running.
Clear()
// Count returns the number of registered tasks.
Count() int
// IsRunning reports whether Start has been called and Stop has not yet
// taken effect.
IsRunning() bool
// Start publishes every task and spawns the ticker goroutine.
// It returns an error if the scheduler is already running.
Start() error
// Stop signals the ticker goroutine to exit. It does not wait for the
// goroutine or for in-flight tasks. A task created by this package
// (New, NewTask*) is not dispatched after Stop returned, and every run
// dispatched before is already counted by RunCount and reported by
// Task.IsRunning until it finishes, although its callback may begin
// executing after Stop returned. The
// scheduler cannot mark a custom Task implementation running: it calls
// its Run on a new goroutine, so a run dispatched before Stop may begin,
// and become visible, after Stop returned. Repeated calls succeed.
Stop() error
// Publish calls Publish on every registered task.
Publish()
}
Scheduler owns a set of tasks and a ticker goroutine that starts due tasks. Its methods are safe for concurrent use.
func NewScheduler ¶
NewScheduler creates a stopped scheduler. Only WithTickerInterval and WithPublisher are meaningful here; task-level options are ignored.
type Task ¶
type Task interface {
// ID returns the task ID: the WithID option value or a generated UUIDv7.
ID() string
// Name returns "<taskName>@<callback function name>" as set by Do;
// empty before Do is called.
Name() string
// RunCount returns how many runs have started; a run is counted when it
// is marked running, right before its callback is invoked.
RunCount() uint32
// Schedule returns a snapshot of the current schedule and run state.
Schedule() *Schedule
// UpdateSchedule replaces the schedule with one parsed from format
// (see ParseSchedule); the next run is recomputed on the next Run.
UpdateSchedule(format string) error
// ShouldRun reports whether the task is not running and NextRunAt has passed.
ShouldRun() bool
// Run executes the callback if the run lock can be acquired within the
// run timeout, then recomputes NextRunAt and returns true. It returns
// false without running if the task is already running. Callback panics
// are recovered and logged.
Run() bool
// SetNextRun forces the next run to TimeNow()+after.
SetNextRun(time.Duration) Task
// Do binds the callback and its arguments and computes the first NextRunAt.
// It panics if task is not a function or the number of params does not
// match its arity; argument types are only checked when the callback is
// invoked (a mismatch is then recovered and logged as an error).
Do(taskName string, task any, params ...any) Task
// IsRunning reports whether the callback is currently executing.
IsRunning() bool
// SetPublisher sets the Publisher notified before and after each run.
SetPublisher(Publisher) Task
// Publish sends the task to its Publisher, if any.
Publish()
}
Task is a scheduled unit of work: a Schedule plus a callback bound with Do. Create one with New, NewTask, NewTaskAtIntervals, NewTaskOnWeekday or NewTaskDaily, bind the callback with Do, then hand it to Scheduler.Add. Task methods synchronize run state. Schedule returns a snapshot; use SetNextRun or UpdateSchedule to change a task's schedule.
func New ¶ added in v0.13.0
New copies a Schedule into a Task. Later changes to the input do not affect the task. Use WithID, WithRunTimeout and WithPublisher to customize; the callback must still be bound with Do.
func NewTask ¶
NewTask creates a task from a schedule string (see ParseSchedule), e.g. "every 5 minutes", "every day 11:15", "16:18", "monday", "saturday 23:13". It returns an error for an invalid format.
func NewTaskAtIntervals ¶
NewTaskAtIntervals creates a task that runs every interval*unit, starting one interval after Do is called. An interval of 0 yields a zero Duration and the task becomes due on every tick.
func NewTaskDaily ¶
NewTaskDaily creates a task that runs every day at hour:minute in the package location. It panics if hour or minute is out of range.
type TimeUnit ¶
type TimeUnit uint
TimeUnit is the unit of a Schedule interval (Seconds, Minutes, ...).
const ( // Never is the zero unit; ParseSchedule rejects it. A Schedule with Unit // Never has a zero Duration and would be due on every tick. Never TimeUnit = iota // Seconds specifies the time unit in seconds Seconds // Minutes specifies the time unit in minutes Minutes // Hours specifies the time unit in hours Hours // Days specifies the time unit in days Days // Weeks specifies the time unit in weeks Weeks )