Task Scheduling #
While there are several libraries out there that provide this, creating a simple task scheduler is a good exercise that anyone can use to up their skills with Go’s concurrency primitives and patterns.
Sample Implementation #
Here is a simple implementation of one such task scheduler. It is relatively straightforward and easy to copy over to any new project, without requiring the need for a separate dependency. Please, not that this implementation has not been tested for any possible edge case scenarios and has been provided for education and illustration purposes only. I would be happy to add your feedback, or improvements to it.
// scheduleTask executes the given task at regular intervals (specified by interval) until the given timeout is reached.
// If a task takes longer than one interval, the next one starts only after the previous task has finished.
func scheduleTask(task func() error, interval time.Duration, timeout time.Duration) {
ctx, cancelFn := context.WithCancel(context.Background())
defer cancelFn()
go func() {
loop(ctx, task, interval)
}()
time.Sleep(timeout)
cancelFn()
}
func loop(ctx context.Context, task func() error, interval time.Duration) {
newJob := make(chan struct{}, 1)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
newJob <- struct{}{}
go func() {
defer func() {
<-newJob
if r := recover(); r != nil {
log.Println("Recovered from a panicking job:", r)
}
}()
log.Println("New job starting")
task()
log.Println("New job ending")
}()
select {
case <-ticker.C:
case <-ctx.Done():
close(newJob)
return
}
}
}
Using the function above becomes as simple as calling the function with the right interval and timeout, and passing an appropriate action to be executed:
func main() {
var a = 0
scheduleTask(func() error {
time.Sleep(10 * time.Millisecond)
// Simulate a random panic
if a == 3 {
a++
panic("oops")
}
a++
return nil
}, time.Millisecond*100, time.Millisecond*1000)
log.Println(a)
}