Add Documentation Comments

Signed-off-by: Sherif Abdel-Naby <sherifabdlnaby@gmail.com>
This commit is contained in:
Sherif Abdel-Naby 2021-04-10 12:19:32 +02:00
parent b3d39cffc1
commit cb8f233b4d
12 changed files with 124 additions and 8 deletions

View file

@ -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

2
go.mod
View file

@ -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
)

19
go.sum
View file

@ -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=

View file

@ -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
}

View file

@ -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()
}

View file

@ -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
)

2
log.go
View file

@ -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()

View file

@ -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}
}

View file

@ -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()

View file

@ -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()

View file

@ -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
)

View file

@ -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
}