Documentation
¶
Overview ¶
Package scheduling is the events an application runs on a clock.
What moved here ¶
hesape/scheduler used to hold a scheduler of its own, and it is gone: Task is Event, its Schedule is CronExpression, its Scheduler loop is Module and ScheduleWorkCommand, and its Module is Module. Two schedulers is two ways to say the same thing, and only one stayed.
Authorization and tenancy ¶
Every scheduled event carries an auth.Action, and what it hands a callback is the Grant built from that action and the tenant. An event that reads a customer's rows calls PerTenant, and the runner expands it to one Grant per tenant instead of reading a tenant off a flag.
The cron expression ¶
Five fields, not six: no seconds. Sub-minute repetition is EverySecond and its companions, which set RepeatSeconds and leave the expression on every minute, because a schedule that says "@every 90s" is a busy loop with a nicer name.
What makes that true is Runner.repeatEvents: after the due events have run, the ones that repeat are run again every RepeatSeconds until the minute is over. Without it the frequency methods would be a field nobody reads, and EveryFifteenSeconds would run once a minute instead of four times.
Index ¶
- Constants
- func NormalizeCommand(command string) string
- type CacheAware
- type CacheEventMutex
- type CacheSchedulingMutex
- type Callback
- type CallbackEvent
- type CommandBuilder
- type Commands
- func (c Commands) All() []console.Command
- func (c Commands) ScheduleClearCacheCommand() console.Command
- func (c Commands) ScheduleFinishCommand() console.Command
- func (c Commands) ScheduleInterruptCommand() console.Command
- func (c Commands) ScheduleListCommand() console.Command
- func (c Commands) ScheduleRunCommand() console.Command
- func (c Commands) ScheduleTestCommand() console.Command
- func (c Commands) ScheduleWorkCommand() console.Command
- type CronExpression
- type Dispatcher
- type Event
- func (e *Event) Action(action auth.Action) *Event
- func (e *Event) After(callback Hook) *Event
- func (e *Event) AppendOutputTo(location string) *Event
- func (e *Event) At(t string) *Event
- func (e *Event) Before(callback Hook) *Event
- func (e *Event) Between(startTime, endTime string) *Event
- func (e *Event) BuildCommand() ([]string, error)
- func (e *Event) CallAfterCallbacks(ctx context.Context) error
- func (e *Event) CallBeforeCallbacks(ctx context.Context) error
- func (e *Event) CreateMutexNameUsing(resolver func(*Event) string) *Event
- func (e *Event) Cron(expression string) *Event
- func (e *Event) Daily() *Event
- func (e *Event) DailyAt(t string) *Event
- func (e *Event) Days(days ...string) *Event
- func (e *Event) DaysOfMonth(days ...int) *Event
- func (e *Event) Description(description string) *Event
- func (e *Event) EmailOutputOnFailure(mailer Mailer, addresses []string) *Event
- func (e *Event) EmailOutputTo(mailer Mailer, addresses []string, onlyIfOutputExists ...bool) *Event
- func (e *Event) EmailWrittenOutputTo(mailer Mailer, addresses []string) *Event
- func (e *Event) Environments(environments ...string) *Event
- func (e *Event) EvenInMaintenanceMode() *Event
- func (e *Event) EveryFifteenMinutes() *Event
- func (e *Event) EveryFifteenSeconds() *Event
- func (e *Event) EveryFiveMinutes() *Event
- func (e *Event) EveryFiveSeconds() *Event
- func (e *Event) EveryFourHours(offset ...int) *Event
- func (e *Event) EveryFourMinutes() *Event
- func (e *Event) EveryMinute() *Event
- func (e *Event) EveryOddHour(offset ...int) *Event
- func (e *Event) EverySecond() *Event
- func (e *Event) EverySixHours(offset ...int) *Event
- func (e *Event) EveryTenMinutes() *Event
- func (e *Event) EveryTenSeconds() *Event
- func (e *Event) EveryThirtyMinutes() *Event
- func (e *Event) EveryThirtySeconds() *Event
- func (e *Event) EveryThreeHours(offset ...int) *Event
- func (e *Event) EveryThreeMinutes() *Event
- func (e *Event) EveryTwentySeconds() *Event
- func (e *Event) EveryTwoHours(offset ...int) *Event
- func (e *Event) EveryTwoMinutes() *Event
- func (e *Event) EveryTwoSeconds() *Event
- func (e *Event) ExpiresAt() int
- func (e *Event) FiltersPass(ctx context.Context) bool
- func (e *Event) Finish(ctx context.Context, exitCode int) error
- func (e *Event) Fridays() *Event
- func (e *Event) GetAction() auth.Action
- func (e *Event) GetDefaultOutput() string
- func (e *Event) GetDescription() string
- func (e *Event) GetExpression() string
- func (e *Event) GetSummaryForDisplay() string
- func (e *Event) GetTimezone() *time.Location
- func (e *Event) Grant() auth.Grant
- func (e *Event) Hourly() *Event
- func (e *Event) HourlyAt(offset ...int) *Event
- func (e *Event) IsDue(now time.Time, environment string, downForMaintenance bool) bool
- func (e *Event) IsRepeatable() bool
- func (e *Event) LastDayOfMonth(t ...string) *Event
- func (e *Event) Mondays() *Event
- func (e *Event) Monthly() *Event
- func (e *Event) MonthlyOn(dayOfMonth int, t ...string) *Event
- func (e *Event) MutexName() string
- func (e *Event) Name(description string) *Event
- func (e *Event) NextRunDate(after time.Time) time.Time
- func (e *Event) OnFailure(callback Hook) *Event
- func (e *Event) OnFailureWithOutput(callback func(ctx context.Context, output string) error, ...) *Event
- func (e *Event) OnOneServer() *Event
- func (e *Event) OnSuccess(callback Hook) *Event
- func (e *Event) OnSuccessWithOutput(callback func(ctx context.Context, output string) error, ...) *Event
- func (e *Event) PerTenant() *Event
- func (e *Event) PingBefore(pinger Pinger, url string) *Event
- func (e *Event) PingBeforeIf(condition bool, pinger Pinger, url string) *Event
- func (e *Event) PingOnFailure(pinger Pinger, url string) *Event
- func (e *Event) PingOnFailureIf(condition bool, pinger Pinger, url string) *Event
- func (e *Event) PingOnSuccess(pinger Pinger, url string) *Event
- func (e *Event) PingOnSuccessIf(condition bool, pinger Pinger, url string) *Event
- func (e *Event) PreventOverlapsUsing(mutex EventMutex) *Event
- func (e *Event) Quarterly() *Event
- func (e *Event) QuarterlyOn(dayOfQuarter int, t ...string) *Event
- func (e *Event) RepeatSeconds() int
- func (e *Event) Run(ctx context.Context) error
- func (e *Event) RunInBackground() *Event
- func (e *Event) RunsInBackground() bool
- func (e *Event) RunsInEnvironment(environment string) bool
- func (e *Event) RunsInMaintenanceMode() bool
- func (e *Event) RunsOnOneServer() bool
- func (e *Event) RunsPerTenant() bool
- func (e *Event) Saturdays() *Event
- func (e *Event) SendOutputTo(location string, append ...bool) *Event
- func (e *Event) ShouldRepeatNow() bool
- func (e *Event) ShouldSkipDueToOverlapping(ctx context.Context) (bool, error)
- func (e *Event) Skip(reject Filter) *Event
- func (e *Event) SkipTrue(condition bool) *Event
- func (e *Event) StoreOutput() *Event
- func (e *Event) Sundays() *Event
- func (e *Event) Tenant(tenant string) *Event
- func (e *Event) Then(callback Hook) *Event
- func (e *Event) ThenPing(pinger Pinger, url string) *Event
- func (e *Event) ThenPingIf(condition bool, pinger Pinger, url string) *Event
- func (e *Event) ThenWithOutput(callback func(ctx context.Context, output string) error, ...) *Event
- func (e *Event) Thursdays() *Event
- func (e *Event) Timezone(timezone *time.Location) *Event
- func (e *Event) Tuesdays() *Event
- func (e *Event) TwiceDaily(hours ...int) *Event
- func (e *Event) TwiceDailyAt(first, second, offset int) *Event
- func (e *Event) TwiceMonthly(first, second int, t ...string) *Event
- func (e *Event) UnlessBetween(startTime, endTime string) *Event
- func (e *Event) UseProcessFactory(factory *process.Factory) *Event
- func (e *Event) User(user string) *Event
- func (e *Event) Wednesdays() *Event
- func (e *Event) Weekdays() *Event
- func (e *Event) Weekends() *Event
- func (e *Event) Weekly() *Event
- func (e *Event) WeeklyOn(dayOfWeek int, t ...string) *Event
- func (e *Event) When(filter Filter) *Event
- func (e *Event) WhenTrue(condition bool) *Event
- func (e *Event) WithoutOverlapping(expiresAt ...int) *Event
- func (e *Event) Yearly() *Event
- func (e *Event) YearlyOn(month, dayOfMonth int, t ...string) *Event
- type EventMutex
- type Filter
- type Hook
- type Interrupter
- type Listener
- type Mailer
- type Module
- type PendingEventAttributes
- type Pinger
- type Runner
- type Schedule
- func (s *Schedule) Attributes() *PendingEventAttributes
- func (s *Schedule) Call(callback Callback) *CallbackEvent
- func (s *Schedule) Command(command string, parameters ...string) *Event
- func (s *Schedule) CompileArrayInput(key string, values []string) string
- func (s *Schedule) DueEvents(at time.Time) []*Event
- func (s *Schedule) Events() []*Event
- func (s *Schedule) Exec(command string, parameters ...string) *Event
- func (s *Schedule) ForgetMutexCache()
- func (s *Schedule) Group(events func(*Schedule))
- func (s *Schedule) Job(job any, queue, connection string) *CallbackEvent
- func (s *Schedule) ServerShouldRun(ctx context.Context, event *Event, at time.Time) (bool, error)
- func (s *Schedule) UseCache(locks *cache.Locks) *Schedule
- func (s *Schedule) UseDispatcher(dispatcher Dispatcher) *Schedule
- type SchedulingMutex
- type Tenants
Constants ¶
const ( // Sunday is 0, which is what cron calls it. Sunday = 0 // Monday is 1. Monday = 1 // Tuesday is 2. Tuesday = 2 // Wednesday is 3. Wednesday = 3 // Thursday is 4. Thursday = 4 // Friday is 5. Friday = 5 // Saturday is 6. Saturday = 6 )
The days of the week, as Schedule declares them.
Variables ¶
This section is empty.
Functions ¶
func NormalizeCommand ¶
NormalizeCommand rewrites the running binary's path out of a command line.
It exists so the mutex name of an event does not change when the binary moves: two replicas installed under different paths must resolve to the same lock, or neither ever loses the race.
Types ¶
type CacheAware ¶
type CacheAware interface {
// UseStore points the mutex at another lock issuer.
UseStore(locks *cache.Locks)
}
CacheAware is a mutex that can be pointed at another store.
The issuer is passed explicitly rather than resolved from a container.
type CacheEventMutex ¶
type CacheEventMutex struct {
// contains filtered or unexported fields
}
CacheEventMutex is the EventMutex over the cache locks.
func NewCacheEventMutex ¶
func NewCacheEventMutex(locks *cache.Locks) *CacheEventMutex
NewCacheEventMutex returns the mutex over an issuer.
func (*CacheEventMutex) Create ¶
Create takes the mutex for the event.
A lock another process holds is false and no error: that is the mutex working.
func (*CacheEventMutex) Exists ¶
Exists reports whether the mutex is held.
It takes the lock to find out and gives it straight back.
func (*CacheEventMutex) Forget ¶
func (m *CacheEventMutex) Forget(ctx context.Context, event *Event) error
Forget releases the mutex this process took.
A lock this process does not hold is left alone: force-releasing it would let the replica that took the lock second delete the mark of the replica that took it first.
func (*CacheEventMutex) UseStore ¶
func (m *CacheEventMutex) UseStore(locks *cache.Locks)
UseStore points the mutex at another lock issuer.
type CacheSchedulingMutex ¶
type CacheSchedulingMutex struct {
// contains filtered or unexported fields
}
CacheSchedulingMutex is the SchedulingMutex over the cache locks.
func NewCacheSchedulingMutex ¶
func NewCacheSchedulingMutex(locks *cache.Locks) *CacheSchedulingMutex
NewCacheSchedulingMutex returns the mutex over an issuer.
func (*CacheSchedulingMutex) Create ¶
func (m *CacheSchedulingMutex) Create(ctx context.Context, event *Event, at time.Time) (bool, error)
Create claims the window for this replica.
The lock is taken and never released: it marks the window as claimed rather than guarding the work as a mutex would.
func (*CacheSchedulingMutex) Exists ¶
func (m *CacheSchedulingMutex) Exists(ctx context.Context, event *Event, at time.Time) (bool, error)
Exists reports whether the window is already claimed.
func (*CacheSchedulingMutex) UseStore ¶
func (m *CacheSchedulingMutex) UseStore(locks *cache.Locks)
UseStore points the mutex at another lock issuer.
type Callback ¶
Callback is the work a scheduled closure does.
It carries the Grant its event was declared with: a scheduled closure is a path to a repository, so it is handed the Grant directly rather than reaching for one.
type CallbackEvent ¶
type CallbackEvent struct {
*Event
}
CallbackEvent is a scheduled closure rather than a command line.
It is an Event with the process replaced by a function call, which is why it embeds one: the frequencies, the filters, the mutex and the callbacks are all the same.
Because Go has no virtual dispatch and a Schedule holds *Event, running, executing and summarizing for display all live on Event and decide their behavior by whether there is a callback. What is left here is what a caller reaches on the way in: the two guards that demand a name before a lock can be named after it, and the refusal to background a closure.
func NewCallbackEvent ¶
func NewCallbackEvent(mutex EventMutex, callback Callback, timezone *time.Location) *CallbackEvent
NewCallbackEvent returns the event for a closure.
The mutex name resolver is installed here rather than overridden as a method, for the reason the type says: a mutex holding an *Event would otherwise name the lock after a command line this event does not have.
func (*CallbackEvent) OnOneServer ¶
func (c *CallbackEvent) OnOneServer() *Event
OnOneServer allows the closure to run on exactly one replica per window.
It requires a name for the same reason WithoutOverlapping does.
func (*CallbackEvent) RunInBackground ¶
func (c *CallbackEvent) RunInBackground() *Event
RunInBackground is refused.
A command sent to the background is a program the scheduler waits for beside the schedule; a closure has no program, so there would be nothing to wait for and the call would mean running the closure itself next to everything that reads the event. It panics rather than returning an error because it is a mistake in the schedule, caught the first time the schedule is declared.
func (*CallbackEvent) ShouldSkipDueToOverlapping ¶
func (c *CallbackEvent) ShouldSkipDueToOverlapping(ctx context.Context) (bool, error)
ShouldSkipDueToOverlapping reports whether another copy holds the mutex.
An event with no description has no stable mutex name, so it never skips.
func (*CallbackEvent) WithoutOverlapping ¶
func (c *CallbackEvent) WithoutOverlapping(expiresAt ...int) *Event
WithoutOverlapping stops the closure from running while a copy of it is.
The mutex is named after the description, so without one there is nothing to name the lock and two copies would each take a different key.
type CommandBuilder ¶
type CommandBuilder struct{}
CommandBuilder turns the command line one event carries into the program and arguments it is started with.
It is a type with one method and no state, kept as a name because splitting a line and putting it behind sudo are two separate decisions and each is worth reading on its own.
func (CommandBuilder) BuildCommand ¶
func (b CommandBuilder) BuildCommand(event *Event) ([]string, error)
BuildCommand renders the program and its arguments.
Nothing here builds a line for a shell. The event's command line is split into words, and every character a shell would act on -- a semicolon, an ampersand, a pipe, a redirection, a dollar sign, a backquote, an asterisk -- is an ordinary character of the word it appears in. That is the whole difference between this and a shell, and it is the difference between an argument and a second command.
A command line of nothing but spaces builds nothing, which is what an event with no command means.
type Commands ¶
type Commands struct {
// Schedule is what the commands read and run.
Schedule *Schedule
// Runner runs the due events. Nil means one is built over the schedule.
Runner *Runner
// Interrupter backs schedule:interrupt and the worker's check of it.
Interrupter Interrupter
// Now is the clock, for tests. Nil means time.Now.
Now func() time.Time
}
Commands is the set of scheduling commands, ready to register.
It is seven commands as values, built over one schedule. interrupter and cleaner may be nil, and then the two commands that need them say so rather than doing nothing.
func (Commands) All ¶
All returns every scheduling command.
The order is the order they are listed in, which is alphabetical by name because that is how the console help sorts them anyway.
func (Commands) ScheduleClearCacheCommand ¶
ScheduleClearCacheCommand is the command that deletes the overlap mutexes left behind.
It is `schedule:clear-cache`. It is for the case a process died holding a lock and the operator does not want to wait out the ttl.
func (Commands) ScheduleFinishCommand ¶
ScheduleFinishCommand is the command that reports the exit code of an event that ran in the background.
It is `schedule:finish {id} {code=0}`, and it is hidden.
Nothing generates it any more: a backgrounded event is waited for and finished by the process that started it, so this is what is left for the case that one ended without finishing -- a replica killed mid-run leaves the mutex held and the after callbacks unrun, and this releases and runs them by name.
It runs in a process that has already lost the run it is reporting on, which shapes two decisions.
A mutex name is a digest of the expression and the command line, so two declarations of the same command share one: every event with that mutex name is finished, not only the first, or the second would keep its lock and never run its after callbacks.
An id that matches nothing succeeds rather than failing: a deploy that changes the schedule while a backgrounded event is still running must not turn into a failed background job. It says which id it could not find, because a silent success is how the same deploy hides a mutex that will now be held until it expires.
func (Commands) ScheduleInterruptCommand ¶
ScheduleInterruptCommand is the command that stops the running schedule worker at its next tick.
It is `schedule:interrupt`. It leaves a mark that expires, so a worker started afterwards is not stopped by an interrupt meant for the one before it. The mark lives until the end of the current minute.
func (Commands) ScheduleListCommand ¶
ScheduleListCommand is the command that lists the scheduled events and when they run next.
It is `schedule:list`, with --timezone to read the times somewhere else.
func (Commands) ScheduleRunCommand ¶
ScheduleRunCommand is the command that runs the events that are due now.
It is `schedule:run`, with --whisper to keep quiet when there was nothing to do.
func (Commands) ScheduleTestCommand ¶
ScheduleTestCommand is the command that runs one scheduled event by hand.
It is `schedule:test {--name=}`. It runs the event through the same path the runner does -- same mutex, same Grant, same callbacks -- which is what makes the manual run auditable rather than a back door.
func (Commands) ScheduleWorkCommand ¶
ScheduleWorkCommand is the command that runs the schedule for as long as the process lives.
It is `schedule:work`. It calls the same runner schedule:run does, in the same process, which is one artifact instead of two and no crontab to configure.
It ticks at the top of each minute rather than every sixty seconds from boot, because an event specified as "0 3 * * *" has to fire at 3:00 and not at 3:00 plus however long after a deploy the process happened to start.
type CronExpression ¶
type CronExpression struct {
// contains filtered or unexported fields
}
CronExpression is a parsed five-field cron expression.
It is written here rather than pulled in as a dependency, because the module declares only one and this is eighty lines.
Five fields, not six: no seconds. Sub-minute repetition is Event.RepeatSeconds -- the expression stays every-minute and Runner.repeatEvents loops within the minute.
What it supports is the syntax people write: `*`, `5`, `1-5`, `*/15`, `1,15,30`, and the shorthands `@hourly`, `@daily`, `@midnight`, `@weekly` and `@monthly`.
func MustParseCronExpression ¶
func MustParseCronExpression(spec string) CronExpression
MustParseCronExpression is ParseCronExpression for a constant.
It panics, which is right for an expression written in source: a schedule nobody can parse must not reach the runner.
func ParseCronExpression ¶
func ParseCronExpression(spec string) (CronExpression, error)
ParseCronExpression reads a cron expression.
func (CronExpression) GetNextRunDate ¶
func (c CronExpression) GetNextRunDate(t time.Time) time.Time
GetNextRunDate returns the first minute after t that matches.
It walks minute by minute, bounded to a year: an expression that matches nothing in a year matches nothing at all -- February 30th, for instance -- and returning the zero time is what lets schedule:list say so instead of hanging.
func (CronExpression) IsDue ¶
func (c CronExpression) IsDue(t time.Time) bool
IsDue reports whether the expression fires in the minute of t.
Day-of-month and day-of-week are OR when both are restricted, which is the behaviour of every cron since Vixie: "0 0 1 * 1" means the first of the month AND every Monday, not their intersection. It surprises people, and matching the surprise is better than being the one implementation that differs.
func (CronExpression) String ¶
func (c CronExpression) String() string
String returns the expression it was parsed from, for schedule:list.
type Dispatcher ¶
type Dispatcher interface {
// Dispatch pushes the job, on the named queue and connection. Either may be
// empty, and then the job's own is used.
Dispatch(ctx context.Context, job any, queue, connection string) error
}
Dispatcher is what Job pushes onto the queue.
It is declared here rather than imported: the queue is not a dependency of the scheduler, and what a schedule needs of a dispatcher is one method.
type Event ¶
type Event struct {
// Command is the command line the event runs. CommandBuilder splits it into
// the program and the arguments a process is started with; no shell reads
// it.
Command string
// Output is where the command's output is sent. It is a path, and the
// default is the null device of the system.
Output string
// ShouldAppendOutput says whether the output is appended rather than
// replacing what is there.
ShouldAppendOutput bool
// Mutex is what stops the event from overlapping itself.
Mutex EventMutex
// MutexNameResolver overrides how the mutex is named. Nil means the default
// name, which is a digest of the expression and the command.
MutexNameResolver func(*Event) string
// ExitCode is what the command ended with, once it has.
ExitCode int
// contains filtered or unexported fields
}
Event is one scheduled command.
Its fields are unexported, with setters and getters standing in for them, because Go cannot have a field and a method that share a name -- such as WithoutOverlapping.
Two fields carry the event's authorization: Action, which the event's Grant is issued for, and PerTenant, which says the event is expanded to one run per tenant with a Grant each. Without them a scheduled task would be the one path into the database with no authorization on it.
func NewEvent ¶
func NewEvent(mutex EventMutex, command string, timezone *time.Location) *Event
NewEvent returns an event for a command line.
It sets the mutex, the command and the timezone, and defaults the output for the system.
func (*Event) Action ¶
Action is what the event's Grant is issued for.
A scheduled task reaches a repository the same way a request does, through a Grant that a Policy checked. An event with no action gets a Grant that passes nothing, which is a task that can print and can call an API and cannot read a customer's rows -- and having to say so is the point.
func (*Event) AppendOutputTo ¶
AppendOutputTo adds the command's output to what is already at a path.
func (*Event) Between ¶
Between runs the event only between two times of day.
The times are "HH:MM", and a range that ends before it starts crosses midnight -- "22:00" to "04:00" is the six hours it reads as, not the eighteen it would be if the comparison were naive.
func (*Event) BuildCommand ¶
BuildCommand is the program and the arguments the event runs.
It is a list and not a line: nothing joins these back together, and nothing parses them. The error is a command line that ends inside a quoted word.
func (*Event) CallAfterCallbacks ¶
CallAfterCallbacks runs every callback registered with Then.
Every one of them runs even when an earlier one failed, and the first error is what comes back: a ping that could not be sent must not stop the mail that reports the output.
func (*Event) CallBeforeCallbacks ¶
CallBeforeCallbacks runs every callback registered with Before.
func (*Event) CreateMutexNameUsing ¶
CreateMutexNameUsing overrides how the mutex is named.
func (*Event) Cron ¶
Cron sets the expression the event fires on.
Every other method in this file ends up here.
func (*Event) Days ¶
Days limits the event to days of the week.
It takes the field as written, so both Days("1-5") and Days("1", "3") say what they read as.
func (*Event) DaysOfMonth ¶
DaysOfMonth runs the event on the given days of the month, at midnight.
Naming no day panics. Without it, joinInts would fall back to "0", and "0 0 0 * *" is not a cron expression: there is no zeroth day of the month, so Schedule would refuse it at registration and the application would not start. The mistake is the empty call, and this is the last place it can still be named.
func (*Event) Description ¶
Description sets the readable description of the event.
func (*Event) EmailOutputOnFailure ¶
EmailOutputOnFailure mails the output when the command failed.
It mails even when there was no output: a task that failed silently is the one worth reading.
func (*Event) EmailOutputTo ¶
EmailOutputTo mails the output of the event.
onlyIfOutputExists defaults to true: a task that printed nothing sends no mail.
func (*Event) EmailWrittenOutputTo ¶
EmailWrittenOutputTo mails the output only when there was some.
func (*Event) Environments ¶
Environments limits the environments the event runs in.
Naming none means every one.
func (*Event) EvenInMaintenanceMode ¶
EvenInMaintenanceMode lets the event run while the application is down.
func (*Event) EveryFifteenMinutes ¶
EveryFifteenMinutes runs the event every fifteen minutes.
func (*Event) EveryFifteenSeconds ¶
EveryFifteenSeconds runs the event every fifteen seconds.
func (*Event) EveryFiveMinutes ¶
EveryFiveMinutes runs the event every five minutes.
func (*Event) EveryFiveSeconds ¶
EveryFiveSeconds runs the event every five seconds.
func (*Event) EveryFourHours ¶
EveryFourHours runs the event every four hours.
func (*Event) EveryFourMinutes ¶
EveryFourMinutes runs the event every four minutes.
func (*Event) EveryMinute ¶
EveryMinute runs the event every minute.
func (*Event) EveryOddHour ¶
EveryOddHour runs the event on every odd hour.
func (*Event) EverySecond ¶
EverySecond runs the event every second.
The expression stays every-minute and the runner loops within the minute, which is what repeatEvery sets up.
func (*Event) EverySixHours ¶
EverySixHours runs the event every six hours.
func (*Event) EveryTenMinutes ¶
EveryTenMinutes runs the event every ten minutes.
func (*Event) EveryTenSeconds ¶
EveryTenSeconds runs the event every ten seconds.
func (*Event) EveryThirtyMinutes ¶
EveryThirtyMinutes runs the event every thirty minutes.
func (*Event) EveryThirtySeconds ¶
EveryThirtySeconds runs the event every thirty seconds.
func (*Event) EveryThreeHours ¶
EveryThreeHours runs the event every three hours.
func (*Event) EveryThreeMinutes ¶
EveryThreeMinutes runs the event every three minutes.
func (*Event) EveryTwentySeconds ¶
EveryTwentySeconds runs the event every twenty seconds.
func (*Event) EveryTwoHours ¶
EveryTwoHours runs the event every two hours.
func (*Event) EveryTwoMinutes ¶
EveryTwoMinutes runs the event every two minutes.
func (*Event) EveryTwoSeconds ¶
EveryTwoSeconds runs the event every two seconds.
func (*Event) ExpiresAt ¶
ExpiresAt reports how many minutes the overlap lock lives. Go cannot have a field and a method of the same name.
func (*Event) FiltersPass ¶
FiltersPass reports whether every when passed and no skip matched.
It records the time: a sub-minute repeat counts from the last check.
func (*Event) Finish ¶
Finish records the exit code, runs the after callbacks and releases the mutex.
The mutex is released whatever the callbacks did.
func (*Event) GetAction ¶
GetAction is what the event is authorized for, and it reads what Action set.
func (*Event) GetDefaultOutput ¶
GetDefaultOutput is where output goes when nobody said.
func (*Event) GetDescription ¶
GetDescription reports the description, or empty.
Go cannot have a field and a method of the same name, so the setter is Description and the getter is named for what it does instead.
func (*Event) GetExpression ¶
GetExpression is the cron expression the event fires on.
func (*Event) GetSummaryForDisplay ¶
GetSummaryForDisplay is the event as a listing prints it.
It is the description when it has one, the command line when it is a command, and "Callback" when it is a closure that was never named.
The command line, and not what the event is started with: where the output goes and which user it runs as are how the command is run rather than what it is, and a listing that repeated them printed the same redirection on every row.
func (*Event) GetTimezone ¶
GetTimezone reports the timezone the expression is evaluated in, or nil for the timezone of the process.
func (*Event) Grant ¶
Grant is the authorization this run carries.
It is auth.SystemGrant of the event's action and tenant, which is the one way a scheduled task reaches a repository.
func (*Event) IsDue ¶
IsDue reports whether the event should run now.
It checks maintenance mode first, then the expression, then the environment.
func (*Event) IsRepeatable ¶
IsRepeatable reports whether the event was asked to repeat within the minute.
func (*Event) LastDayOfMonth ¶
LastDayOfMonth runs the event on the last day of the month, at a time.
The day is resolved when the expression is tested, not when the schedule is declared: it asks for the 28th through the 31st and lets its own When filter drop the days that are not the last one.
func (*Event) MutexName ¶
MutexName is the key the event's overlap lock is stored under.
The name is derived from the expression and the command, so the same event on two replicas resolves to the same key and one of them loses the race.
func (*Event) Name ¶
Name sets the readable description of the event.
It is one call to Description. Both exist because WithoutOverlapping on a closure requires one of them to have been called first.
func (*Event) NextRunDate ¶
NextRunDate is when the event runs next, after the given time.
To find the nth occurrence, call it again with each result: there is no argument for walking further down the list in one call.
func (*Event) OnFailureWithOutput ¶
func (e *Event) OnFailureWithOutput(callback func(ctx context.Context, output string) error, onlyIfOutputExists ...bool) *Event
OnFailureWithOutput is OnFailure with the output handed to the callback.
func (*Event) OnOneServer ¶
OnOneServer allows the event to run on exactly one replica per window.
It is the scheduling mutex rather than the event mutex: the first replica to claim the window runs it, and the others skip it entirely rather than waiting.
func (*Event) OnSuccessWithOutput ¶
func (e *Event) OnSuccessWithOutput(callback func(ctx context.Context, output string) error, onlyIfOutputExists ...bool) *Event
OnSuccessWithOutput is OnSuccess with the output handed to the callback.
func (*Event) PerTenant ¶
PerTenant expands the event to one run per tenant, each with its own Grant and its own lock.
The tenant comes from the Grant and never from a path or a flag, so a task that reads a customer's rows says here that it is per tenant and the runner hands it one Grant at a time.
func (*Event) PingBefore ¶
PingBefore makes a GET to the URL before the event runs.
func (*Event) PingBeforeIf ¶
PingBeforeIf makes the ping before, when the condition holds.
func (*Event) PingOnFailure ¶
PingOnFailure makes a GET to the URL when the event failed.
func (*Event) PingOnFailureIf ¶
PingOnFailureIf makes the ping on failure, when the condition holds.
func (*Event) PingOnSuccess ¶
PingOnSuccess makes a GET to the URL when the event succeeded.
func (*Event) PingOnSuccessIf ¶
PingOnSuccessIf makes the ping on success, when the condition holds.
func (*Event) PreventOverlapsUsing ¶
func (e *Event) PreventOverlapsUsing(mutex EventMutex) *Event
PreventOverlapsUsing points the event at another mutex.
func (*Event) QuarterlyOn ¶
QuarterlyOn runs the event on a day of each quarter, at a time.
func (*Event) RepeatSeconds ¶
RepeatSeconds reports how often the event repeats within a minute, or zero.
func (*Event) Run ¶
Run runs the event.
An overlapping event returns without doing anything, and an event that was not sent to the background is finished -- callbacks and mutex release -- as soon as it returns.
func (*Event) RunInBackground ¶
RunInBackground sends the command to the background.
Run returns as soon as the program has started, so the runner goes on to the next event. The command is waited for beside it, and the exit code, the after callbacks and the mutex release happen when it ends rather than when Run returns -- which is why an event sent to the background wants WithoutOverlapping.
func (*Event) RunsInBackground ¶
RunsInBackground reports whether the event was sent to the background.
func (*Event) RunsInEnvironment ¶
RunsInEnvironment reports whether the event runs in the given environment.
An event that named none runs in all.
func (*Event) RunsInMaintenanceMode ¶
RunsInMaintenanceMode reports whether the event runs while the application is down.
func (*Event) RunsOnOneServer ¶
RunsOnOneServer reports whether the event was limited to one replica.
func (*Event) RunsPerTenant ¶
RunsPerTenant reports whether the event is expanded to one run per tenant.
func (*Event) SendOutputTo ¶
SendOutputTo sends the command's output to a path.
func (*Event) ShouldRepeatNow ¶
ShouldRepeatNow reports whether enough of the minute has passed for the next repeat.
Runner.repeatEvents is what calls it: without a caller, EveryFifteenSeconds would be a schedule that ran once a minute instead of four times.
func (*Event) ShouldSkipDueToOverlapping ¶
ShouldSkipDueToOverlapping reports whether another copy of the event holds the mutex.
func (*Event) StoreOutput ¶
StoreOutput makes sure the output is written to a file rather than discarded.
func (*Event) Tenant ¶
Tenant fixes the tenant this copy of the event runs for.
The runner calls it while expanding a per-tenant event; a schedule that pins one tenant by hand calls it too.
func (*Event) ThenPingIf ¶
ThenPingIf makes the ping after, when the condition holds.
func (*Event) ThenWithOutput ¶
func (e *Event) ThenWithOutput(callback func(ctx context.Context, output string) error, onlyIfOutputExists ...bool) *Event
ThenWithOutput registers a callback that is handed what the event printed.
func (*Event) TwiceDaily ¶
TwiceDaily runs the event at two hours of the day.
The defaults are 1am and 1pm.
func (*Event) TwiceDailyAt ¶
TwiceDailyAt runs the event at two hours of the day, at an offset in the hour.
func (*Event) TwiceMonthly ¶
TwiceMonthly runs the event on two days of the month, at a time.
The defaults are the 1st and the 16th, at midnight.
func (*Event) UnlessBetween ¶
UnlessBetween stops the event between two times of day.
func (*Event) UseProcessFactory ¶ added in v0.18.0
UseProcessFactory points the event at another process factory, and a nil one leaves the event on the factory it has.
It is the seam a test uses: a factory with fakes registered answers the event's command line without a program being started.
func (*Event) User ¶
User sets which user the command runs as.
CommandBuilder is what turns it into a sudo, and it does nothing on Windows.
func (*Event) Wednesdays ¶
Wednesdays runs the event only on Wednesdays.
func (*Event) WeeklyOn ¶
WeeklyOn runs the event on a day of the week, at a time.
The default time is midnight.
func (*Event) When ¶
When registers a callback that has to return true for the event to run.
WhenTrue is the bool form, because a Go method cannot take both a closure and a bool.
func (*Event) WithoutOverlapping ¶
WithoutOverlapping stops the event from running while a copy of it is still running.
The lock is both the mark and the filter: an event whose previous run is still going is not merely refused, it is reported as skipped.
expiresAt is in minutes, and left out it is a day. It is how long the lock lives when the process holding it dies, so it is sized above the longest run rather than tight against the usual one.
type EventMutex ¶
type EventMutex interface {
// Create takes the mutex for the event, and reports whether it got it.
Create(ctx context.Context, event *Event) (bool, error)
// Exists reports whether the mutex is held.
Exists(ctx context.Context, event *Event) (bool, error)
// Forget releases the mutex.
Forget(ctx context.Context, event *Event) error
}
EventMutex is what stops one event from overlapping itself.
Every method takes a context, because a lock is a round trip to a store, and every method can fail, because that round trip can -- and a mutex that reports "not held" when it could not ask is a mutex that lets both copies run.
type Interrupter ¶
type Interrupter interface {
// Interrupt marks the current schedule run as interrupted, for ttl.
Interrupt(ctx context.Context, ttl time.Duration) error
// Interrupted reports whether the mark is there, and clears it.
Interrupted(ctx context.Context) (bool, error)
}
Interrupter is what schedule:interrupt writes to and the worker reads.
It is declared here rather than imported as a cache repository: what the two commands need of a store is a flag that expires, and a flag that expires is a lock with a ttl.
type Listener ¶
type Listener func(event any)
Listener is told about every scheduled event that starts, finishes, fails or is skipped.
It is a function rather than a dispatcher interface because the scheduler fires four events and nothing else, and a listener that wants them all switches on the type.
type Mailer ¶
type Mailer interface {
// Raw sends a plain text message.
Raw(ctx context.Context, addresses []string, subject, body string) error
}
Mailer is what EmailOutputTo sends through.
It is declared here rather than imported: the mail package is not a dependency of the scheduler, and what an event needs of a mailer is one method.
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module runs the schedule in the application process.
It is what `schedule:work` is, wired as a module rather than typed as a command: this binary has a resident process, so nothing external has to call `schedule:run` on a timer. That is one artifact instead of two, and nothing to forget when a machine is replaced.
It registers no routes. A scheduled event is not reachable over HTTP, and making it reachable would be a way to trigger billing by URL.
It is the last piece of the hesape/scheduler package, which this one replaced: Task became Event, Schedule became CronExpression, and the loop is here.
func (*Module) Boot ¶
Boot checks that every declared expression parses.
Waiting for Event.IsDue to parse it would mean a schedule nobody can read is found only at the first tick.
The application does not start with an event that would silently never run, and it fails for every command and not only the one that serves -- so `schedule:list` catches a bad expression too.
func (*Module) Close ¶
Close stops the loop and waits for the run in flight.
Waiting matters: an event killed halfway is an event whose lock is still held and whose work is half done, and the next window will not know either.
func (*Module) Start ¶
Start begins the loop, and only the process that serves calls it.
It ticks at the top of each minute rather than every sixty seconds from boot, because an event specified as "0 3 * * *" has to fire at 3:00 and not at 3:00 plus however long after a deploy the process happened to start.
type PendingEventAttributes ¶
type PendingEventAttributes struct {
*Event
// contains filtered or unexported fields
}
PendingEventAttributes is a set of attributes waiting for an event to be declared.
It is what makes Schedule.Daily().Group(...) work: the frequency is named before there is an event to put it on, so it is held here and merged into every event the group declares.
It embeds an Event rather than repeating the seventy frequency and attribute methods. The event it embeds is never run: it is a bag of attributes with the methods already on it.
func NewPendingEventAttributes ¶
func NewPendingEventAttributes(schedule *Schedule) *PendingEventAttributes
NewPendingEventAttributes returns an empty set of attributes.
func (*PendingEventAttributes) Attributes ¶
func (p *PendingEventAttributes) Attributes() *Event
Attributes returns the event the attributes are collected on, so the frequency methods chain from a group the way they chain from an event.
func (*PendingEventAttributes) MergeAttributes ¶
func (p *PendingEventAttributes) MergeAttributes(event *Event)
MergeAttributes copies what was collected onto an event.
Only what was actually set is copied, so an event that named its own timezone keeps it.
func (*PendingEventAttributes) Schedule ¶
func (p *PendingEventAttributes) Schedule() *Schedule
Schedule returns the schedule the group belongs to.
Go has no fallthrough for a missing method, so a schedule-level call in a chain that started from Attributes reaches it through this instead.
func (*PendingEventAttributes) WithoutOverlapping ¶
func (p *PendingEventAttributes) WithoutOverlapping(expiresAt ...int) *PendingEventAttributes
WithoutOverlapping records that the events of this group must not overlap themselves.
The attributes have no mutex to register a skip against, so it only records the decision and MergeAttributes is what turns it into the skip.
type Pinger ¶
type Pinger interface {
// Ping makes a GET request to the URL.
Ping(ctx context.Context, url string) error
}
Pinger is what the ping callbacks call.
It is declared here for the same reason Mailer is: a GET to a URL is the whole of what an event asks of an HTTP client.
type Runner ¶
type Runner struct {
// Listen receives ScheduledTaskStarting, ScheduledTaskFinished,
// ScheduledTaskFailed and ScheduledTaskSkipped. Nil means nobody is
// listening.
Listen Listener
// Now is the clock, for tests. Nil means time.Now. It is also the clock the
// due events read, so a sub-minute repeat is decided against the same time
// the loop around it is.
Now func() time.Time
// Sleep is how the repeat loop waits between passes, for tests. Nil means
// time.Sleep.
Sleep func(d time.Duration)
// contains filtered or unexported fields
}
Runner runs the events of a schedule that are due.
schedule:run and schedule:work both do exactly this, and a second copy in the worker is the copy that drifts.
func (*Runner) Run ¶
Run fires everything due in the minute of at, and reports how many ran.
The order is the filters first, then the one-server claim, then the run. An event that fails does not stop the ones after it -- a schedule where one broken task silences the rest is a schedule that hides its own failures.
type Schedule ¶
type Schedule struct {
// Tenants expands per-tenant events. Nil means those events do not run,
// which the runner reports rather than swallowing.
Tenants Tenants
// Environment is what an event's Environments list is matched against.
Environment string
// DownForMaintenance reports whether the application is down. Nil means it
// is not.
DownForMaintenance func() bool
// contains filtered or unexported fields
}
Schedule is every event an application runs on a clock.
It is built at wiring time and read afterwards: it is not safe to declare an event on one goroutine while another is running the schedule.
func NewSchedule ¶
func NewSchedule(eventMutex EventMutex, schedulingMutex SchedulingMutex, timezone *time.Location) *Schedule
NewSchedule returns an empty schedule.
The two mutexes are passed explicitly rather than resolved implicitly; nil for either means the events that need it are refused rather than run unprotected.
func (*Schedule) Attributes ¶
func (s *Schedule) Attributes() *PendingEventAttributes
Attributes returns the attributes waiting to be merged into the next event, creating them if there are none.
Go has no fallthrough for a missing method, so the caller asks for the attributes explicitly instead of one being created implicitly. Chaining a frequency onto the result is what Group then reads.
func (*Schedule) Call ¶
func (s *Schedule) Call(callback Callback) *CallbackEvent
Call schedules a closure.
The closure receives the Grant the event was declared with, which is required of any path that can reach a repository.
func (*Schedule) CompileArrayInput ¶
CompileArrayInput renders a repeated parameter as a command line fragment.
A long flag repeats as --key=value, a short one as -k value, and a positional list is just the values.
func (*Schedule) Exec ¶
Exec schedules a command line.
An expression that does not parse is not possible here -- the event starts on every minute -- but a parameter list that would change the meaning of the line is escaped, which is what compileParameters is for.
func (*Schedule) ForgetMutexCache ¶
func (s *Schedule) ForgetMutexCache()
ForgetMutexCache drops what ServerShouldRun remembered.
The cache is scoped to one tick: the runner calls this between them, and without it a replica that lost one window would lose every window afterwards.
func (*Schedule) Group ¶
Group declares several events that share the attributes named before it.
Calling it without having named an attribute first panics: a group with nothing to merge is a group that does nothing, and it is a mistake in the schedule rather than a condition to handle.
func (*Schedule) Job ¶
func (s *Schedule) Job(job any, queue, connection string) *CallbackEvent
Job schedules a queued job.
A job that reaches the queue with no dispatcher wired fails the run rather than being dropped, because a scheduled job that silently never enqueues is the failure nobody notices for a month.
func (*Schedule) ServerShouldRun ¶
ServerShouldRun reports whether this replica is the one that runs the event.
An event asked about twice in one tick gets the same answer, because the second ask would otherwise find the window already claimed by the first and say no.
func (*Schedule) UseCache ¶
UseCache points both mutexes at another lock issuer.
The issuer is passed here, and a mutex that is not CacheAware is left alone.
func (*Schedule) UseDispatcher ¶
func (s *Schedule) UseDispatcher(dispatcher Dispatcher) *Schedule
UseDispatcher gives the schedule the queue Job pushes onto.
type SchedulingMutex ¶
type SchedulingMutex interface {
// Create claims the window, and reports whether it got it.
Create(ctx context.Context, event *Event, at time.Time) (bool, error)
// Exists reports whether the window is claimed.
Exists(ctx context.Context, event *Event, at time.Time) (bool, error)
}
SchedulingMutex is what makes an event run on exactly one replica per window.
It is a different mutex from EventMutex and for a different reason: this one marks a window as claimed, and it is never released -- releasing it would let the replica that ticks two hundred milliseconds later claim the same window and run it a second time, which is the duplicate the mark exists to stop.
type Tenants ¶
Tenants returns the tenants a per-tenant event expands to.
The tenant comes from the Grant, and the core does not know where the application keeps its tenants -- a table, a config file, a control plane -- so the list is injected. Returning an empty list is valid and means those events simply do not run.