job.go

v0.4.0
Doc Versions Source
1
package chronos
2
3
import (
4
	"time"
5
6
	"go.bigb.es/auxilia/culpa"
7
)
8
9
// JobFunc is the work a [Job] runs on each fire. It receives a
10
// [ChronosContext] scoped to the run (cancelled on timeout or
11
// shutdown) and returns an error that the supervisor records.
12
type JobFunc func(cc *ChronosContext) error
13
14
// OverlapPolicy decides what happens when a [Job]'s fire time arrives
15
// while its previous run is still in flight.
16
type OverlapPolicy int
17
18
const (
19
	// OverlapSkip drops the new fire when the prior run is still active.
20
	OverlapSkip OverlapPolicy = iota
21
	// OverlapAllow starts the new run concurrently with the prior one.
22
	OverlapAllow
23
)
24
25
// Job is a schedule paired with the function to run and per-job
26
// options. Construct one with [NewCron] or [NewEvery], then refine it
27
// with the chained builders ([Job.Delta], [Job.Timeout], etc.).
28
//
29
// Construction errors are stashed in buildErr rather than panicked
30
// (IV6); they surface when the job is registered. The Must* variants
31
// panic instead. The paired *Set bools record whether [Job.Timeout]
32
// and [Job.SlotWait] were called, so the supervisor can tell an
33
// explicit zero from "unset" (GPC1).
34
type Job struct {
35
	name        string
36
	sched       Schedule
37
	fn          JobFunc
38
	delta       time.Duration
39
	timeout     time.Duration
40
	timeoutSet  bool
41
	slotWait    time.Duration
42
	slotWaitSet bool
43
	overlap     OverlapPolicy
44
	buildErr    error
45
}
46
47
// NewCron builds a [Job] from a cron spec (5- or 6-field, an @-macro, or
48
// "@every <dur>"). A malformed spec is stashed in buildErr (IV6) rather
49
// than panicked; see [MustCron] for the panicking variant.
50
func NewCron(spec string, fn JobFunc) *Job {
51
	sched, err := parseSpec(spec)
52
	return &Job{sched: sched, fn: fn, buildErr: err}
53
}
54
55
// NewEvery builds a [Job] that fires every total of the given spans.
56
// The spans are summed component-wise (AS1); a non-positive total (empty,
57
// all-zero, or any negative component) is rejected via buildErr (IV6),
58
// since [everySchedule] requires a forward-moving span to honour the
59
// strictly-after contract (IV1).
60
func NewEvery(fn JobFunc, every ...Span) *Job {
61
	var sum Span
62
	for _, s := range every {
63
		sum = sum.Add(s)
64
	}
65
	if sum.IsZero() || sum.Years < 0 || sum.Months < 0 || sum.Days < 0 || sum.Dur < 0 {
66
		return &Job{fn: fn, buildErr: culpa.New("chronos: NewEvery requires a positive total span")}
67
	}
68
	return &Job{sched: everySchedule{span: sum}, fn: fn}
69
}
70
71
// MustCron is [NewCron] that panics on a malformed spec.
72
func MustCron(spec string, fn JobFunc) *Job {
73
	j := NewCron(spec, fn)
74
	if j.buildErr != nil {
75
		panic(j.buildErr)
76
	}
77
	return j
78
}
79
80
// MustEvery is [NewEvery] that panics on an empty or all-zero span.
81
func MustEvery(fn JobFunc, every ...Span) *Job {
82
	j := NewEvery(fn, every...)
83
	if j.buildErr != nil {
84
		panic(j.buildErr)
85
	}
86
	return j
87
}
88
89
// Delta offsets each fire by d relative to the schedule's nominal time.
90
func (j *Job) Delta(d time.Duration) *Job {
91
	j.delta = d
92
	return j
93
}
94
95
// Timeout caps each run at d; the run's [ChronosContext] is cancelled
96
// when it elapses. Calling it marks the timeout as explicitly set (GPC1).
97
func (j *Job) Timeout(d time.Duration) *Job {
98
	j.timeout = d
99
	j.timeoutSet = true
100
	return j
101
}
102
103
// SlotWait bounds how long a fire waits for an in-flight run before the
104
// overlap policy applies. Calling it marks slotWait as explicitly set.
105
func (j *Job) SlotWait(d time.Duration) *Job {
106
	j.slotWait = d
107
	j.slotWaitSet = true
108
	return j
109
}
110
111
// Name sets the job's name, used for logging and self-deactivation.
112
func (j *Job) Name(name string) *Job {
113
	j.name = name
114
	return j
115
}
116
117
// OnOverlap sets the [OverlapPolicy] applied when a fire arrives during
118
// a still-running prior invocation.
119
func (j *Job) OnOverlap(p OverlapPolicy) *Job {
120
	j.overlap = p
121
	return j
122
}
123

Source Files