Background jobs
A tango.Job is a name, an interval and a function. An app records it from its Register callback, and tango.ServeContext runs it on a ticker for as long as the server is up. Use it for small, in-process recurring work: pruning expired rows, a periodic reconciliation, refreshing something the app owns.
The runnable proof is the housekeeping app of examples/board, built step by step in tutorial part 9.
Registering a job
func (App) Register(registry *tango.Registry) error {
return registry.RegisterJob(tango.Job{
Name: "prune-expired-sessions",
Interval: time.Hour,
Run: func(ctx context.Context) error {
return prune(ctx)
},
})
}
RegisterJob only records the job and never calls Run. It returns an error when Name is blank, Interval is not positive, Run is nil, or another job already has the same Name. Names appear in logs and metrics, so keep them stable.
When it runs
ServeContextonly. The scheduler starts and stops withServeContext. A host that callstango.Servenever runs its jobs, becauseServehas no shutdown path to stop them with. See application lifecycle.- The first run is one interval after startup, not at startup. If a job must also run once at boot, call the same function yourself from a
Lifecyclestart hook. - A slow run never piles up. If a run is still going when the next tick arrives, that tick is dropped: not queued, not replayed. The next run starts on the first tick after the previous one finished.
- Each job has its own ticker. One job being slow does not delay another.
Errors, panics and shutdown
- A returned error, or a panic, is logged at error level as the event
tango.scheduler.job_failed(tango.EventJobFailed) with the attributesjob,errorandpanicked. The next tick runs on schedule anyway. - The
ctxpassed toRunis cancelled when the application shuts down. An error that matches that cancellation is treated as an expected stop, not a failure: nothing is logged for it. Passctxto your database calls and other blocking work so a run in progress stops promptly. - When shutdown begins, no new run starts, and the scheduler waits for a run already in progress to return. A
Runthat ignoresctx.Done()therefore holds shutdown up.
Metrics
With a recorder installed (tango.WithRecorder), every run adds 1 to the counter tango_scheduler_job_invocations_total (observability.MetricSchedulerJobInvocations) with the attributes job and outcome, where outcome is one of success, error, panic or canceled. See observability.
What it is not
Jobs are in-process only. There is no cron syntax, retry engine, persistence of what ran, distributed coordination or status page. If you run more than one server process, every process runs every job, so make the work safe to repeat; see running more than one instance. Do not depend on a job for exactly-once delivery or for time-critical work.
Testing a job
Test the function itself, outside the scheduler: call prune(ctx) against a test database (see the testing helpers in testdb), once with a context that is cancelled to check it stops, and once with a failing dependency to check the error it returns.