3
0
Files
corteza/pkg/scheduler/service.go
2020-01-18 07:05:34 +01:00

179 lines
3.2 KiB
Go

package scheduler
import (
"context"
"sync"
"time"
"go.uber.org/zap"
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/cortezaproject/corteza-server/pkg/sentry"
)
type (
service struct {
log *zap.Logger
events []eventbus.Event
interval time.Duration
dispatcher dispatcher
// Read & write locking
l *sync.RWMutex
// Simple chan to control if service is running or not
ticker *time.Ticker
}
dispatcher interface {
Dispatch(ctx context.Context, ev eventbus.Event)
}
)
const (
defaultInterval = time.Minute
// There should not be more than 2 per each service (<no of services> * 2 [interval + timestamp])
maxEvents = 16
)
var (
now = func() time.Time { return time.Now() }
// Global scheduler
gScheduler *service
)
// Setup configures global scheduling service
func Setup(log *zap.Logger, d dispatcher, interval time.Duration) {
if gScheduler != nil {
// shut down current global scheduler
gScheduler.Stop()
}
gScheduler = NewService(log, d, interval)
}
func Service() *service {
return gScheduler
}
func NewService(log *zap.Logger, d dispatcher, interval time.Duration) *service {
// Fix interval to positive number
if interval == 0 {
interval = defaultInterval
}
var svc = &service{
l: &sync.RWMutex{},
log: log.Named("scheduler"),
interval: interval,
dispatcher: d,
events: make([]eventbus.Event, 0, maxEvents),
}
return svc
}
// Register all events that should fire on tick (interval)
func (svc *service) OnTick(events ...eventbus.Event) {
svc.l.Lock()
defer svc.l.Unlock()
svc.events = append(svc.events, events...)
}
func (svc *service) Stop() {
if svc.ticker == nil {
svc.log.Debug("already stopped")
} else {
svc.log.Debug("stopping")
svc.ticker.Stop()
svc.l.Lock()
svc.ticker = nil
defer svc.l.Unlock()
}
}
// Run starts event scheduler service
func (svc *service) Start(ctx context.Context) {
if svc.ticker != nil {
svc.log.Debug("already started")
return
}
svc.l.Lock()
svc.ticker = &time.Ticker{}
svc.l.Unlock()
go func() {
defer sentry.Recover()
// Calculate how much time we need to wait until next tick
delay := now().Truncate(svc.interval).Add(svc.interval).Sub(now())
svc.log.Debug(
"starting",
zap.Duration("delay", delay),
zap.Duration("interval", svc.interval),
)
// Wait until start of the next interval
time.Sleep(delay)
svc.l.Lock()
svc.ticker = time.NewTicker(svc.interval)
svc.l.Unlock()
svc.log.Debug("started")
go svc.watch(ctx)
}()
}
func (svc service) watch(ctx context.Context) {
defer sentry.Recover()
defer func() {
defer svc.log.Debug("stopped")
svc.ticker.Stop()
svc.l.Lock()
svc.ticker = nil
svc.l.Unlock()
}()
// start with first interval
svc.dispatch(ctx)
for {
select {
case <-svc.ticker.C:
svc.dispatch(ctx)
case <-ctx.Done():
svc.log.Debug("done")
return
}
}
}
func (svc service) Started() (started bool) {
svc.l.RLock()
defer svc.l.RUnlock()
return svc.ticker != nil
}
func (svc service) dispatch(ctx context.Context) {
svc.l.RLock()
ee := make([]eventbus.Event, len(svc.events))
for e := range svc.events {
ee[e] = svc.events[e]
}
defer svc.l.RUnlock()
for _, ev := range ee {
go svc.dispatcher.Dispatch(ctx, ev)
}
}