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  }