github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/aagent/watchers/schedulewatcher/item.go (about) 1 // Copyright (c) 2019-2021, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package schedulewatcher 6 7 import ( 8 "context" 9 "math/rand" 10 "sync" 11 "time" 12 13 "github.com/choria-io/go-choria/aagent/model" 14 "github.com/choria-io/go-choria/internal/util" 15 "github.com/robfig/cron" 16 ) 17 18 type scheduleItem struct { 19 spec string 20 sched cron.Schedule 21 events chan int 22 on bool 23 duration time.Duration 24 randomize time.Duration 25 machine model.Machine 26 watcher *Watcher 27 28 sync.Mutex 29 } 30 31 func newSchedItem(s string, w *Watcher) (item *scheduleItem, err error) { 32 item = &scheduleItem{ 33 spec: s, 34 events: w.ctrq, 35 machine: w.machine, 36 watcher: w, 37 duration: w.properties.Duration, 38 randomize: w.properties.StartSplay, 39 } 40 41 parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) 42 item.sched, err = parser.Parse(s) 43 if err != nil { 44 return nil, err 45 } 46 47 return item, nil 48 } 49 50 func (s *scheduleItem) check(ctx context.Context) { 51 now := time.Now() 52 next := s.sched.Next(now) 53 54 // using unix time to round it to nearest second 55 if next.Unix()-1 == now.Unix() { 56 s.Lock() 57 defer s.Unlock() 58 59 sleep := time.Duration(0) 60 if s.randomize > 0 { 61 sleep = time.Duration(rand.Int63n(int64(s.randomize))) 62 s.watcher.Infof("Splay sleeping %v before starting schedule", sleep) 63 err := util.InterruptibleSleep(ctx, sleep) 64 if err != nil { 65 return 66 } 67 } 68 69 s.watcher.Infof("Schedule %s starting", s.spec) 70 s.on = true 71 s.events <- 1 72 73 go s.wait(ctx, s.duration-sleep) 74 } 75 } 76 77 func (s *scheduleItem) wait(ctx context.Context, t time.Duration) { 78 s.watcher.Infof("Scheduling on until %v", time.Now().Add(t)) 79 timer := time.NewTimer(t) 80 defer timer.Stop() 81 82 select { 83 case <-timer.C: 84 case <-ctx.Done(): 85 return 86 } 87 88 s.Lock() 89 s.watcher.Infof("Schedule %s ending", s.spec) 90 s.on = false 91 s.events <- -1 92 s.Unlock() 93 } 94 95 func (s *scheduleItem) start(ctx context.Context, wg *sync.WaitGroup) { 96 defer wg.Done() 97 98 ticker := time.NewTicker(time.Second) 99 100 for { 101 select { 102 case <-ticker.C: 103 s.check(ctx) 104 105 case <-ctx.Done(): 106 ticker.Stop() 107 return 108 } 109 } 110 }