func (s *Service) ScheduleJob(ctx context.Context, name string, runtime time.Time, jobFunc scheduler.JobFunc, data interface{}) error {
	if name == "" {
		return scheduler.ErrNoJobName
	}
	if jobFunc == nil {
		return scheduler.ErrNoJobFunc
	}
	s.jobsMutex.Lock()
	_, exists := s.jobs[name]
	if exists {
		s.jobsMutex.Unlock()
		return scheduler.ErrJobAlreadyExists
	}
	job := &job{
		cancelCh: make(chan struct{}),
		runCh:    make(chan struct{}),
	}
	s.jobs[name] = job
	s.jobsMutex.Unlock()
	s.monitor.JobScheduled()
	log.Trace().Str("job", name).Time("scheduled", runtime).Msg("Scheduled job")
	go func() {
		select {
		case <-ctx.Done():
			log.Trace().Str("job", name).Time("scheduled", runtime).Msg("Parent context done; job not running")
			s.jobsMutex.Lock()
			delete(s.jobs, name)
			s.jobsMutex.Unlock()
			finaliseJob(job)
			s.monitor.JobCancelled()
		case <-job.cancelCh:
			log.Trace().Str("job", name).Time("scheduled", runtime).Msg("Cancel triggered; job not running")
			s.jobsMutex.Lock()
			delete(s.jobs, name)
			s.jobsMutex.Unlock()
			finaliseJob(job)
			s.monitor.JobCancelled()
		case <-job.runCh:
			log.Trace().Str("job", name).Time("scheduled", runtime).Msg("Run triggered; job running")
			s.monitor.JobStartedOnSignal()
			jobFunc(ctx, data)
			log.Trace().Str("job", name).Time("scheduled", runtime).Msg("Job complete")
			finaliseJob(job)
			job.active.Store(false)
		case <-time.After(time.Until(runtime)):
			if job.active.Load() {
				log.Trace().Str("job", name).Time("scheduled", runtime).Msg("Already running; job not running")
				break
			}
			s.jobsMutex.Lock()
			delete(s.jobs, name)
			s.jobsMutex.Unlock()
			log.Trace().Str("job", name).Time("scheduled", runtime).Msg("Timer triggered; job running")
			job.active.Store(true)
			s.monitor.JobStartedOnTimer()
			jobFunc(ctx, data)
			log.Trace().Str("job", name).Time("scheduled", runtime).Msg("Job complete")
			job.active.Store(false)
			finaliseJob(job)
		}
	}()
	return nil
}
ScheduleJob schedules a one-off job for a given time.
Note that if the parent context is cancelled the job wil not run.
package advanced
import (
	"context"
	"strings"
	"time"
	"github.com/attestantio/vouch/services/metrics"
	"github.com/attestantio/vouch/services/scheduler"
	"github.com/pkg/errors"
	"github.com/rs/zerolog"
	zerologger "github.com/rs/zerolog/log"
	"github.com/sasha-s/go-deadlock"
	"go.uber.org/atomic"
)
var log zerolog.Logger
type job struct {
	stateLock deadlock.Mutex
	active    atomic.Bool
	finalised atomic.Bool
	periodic  bool
	cancelCh  chan struct{}
	runCh     chan struct{}
}
type Service struct {
	monitor   metrics.SchedulerMonitor
	jobs      map[string]*job
	jobsMutex deadlock.RWMutex
}
func New(ctx context.Context, params ...Parameter) (*Service, error) {
	parameters, err := parseAndCheckParameters(params...)
	if err != nil {
		return nil, errors.Wrap(err, "problem with parameters")
	}
	log = zerologger.With().Str("service", "scheduler").Str("impl", "advanced").Logger()
	if parameters.logLevel != log.GetLevel() {
		log = log.Level(parameters.logLevel)
	}
	return &Service{
		jobs:    make(map[string]*job),
		monitor: parameters.monitor,
	}, nil
}