From edb373fe01d471c6d8b4ace961f73159e6b1639c Mon Sep 17 00:00:00 2001 From: Ted Brownlow Date: Tue, 20 Dec 2022 17:02:10 -0500 Subject: [PATCH] Docment structs and interfaces --- executor.go | 14 ++++++++++++++ scheduler.go | 3 +++ task.go | 16 ++++++++++++++++ trigger.go | 6 ++++++ 4 files changed, 39 insertions(+) diff --git a/executor.go b/executor.go index 2717ada..8660263 100644 --- a/executor.go +++ b/executor.go @@ -15,6 +15,9 @@ type TaskExecutor interface { Shutdown() chan bool } +// An executor schedules and executes tasks. +// It also reschedules fixed-rate and fixed-delay periodic tasks, +// though trigger tasks will reschedule themselves. type SimpleTaskExecutor struct { nextSequence int isShutdown bool @@ -206,6 +209,7 @@ func (executor *SimpleTaskExecutor) run() { executor.taskQueue = append(executor.taskQueue, rescheduledTask) case stoppedChan := <-executor.shutdownChannel: executor.timer.Stop() + // Wait for all current tasks to finish executing. executor.taskWaitGroup.Wait() stoppedChan <- true return @@ -231,9 +235,14 @@ func (executor *SimpleTaskExecutor) startTask(scheduledRunnableTask *ScheduledRu executor.taskWaitGroup.Done() + // If this task is non-periodic, it should be cancelled after execution. if !scheduledRunnableTask.isPeriodic() { scheduledRunnableTask.Cancel() } else { + // If the task is periodic but not at a fixed rate, + // then it must be a fixed delay task. This means + // that the period is measured from the end of the previous + // execution. if !scheduledRunnableTask.isFixedRate() { scheduledRunnableTask.triggerTime = executor.calculateTriggerTime(scheduledRunnableTask.period) executor.rescheduleTaskChannel <- scheduledRunnableTask @@ -241,6 +250,11 @@ func (executor *SimpleTaskExecutor) startTask(scheduledRunnableTask *ScheduledRu } }() + // If the task is a fixed-rate periodic task, + // then we can schedule the next task + // before we begin executing the current task. + // We assume that the triggerTime was incremented + // before startTask() was called. if scheduledRunnableTask.isPeriodic() && scheduledRunnableTask.isFixedRate() { executor.rescheduleTaskChannel <- scheduledRunnableTask } diff --git a/scheduler.go b/scheduler.go index 8f49068..71a89df 100644 --- a/scheduler.go +++ b/scheduler.go @@ -4,6 +4,9 @@ import ( "time" ) +// A TaskScheduler is the simplified top-level object +// that most user code will interface with. +// All state is handled internally by the executor. type TaskScheduler interface { Schedule(task Task, options ...Option) (ScheduledTask, error) ScheduleWithCron(task Task, expression string, options ...Option) (ScheduledTask, error) diff --git a/task.go b/task.go index 06289eb..6531afb 100644 --- a/task.go +++ b/task.go @@ -9,8 +9,12 @@ import ( "time" ) +// A Task represents a function which will be executed by the executor. type Task func(ctx context.Context) +// A SchedulerTask represents a task that is about to be scheduled. +// This struct supports setting values via option functions +// such as WithTime() and WithLocation(). type SchedulerTask struct { task Task startTime time.Time @@ -90,11 +94,18 @@ func WithLocation(location string) Option { } } +// A ScheduledTask is a minimal interface that represents +// one-off, fixed period, and dynamic period tasks +// that have been scheduled. type ScheduledTask interface { Cancel() IsCancelled() bool } +// A ScheduledRunnableTask either represents a fixed-rate task +// or a fixed-delay task. In either case, finding the time of +// next execution is a fairly simple operation. +// For more advanced cases, see TriggerTask. type ScheduledRunnableTask struct { id int task Task @@ -165,6 +176,8 @@ func (queue ScheduledTaskQueue) SorByTriggerTime() { sort.Sort(queue) } +// A TriggerTask represents a task which re-schedules itself. +// Useful for dynamic periods, such as those represented via CRON expressions. type TriggerTask struct { task Task currentScheduledTask *ScheduledRunnableTask @@ -208,6 +221,8 @@ func (task *TriggerTask) IsCancelled() bool { return task.currentScheduledTask.IsCancelled() } +// Schedule the task onto an executor. Note that when the task is run, +// it schedules itself again. func (task *TriggerTask) Schedule() (ScheduledTask, error) { task.triggerContextMu.Lock() defer task.triggerContextMu.Unlock() @@ -230,6 +245,7 @@ func (task *TriggerTask) Schedule() (ScheduledTask, error) { return task, nil } +// Run the trigger task and schedule it again. func (task *TriggerTask) Run(ctx context.Context) { task.triggerContextMu.Lock() diff --git a/trigger.go b/trigger.go index 5cfd742..8072b9f 100644 --- a/trigger.go +++ b/trigger.go @@ -2,6 +2,10 @@ package chrono import "time" +// A trigger context is a store of previous times +// that can be used to calculate the next trigger time. +// For example, if our schedule runs every 2 days, +// it is important to know which day our schedule started. type TriggerContext interface { LastCompletionTime() time.Time LastExecutionTime() time.Time @@ -36,6 +40,8 @@ func (ctx *SimpleTriggerContext) LastTriggeredExecutionTime() time.Time { return ctx.lastTriggeredExecutionTime } +// A trigger is a strategy which can be used to generate the next +// execution time, when supplied with context of previous times. type Trigger interface { NextExecutionTime(ctx TriggerContext) time.Time }