diff --git a/README.md b/README.md index 69ba9c9..380d929 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# schedule +# recurrent [![main](https://github.com/flowck/schedule/actions/workflows/main.yml/badge.svg)](https://github.com/flowck/schedule/actions/workflows/main.yml) -Golang job scheduling for humans. Run Go functions periodically using a friendly syntax. - Inspired by Python lib [schedule](https://github.com/dbader/schedule) +A Go package to run tasks recurrently. - Inspired by the Python lib [schedule](https://github.com/dbader/schedule) ## Usage @@ -14,20 +14,20 @@ import ( "fmt" "time" - "github.com/flowck/schedule" + "github.com/flowck/recurrent/recurrent" ) func main() { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - s := schedule.New() + s := recurrent.New() - s.Every(10).Seconds().Do(func(ctx context.Context) { + s.Every(time.Second * 1).Do(func(ctx context.Context) { fmt.Println("--->", time.Now()) }) - s.Every(30).Seconds().Do(func(ctx context.Context) { + s.Every(time.Second * 2).Do(func(ctx context.Context) { fmt.Println("--->", time.Now()) }) diff --git a/examples/main.go b/examples/main.go index 480d303..cccc970 100644 --- a/examples/main.go +++ b/examples/main.go @@ -5,20 +5,20 @@ import ( "fmt" "time" - "github.com/flowck/schedule/schedule" + "github.com/flowck/recurrent/recurrent" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - s := schedule.New() + s := recurrent.New() - s.Every(1).Seconds().Do(func(ctx context.Context) { + s.Every(time.Second * 1).Do(func(ctx context.Context) { fmt.Println("--->", time.Now()) }) - s.Every(2).Seconds().Do(func(ctx context.Context) { + s.Every(time.Second * 2).Do(func(ctx context.Context) { fmt.Println("--->", time.Now()) }) diff --git a/go.mod b/go.mod index d8b4130..d3b9b67 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/flowck/schedule +module github.com/flowck/recurrent go 1.21 diff --git a/schedule/job.go b/recurrent/job.go similarity index 56% rename from schedule/job.go rename to recurrent/job.go index cb0e922..4e8c350 100644 --- a/schedule/job.go +++ b/recurrent/job.go @@ -1,4 +1,4 @@ -package schedule +package recurrent import ( "context" @@ -9,9 +9,10 @@ import ( var ErrJobStopped = errors.New("the job has been stopped") +type JobHandler func(ctx context.Context) + type Job struct { - interval int - ticker *time.Ticker + ticker *time.Ticker // isRunning is read and written in different places simultaneously thus causing data race issues // hence the atomic type @@ -24,27 +25,16 @@ type Job struct { id string - // having a reference to the scheduler will allow us to cal + // having a reference to the manager will allow us to cal // job.Do(func(ctx context.Content) {}) and finally have the job - // appended to the scheduler - scheduler *Schedule - handler JobHandler -} - -type JobHandler func(ctx context.Context) - -func (j *Job) Second() *Job { - return j -} - -func (j *Job) Seconds() *Job { - j.ticker = time.NewTicker(time.Duration(j.interval) * time.Second) - return j + // appended to the manager + manager *Manager + handler JobHandler } func (j *Job) Do(handler JobHandler) *Job { j.handler = handler - j.scheduler.appendJob(j) + j.manager.appendJob(j) return j } @@ -75,7 +65,7 @@ func (j *Job) runHandler(ctx context.Context) { } func (j *Job) stop() { - j.scheduler.logger.Infof("Stopping the job %s", j.id) + j.manager.logger.Infof("Stopping the job %s", j.id) // send a signal to the job handler to inform it about the stoppage of the job if j.cancelCtx != nil { @@ -89,26 +79,7 @@ func (j *Job) stop() { for j.isRunning.Load() { } - j.scheduler.logger.Infof("Job %s has been stopped", j.id) + j.manager.logger.Infof("Job %s has been stopped", j.id) } -func (j *Job) Minute() {} -func (j *Job) Minutes() {} -func (j *Job) Hour() {} -func (j *Job) Hours() {} -func (j *Job) Day() {} -func (j *Job) Days() {} -func (j *Job) Week() {} -func (j *Job) Weeks() {} -func (j *Job) Monday() {} -func (j *Job) Tuesday() {} -func (j *Job) Wednesday() {} -func (j *Job) Thursday() {} -func (j *Job) Friday() {} -func (j *Job) Saturday() {} -func (j *Job) Sunday() {} -func (j *Job) Tag() {} -func (j *Job) At() {} -func (j *Job) To() {} -func (j *Job) Until() {} -func (j *Job) ShouldRun() {} +func (j *Job) Until() {} diff --git a/schedule/logger.go b/recurrent/logger.go similarity index 97% rename from schedule/logger.go rename to recurrent/logger.go index da36bfd..27e5ff2 100644 --- a/schedule/logger.go +++ b/recurrent/logger.go @@ -1,4 +1,4 @@ -package schedule +package recurrent import ( "fmt" diff --git a/schedule/schedule.go b/recurrent/manager.go similarity index 69% rename from schedule/schedule.go rename to recurrent/manager.go index 703a7bf..ad7412d 100644 --- a/schedule/schedule.go +++ b/recurrent/manager.go @@ -1,4 +1,4 @@ -package schedule +package recurrent import ( "context" @@ -8,21 +8,21 @@ import ( "github.com/oklog/ulid/v2" ) -type Schedule struct { +type Manager struct { logger Logger lock *sync.RWMutex jobs map[string]*Job } -func New() *Schedule { - return &Schedule{ +func New() *Manager { + return &Manager{ lock: &sync.RWMutex{}, logger: NewDefaultLogger(), jobs: make(map[string]*Job), } } -func (s *Schedule) Run(ctx context.Context) { +func (s *Manager) Run(ctx context.Context) { s.lock.Lock() for _, job := range s.jobs { go func(ctx context.Context, job *Job) { @@ -36,25 +36,25 @@ func (s *Schedule) Run(ctx context.Context) { s.cancelAllJobs() } -func (s *Schedule) Every(interval int) *Job { +func (s *Manager) Every(interval time.Duration) *Job { return &Job{ - scheduler: s, - interval: interval, - id: ulid.Make().String(), - done: make(chan interface{}), + manager: s, + id: ulid.Make().String(), + done: make(chan interface{}), + ticker: time.NewTicker(interval), } } -func (s *Schedule) appendJob(job *Job) { +func (s *Manager) appendJob(job *Job) { s.lock.Lock() defer s.lock.Unlock() s.jobs[job.id] = job - s.logger.Infof("Job with id %s has been appended to the scheduler", job.id) + s.logger.Infof("Job with id %s has been appended to the manager", job.id) } // RunAll Runs all jobs regardless if they are scheduled to run or not. -func (s *Schedule) RunAll(ctx context.Context, delay time.Duration) { +func (s *Manager) RunAll(ctx context.Context, delay time.Duration) { s.lock.RLock() defer s.lock.RUnlock() @@ -67,7 +67,7 @@ func (s *Schedule) RunAll(ctx context.Context, delay time.Duration) { } // GetJobs Returns all jobs -func (s *Schedule) GetJobs() []*Job { +func (s *Manager) GetJobs() []*Job { s.lock.RLock() defer s.lock.RUnlock() @@ -82,7 +82,7 @@ func (s *Schedule) GetJobs() []*Job { } // Clear Stops all jobs and then delete them from the schedule -func (s *Schedule) Clear() { +func (s *Manager) Clear() { s.lock.Lock() defer s.lock.Unlock() @@ -93,7 +93,7 @@ func (s *Schedule) Clear() { } // CancelJob Stops a job a removes it from the schedule -func (s *Schedule) CancelJob(job *Job) { +func (s *Manager) CancelJob(job *Job) { job.stop() s.lock.Lock() @@ -102,7 +102,7 @@ func (s *Schedule) CancelJob(job *Job) { delete(s.jobs, job.id) } -func (s *Schedule) cancelAllJobs() { +func (s *Manager) cancelAllJobs() { s.logger.Info("Cancelling all jobs") s.lock.RLock() diff --git a/schedule/schedule_test.go b/recurrent/manager_test.go similarity index 67% rename from schedule/schedule_test.go rename to recurrent/manager_test.go index 0ec7cc3..fb74582 100644 --- a/schedule/schedule_test.go +++ b/recurrent/manager_test.go @@ -1,4 +1,4 @@ -package schedule_test +package recurrent_test import ( "context" @@ -8,22 +8,22 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/flowck/schedule/schedule" + "github.com/flowck/recurrent/recurrent" ) -func TestSchedule(t *testing.T) { +func TestManager(t *testing.T) { testCases := []struct { name string expectedNumberOfCallsToHandler int timeout time.Duration - job func(s *schedule.Schedule) *schedule.Job + job func(s *recurrent.Manager) *recurrent.Job }{ { - name: "schedule_every_1_seconds_for_5_seconds", + name: "every_1_seconds_for_5_seconds", expectedNumberOfCallsToHandler: 5, timeout: time.Second * 5, - job: func(s *schedule.Schedule) *schedule.Job { - return s.Every(1).Seconds() + job: func(s *recurrent.Manager) *recurrent.Job { + return s.Every(time.Second * 1) }, }, } @@ -35,7 +35,7 @@ func TestSchedule(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), tc.timeout) defer cancel() - s := schedule.New() + s := recurrent.New() counter := 0 tc.job(s).Do(func(ctx context.Context) { @@ -50,12 +50,12 @@ func TestSchedule(t *testing.T) { } } -func TestSchedule_Clear(t *testing.T) { - s := schedule.New() +func TestManager_Clear(t *testing.T) { + s := recurrent.New() // Given x amount of handlers for i := 0; i < 5; i++ { - s.Every(1).Seconds().Do(func(ctx context.Context) { + s.Every(time.Second * 1).Do(func(ctx context.Context) { t.Log("Handler ran successfully") }) } @@ -68,11 +68,11 @@ func TestSchedule_Clear(t *testing.T) { assert.Empty(t, s.GetJobs()) } -func TestSchedule_CancelJob(t *testing.T) { - s := schedule.New() +func TestManager_CancelJob(t *testing.T) { + s := recurrent.New() // Given - job := s.Every(1).Seconds().Do(func(ctx context.Context) { + job := s.Every(time.Second * 1).Do(func(ctx context.Context) { t.Log("Handler ran successfully") }) require.Contains(t, s.GetJobs(), job) @@ -84,8 +84,8 @@ func TestSchedule_CancelJob(t *testing.T) { assert.NotContains(t, s.GetJobs(), job) } -func TestSchedule_RunAll(t *testing.T) { - s := schedule.New() +func TestManager_RunAll(t *testing.T) { + s := recurrent.New() result := make([]int, 3) handler := func(idx int) func(ctx context.Context) { @@ -96,9 +96,9 @@ func TestSchedule_RunAll(t *testing.T) { } // Given - s.Every(1).Seconds().Do(handler(0)) - s.Every(1).Seconds().Do(handler(1)) - s.Every(1).Seconds().Do(handler(2)) + s.Every(time.Second * 1).Do(handler(0)) + s.Every(time.Second * 1).Do(handler(1)) + s.Every(time.Second * 1).Do(handler(2)) // Do s.RunAll(context.Background(), time.Millisecond*10)