From cb8f233b4d8e94bf68808b1be76b6c5569424a7a Mon Sep 17 00:00:00 2001 From: Sherif Abdel-Naby Date: Sat, 10 Apr 2021 12:19:32 +0200 Subject: [PATCH] Add Documentation Comments Signed-off-by: Sherif Abdel-Naby --- .golangci.yml | 3 ++- go.mod | 2 ++ go.sum | 19 +++++++++++++++++++ job/error.go | 2 ++ job/job.go | 15 +++++++++++++++ job/state.go | 5 +++++ log.go | 2 ++ options.go | 9 +++++++++ schedule.go | 23 +++++++++++++++++++++-- scheduler.go | 16 +++++++++++++++- state.go | 10 ++++++++++ timer.go | 26 ++++++++++++++++++++++---- 12 files changed, 124 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 14bb505..d44d32e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -56,7 +56,7 @@ linters-settings: - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf golint: # minimal confidence for issues, default is 0.8 - min-confidence: 0.8 + min-confidence: 0.1 gofmt: # simplify code: gofmt with `-s` option, true by default simplify: true @@ -133,6 +133,7 @@ linters: - varcheck - bodyclose - depguard + - golint - dupl - govet - gochecknoinits diff --git a/go.mod b/go.mod index 65de444..f39d87b 100644 --- a/go.mod +++ b/go.mod @@ -14,4 +14,6 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/uber-go/tally v3.3.17+incompatible go.uber.org/zap v1.16.0 + golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect + golang.org/x/tools v0.1.0 // indirect ) diff --git a/go.sum b/go.sum index 2a64889..ccc60b7 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,7 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/uber-go/tally v3.3.17+incompatible h1:nFHIuW3VQ22wItiE9kPXic8dEgExWOsVOHwpmoIvsMw= github.com/uber-go/tally v3.3.17+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= @@ -50,24 +51,42 @@ go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= diff --git a/job/error.go b/job/error.go index c93959e..ab9576b 100644 --- a/job/error.go +++ b/job/error.go @@ -1,5 +1,6 @@ package job +//ErrorJobPanic Error returned when a Job panics type ErrorJobPanic struct { Message string } @@ -12,6 +13,7 @@ func (e ErrorJobPanic) Unwrap() error { return e } +//ErrorJobStarted Error returned when a has already started. type ErrorJobStarted struct { Message string } diff --git a/job/job.go b/job/job.go index 08fecfc..c4e4fe8 100644 --- a/job/job.go +++ b/job/job.go @@ -8,6 +8,9 @@ import ( "github.com/google/uuid" ) +//Job Wraps JobFun and provide: +// 1. Creation, Start, and Finish Time +// 2. Recover From Panics type Job struct { id string jobFunc func() @@ -18,10 +21,14 @@ type Job struct { mx sync.RWMutex } +//State Return Job current state. func (j *Job) State() State { + j.mx.RLock() + defer j.mx.RUnlock() return j.state } +//NewJobWithID Create new Job with the supplied Id. func NewJobWithID(id string, jobFunc func()) *Job { return &Job{ id: id, @@ -33,14 +40,18 @@ func NewJobWithID(id string, jobFunc func()) *Job { } } +//NewJob Create new Job, ID is assigned a UUID instead. func NewJob(jobFunc func()) *Job { return NewJobWithID(uuid.New().String(), jobFunc) } +//ID Return Job ID func (j *Job) ID() string { return j.id } +//ActualElapsed Return the actual time of procession of Job. +// Return -1 if job hasn't started yet. func (j *Job) ActualElapsed() time.Duration { j.mx.RLock() defer j.mx.RUnlock() @@ -54,6 +65,8 @@ func (j *Job) ActualElapsed() time.Duration { return -1 } +//TotalElapsed Returns the total time between creation of object and finishing processing its job. +// Return -1 if job hasn't started yet. func (j *Job) TotalElapsed() time.Duration { j.mx.RLock() defer j.mx.RUnlock() @@ -66,6 +79,8 @@ func (j *Job) TotalElapsed() time.Duration { } return -1 } + +//Run Run the internal Job (synchronous) func (j *Job) Run() error { return j.run() } diff --git a/job/state.go b/job/state.go index 1263ed9..519fe92 100644 --- a/job/state.go +++ b/job/state.go @@ -1,11 +1,16 @@ package job +//State Indicate the state of the Job type State int64 const ( + // NEW Job has just been created and hasn't started yet NEW State = iota + // RUNNING Job started and is running. RUNNING + // FINISHED Job started and finished processing. FINISHED + // PANICKED Job started and finished but encountered a panic. PANICKED ) diff --git a/log.go b/log.go index 2926da5..07381d7 100644 --- a/log.go +++ b/log.go @@ -30,6 +30,7 @@ func (l logger) Named(name string) Logger { return logger{SugaredLogger: l.SugaredLogger.Named(name)} } +//DefaultLogger Return Default Sched Logger based on Zap's sugared logger func DefaultLogger() Logger { // TODO control verbosity loggerBase, _ := zap.NewDevelopment() @@ -39,6 +40,7 @@ func DefaultLogger() Logger { } } +//NopLogger Return a No Op Logger that prints nothing. func NopLogger() Logger { loggerBase := zap.NewNop() sugarLogger := loggerBase.Sugar() diff --git a/options.go b/options.go index eea6738..56f76a3 100644 --- a/options.go +++ b/options.go @@ -25,6 +25,8 @@ func defaultOptions() *options { } } +// Option to customize schedule behavior, check the sched.With*() functions that implement Option interface for the +// available options type Option interface { apply(*options) } @@ -37,6 +39,7 @@ func (l loggerOption) apply(opts *options) { opts.logger = l.Logger.Named("sched") } +//WithLogger Use the supplied Logger as the logger. func WithLogger(logger Logger) Option { return loggerOption{Logger: logger} } @@ -56,10 +59,16 @@ func (m metricsOption) apply(opts *options) { opts.defaultScopePrintEvery = m.defaultScopePrintEvery } +// WithMetrics Supply a tally.Scope to expose schedule metrics with. Ex. uber-go/tally/prometheus scope to expose +// schedule metrics via Prometheus endpoint. +// Use WithConsoleMetrics() to supply a predefined metrics console reporter without the need to implement any +// special metrics reporter scope. func WithMetrics(metricsScope tally.Scope) Option { return metricsOption{metricsScope: metricsScope, initConsoleMetrics: false, defaultScopePrintEvery: 0} } +// WithConsoleMetrics a predefined console metrics reporter, uses the Logger interface of the schedule to print out +// metrics logs. func WithConsoleMetrics(printEvery time.Duration) Option { return metricsOption{metricsScope: nil, initConsoleMetrics: true, defaultScopePrintEvery: printEvery} } diff --git a/schedule.go b/schedule.go index 8834f84..a127883 100644 --- a/schedule.go +++ b/schedule.go @@ -8,6 +8,8 @@ import ( "github.com/uber-go/tally" ) +// Schedule A Schedule is an object that wraps a Job (func(){}) and runs it on a schedule according to the supplied +// Timer; With the the ability to expose metrics, and write logs to indicate job health, state, and stats. type Schedule struct { ID string @@ -39,7 +41,7 @@ type Schedule struct { metrics metrics } -// NewSchedule NewSchedule +// NewSchedule Create a new schedule for` jobFunc func()` that will run according to `timer Timer` with the supplied []Options func NewSchedule(id string, timer Timer, jobFunc func(), opts ...Option) *Schedule { var options = defaultOptions() @@ -71,6 +73,12 @@ func NewSchedule(id string, timer Timer, jobFunc func(), opts ...Option) *Schedu } } +// Start Start the scheduler. Method is concurrent safe. Calling Start() have the following effects according to the +// scheduler state: +// 1. NEW: Start the Schedule; running the defined Job on the first Timer's Next() time. +// 2. STARTED: No Effect (and prints warning) +// 3. STOPPED: Restart the schedule +// 4. FINISHED: No Effect (and prints warning) func (s *Schedule) Start() { s.mx.Lock() defer s.mx.Unlock() @@ -96,11 +104,19 @@ func (s *Schedule) Start() { go func() {}() } +// Stop stops the scheduler. Method is **Blocking** and concurrent safe. When called: +// 1. Schedule will cancel all waiting scheduled jobs. +// 2. Schedule will wait for all running jobs to finish. +// Calling Stop() has the following effects depending on the state of the schedule: +// 1. NEW: No Effect +// 2. STARTED: Stop Schedule +// 3. STOPPED: No Effect +// 4. FINISHED: No Effect func (s *Schedule) Stop() { s.mx.Lock() defer s.mx.Unlock() - if s.state == STOPPED || s.state == FINISHED { + if s.state == STOPPED || s.state == FINISHED || s.state == NEW { return } s.state = STOPPING @@ -119,6 +135,9 @@ func (s *Schedule) Stop() { _ = s.logger.Sync() } +// Finish stops the scheduler and put it FINISHED state so that schedule cannot re-start again. Finish() is called +// automatically if Schedule Timer returned `done bool` == true. +// Method is **Blocking** and concurrent safe. func (s *Schedule) Finish() { // Stop First s.Stop() diff --git a/scheduler.go b/scheduler.go index b30c22f..4398157 100644 --- a/scheduler.go +++ b/scheduler.go @@ -5,12 +5,15 @@ import ( "sync" ) +// Scheduler manage one or more Schedule creating them using common options, enforcing unique IDs, and supply methods to +// Start / Stop all schedule(s). type Scheduler struct { schedules map[string]*Schedule scheduleOpts []Option mx sync.RWMutex } +//NewScheduler Creates new Scheduler, opt Options are applied to *every* schedule added and created by this scheduler. func NewScheduler(opts ...Option) *Scheduler { return &Scheduler{ schedules: make(map[string]*Schedule), @@ -18,17 +21,25 @@ func NewScheduler(opts ...Option) *Scheduler { } } -func (s *Scheduler) Add(id string, timer Timer, job func()) { +//Add Create a new schedule for` jobFunc func()` that will run according to `timer Timer` with the []Options of the Scheduler. +func (s *Scheduler) Add(id string, timer Timer, job func()) error { s.mx.Lock() defer s.mx.Unlock() + if _, ok := s.schedules[id]; !ok { + return fmt.Errorf("job with this ID already exists") + } + // Create schedule schedule := NewSchedule(id, timer, job, s.scheduleOpts...) // Add to managed schedules s.schedules[id] = schedule + + return nil } +//Start Start the Schedule with the given ID. Return error if no Schedule with the given ID exist. func (s *Scheduler) Start(id string) error { s.mx.Lock() defer s.mx.Unlock() @@ -45,6 +56,7 @@ func (s *Scheduler) Start(id string) error { return nil } +//StartAll Start All Schedules managed by the Scheduler func (s *Scheduler) StartAll() { s.mx.Lock() defer s.mx.Unlock() @@ -53,6 +65,7 @@ func (s *Scheduler) StartAll() { } } +//Stop Stop the Schedule with the given ID. Return error if no Schedule with the given ID exist. func (s *Scheduler) Stop(id string) error { s.mx.Lock() defer s.mx.Unlock() @@ -64,6 +77,7 @@ func (s *Scheduler) Stop(id string) error { return nil } +//StopAll Stops All Schedules managed by the Scheduler concurrently, but will block until ALL of them have stopped. func (s *Scheduler) StopAll() { s.mx.Lock() defer s.mx.Unlock() diff --git a/state.go b/state.go index f2d52af..6198baa 100644 --- a/state.go +++ b/state.go @@ -1,12 +1,22 @@ package sched +//State Indicate the state of the Schedule type State int64 const ( + //NEW Schedule has just been created and hasn't started before NEW State = iota + + // STARTED Start Schedule has started and is running. STARTED + + // STOPPING Schedule is Stopping and is waiting for all active jobs to finish. STOPPING + + // STOPPED Schedule has stopped and no longer scheduling new Jobs. STOPPED + + // FINISHED Schedule has finished, and will not be able to start again. FINISHED ) diff --git a/timer.go b/timer.go index af7384a..f5c1681 100644 --- a/timer.go +++ b/timer.go @@ -7,24 +7,33 @@ import ( "github.com/gorhill/cronexpr" ) +//Timer is an Interface for a Timer object that is used by a Schedule to determine when to run the next run of a job. +// Timer need to implement the Next() method returning the time of the next Job run. Timer indicates that no jobs shall +// be scheduled anymore by returning done == true. The `next time.Time` returned with `done bool` == true IS IGNORED. +// Next() shall not return time in the past. Time in the past is reset to time.Now() at evaluation time in the scheduler. type Timer interface { Next() (next time.Time, done bool) } +//Once A timer that run ONCE after an optional specific delay. type Once struct { delay time.Duration done bool } -func NewOnce(delay time.Duration) (*Once, error) { - if delay < 0 { - return nil, fmt.Errorf("invalid delay, must be >= 0") +//NewOnce Return a timer that trigger ONCE after `d` delay as soon as Timer is inquired for the next Run. +//Delay = 0 means the Timer return now(), aka as soon as time is inquired. +func NewOnce(d time.Duration) (*Once, error) { + if d < 0 { + return nil, fmt.Errorf("invalid d, must be >= 0") } return &Once{ - delay: delay, + delay: d, }, nil } +// NewOnceTime Return a timer that trigger ONCE at `t` time.Time. +//If `t` is in the past at inquery time, timer will NOT run. func NewOnceTime(t time.Time) (*Once, error) { remaining := time.Until(t) if remaining < 0 { @@ -38,6 +47,7 @@ func NewOnceTime(t time.Time) (*Once, error) { }, nil } +//Next Return Next Time OR a boolean indicating no more Next()(s) func (o *Once) Next() (time.Time, bool) { if !o.done { o.done = true @@ -46,11 +56,13 @@ func (o *Once) Next() (time.Time, bool) { return time.Time{}, o.done } +//Fixed A Timer that fires at a fixed duration intervals type Fixed struct { duration time.Duration next time.Time } +//NewFixed Returns Fixed Timer; A Timer that fires at a fixed duration intervals. func NewFixed(duration time.Duration) (*Fixed, error) { if duration < 0 { return nil, fmt.Errorf("invalid duration, must be >= 0") @@ -61,6 +73,7 @@ func NewFixed(duration time.Duration) (*Fixed, error) { }, nil } +//Next Return Next fire time. func (f *Fixed) Next() (time.Time, bool) { now := time.Now() if now.After(f.next) { @@ -69,10 +82,14 @@ func (f *Fixed) Next() (time.Time, bool) { return f.next, false } +//Cron A Timer that fires at according to a cron expression. +//All expresion supported by `https://github.com/gorhill/cronexpr` are supported. type Cron struct { expression cronexpr.Expression } +//NewCron returns a Timer that fires at according to a cron expression. +//All expresion supported by `https://github.com/gorhill/cronexpr` are supported. func NewCron(cronExpression string) (*Cron, error) { expression, err := cronexpr.Parse(cronExpression) if err != nil { @@ -81,6 +98,7 @@ func NewCron(cronExpression string) (*Cron, error) { return &Cron{expression: *expression}, nil } +//Next Return Next fire time. func (c *Cron) Next() (time.Time, bool) { return c.expression.Next(time.Now()), false }