A small in-process scheduler for Go tasks that need to run on time.
Tasks is built for recurring, quick-running jobs where scheduler-induced jitter needs to stay low and the scheduler should stay out of the way. Each invocation runs in its own goroutine, so one slow task does not make the whole schedule trip over its shoelaces.
Use Tasks when you want recurring work without cron wiring, a worker fleet, or a pile of scheduling boilerplate. It is an in-process scheduler, not a durable queue or distributed job runner; if your process exits, your schedule exits with it. That tradeoff keeps the package small, fast, and easy to reason about.
go get github.com/madflojo/tasks- Accurate recurring execution: Each invocation runs independently, so a long-running task does not block unrelated schedules.
- Small API: Intervals use Go's
time.Duration; no custom cron language required. - Delayed and one-time runs: Use
StartAfterandRunOncefor jobs that should begin later or run just once. - Overlap control: Use
RunSingleInstanceto skip a run when the previous invocation is still working. No dogpiling. - Task context support: Pass user-defined context and task metadata into callbacks with
FuncWithTaskContext,ErrFuncWithTaskContext, andTaskContext.ID(). - Stable IDs and branchable errors: Use
AddWithIDfor deterministic identifiers anderrors.Isfor scheduler errors.
Calling Del or Stop prevents delayed or future invocations, including tasks waiting on StartAfter. These methods do
not interrupt task functions that have already started; once your callback is running, it gets to finish its lap.
Error handlers run as part of a task's execution lifecycle. For RunOnce tasks, self-deletion happens after the task
function and any configured error handler finish.
Scheduler validation and lookup errors are exposed as sentinel errors so callers can branch with errors.Is:
ErrNilTaskErrIDInUseErrInvalidIDErrMissingTaskFuncErrInvalidIntervalErrTaskNotFoundErrTaskPanic
if errors.Is(err, tasks.ErrIDInUse) {
// Pick another ID or update the existing task.
}If a task callback panics with a non-nil recovered value, Tasks recovers it and reports ErrTaskPanic through the task error callback.
If the error callback itself panics, Tasks recovers and drops that panic.
Error callbacks run in the same goroutine as the task execution. Slow error handlers extend the task lifecycle, including
RunOnce deletion and RunSingleInstance overlap prevention.
Here are some examples to help you get Tasks doing useful work without much ceremony.
// Start the Scheduler
scheduler := tasks.New()
defer scheduler.Stop()
// Add a task
id, err := scheduler.Add(&tasks.Task{
Interval: 30 * time.Second,
TaskFunc: func() error {
// Put your logic here
return nil
},
})
if err != nil {
// Handle error
}Sometimes schedules need to start later, not right now with a tiny starter pistol. Set StartAfter to delay the start
of a task's interval schedule. Deleting the task or stopping the scheduler before StartAfter prevents the delayed run
from being scheduled.
// Add a recurring task for every 30 days, starting 30 days from now
id, err := scheduler.Add(&tasks.Task{
Interval: 30 * (24 * time.Hour),
StartAfter: time.Now().Add(30 * (24 * time.Hour)),
TaskFunc: func() error {
// Put your logic here
return nil
},
})
if err != nil {
// Handle error
}Some jobs only need one lap. The example below schedules a task to run once after waiting for 60 seconds.
// Add a one-time task for 60 seconds from now
id, err := scheduler.Add(&tasks.Task{
Interval: 60 * time.Second,
RunOnce: true,
TaskFunc: func() error {
// Put your logic here
return nil
},
})
if err != nil {
// Handle error
}Tasks lets callers define custom error handling with a callback that runs when a task returns an error. The example below schedules a task that logs when things go sideways.
If both ErrFunc and ErrFuncWithTaskContext are set, ErrFuncWithTaskContext is used.
// Add a task with custom error handling
id, err := scheduler.Add(&tasks.Task{
Interval: 30 * time.Second,
TaskFunc: func() error {
// Put your logic here
return nil
},
ErrFunc: func(e error) {
log.Printf("An error occurred when executing task %s - %s", id, e)
},
})
if err != nil {
// Handle error
}Use RunSingleInstance when a task might take longer than its interval and overlapping executions should be skipped.
No dogpiling, no duplicate workers stepping on each other.
id, err := scheduler.Add(&tasks.Task{
Interval: 30 * time.Second,
RunSingleInstance: true,
TaskFunc: func() error {
// Put your logic here
return nil
},
})
if err != nil {
// Handle error
}Use the context-aware callbacks when you want to pass a user-defined context into task execution and error handling.
ctx := context.Background()
id, err := scheduler.Add(&tasks.Task{
Interval: 30 * time.Second,
TaskContext: tasks.TaskContext{Context: ctx},
FuncWithTaskContext: func(taskCtx tasks.TaskContext) error {
log.Printf("running task %s", taskCtx.ID())
return nil
},
ErrFuncWithTaskContext: func(taskCtx tasks.TaskContext, err error) {
log.Printf("task %s failed: %v", taskCtx.ID(), err)
},
})
if err != nil {
// Handle error
}Use AddWithID when you want to provide your own stable identifier for a task. Handy when "whatever ID the scheduler
picked" is not quite descriptive enough for future you.
err := scheduler.AddWithID("nightly-report", &tasks.Task{
Interval: time.Hour,
TaskFunc: func() error {
// Put your logic here
return nil
},
})
if err != nil {
// Handle error
}For more details on usage, see the GoDoc.
Contributions are welcome! Please see CONTRIBUTING.md for more details.
Common local workflows are available through the repository Makefile:
make buildmake testsmake benchmarksmake coveragemake lintmake format