├── go.mod ├── .gitignore ├── clockwerk_test.go ├── doc.go ├── .github └── workflows │ └── build.yml ├── LICENSE ├── README.md └── clockwerk.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/onatm/clockwerk 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /clockwerk_test.go: -------------------------------------------------------------------------------- 1 | package clockwerk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type DummyJob1 struct{} 10 | 11 | func (d DummyJob1) Run() { 12 | fmt.Println("HEY") 13 | } 14 | 15 | type DummyJob2 struct{} 16 | 17 | func (d DummyJob2) Run() { 18 | time.Sleep(4 * time.Second) 19 | fmt.Println("HOO") 20 | } 21 | 22 | func TestDo(*testing.T) { 23 | var job1 DummyJob1 24 | var job2 DummyJob2 25 | 26 | c := New() 27 | c.Every(1 * time.Second).Do(job1) 28 | c.Every(1 * time.Second).Do(job2) 29 | defer c.Stop() 30 | c.Start() 31 | 32 | time.Sleep(5 * time.Second) 33 | } 34 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package clockwerk implements an in-process scheduler for periodic jobs. 3 | 4 | # Usage 5 | 6 | Callers may register Jobs to be invoked on a given schedule. Clockwerk will run 7 | them in their own goroutines. 8 | 9 | type DummyJob struct{} 10 | 11 | func (d DummyJob) Run() { 12 | fmt.Println("Every 30 seconds") 13 | } 14 | ... 15 | var job DummyJob 16 | c := clockwerk.New() 17 | c.Every(30 * time.Second).Do(job) 18 | c.Start() 19 | ... 20 | // Funcs are invoked in their own goroutine, asynchronously. 21 | ... 22 | c.Stop() // Stop the scheduler (does not stop any jobs already running). 23 | */ 24 | package clockwerk 25 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Setup Go 1.21.x 16 | uses: actions/setup-go@v5 17 | with: 18 | go-version: 1.21.x 19 | - name: Install golint 20 | run: go install golang.org/x/lint/golint@latest 21 | - name: Install goveralls 22 | run: go install github.com/mattn/goveralls@b031368 23 | - name: Build 24 | run: go build 25 | - name: Run go vet 26 | run: go vet 27 | - name: Run golint 28 | run: golint 29 | - name: Test 30 | run: go test -v -covermode=count -coverprofile=coverage.out 31 | - name: Send coverage 32 | env: 33 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | run: goveralls -coverprofile=coverage.out -service=github 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Onat Yiğit Mercan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clockwerk 2 | 3 | [![Coverage Status](https://coveralls.io/repos/github/onatm/clockwerk/badge.svg?branch=main)](https://coveralls.io/github/onatm/clockwerk?branch=main) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/onatm/clockwerk)](https://goreportcard.com/report/github.com/onatm/clockwerk) 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/onatm/clockwerk.svg)](https://pkg.go.dev/github.com/onatm/clockwerk) 6 | 7 | Job Scheduling Library 8 | 9 | clockwerk allows you to schedule periodic jobs using a simple, fluent syntax. 10 | 11 | ## Installing 12 | 13 | Using clockwerk is easy. First, use `go get` to install the latest version of the library. 14 | 15 | ``` sh 16 | go get -u github.com/onatm/clockwerk@latest 17 | ``` 18 | 19 | ## Usage 20 | 21 | Include clockwerk in your application: 22 | 23 | ```go 24 | import "github.com/onatm/clockwerk" 25 | ``` 26 | 27 | ## Example 28 | 29 | ``` go 30 | package main 31 | 32 | import ( 33 | "fmt" 34 | "time" 35 | "github.com/onatm/clockwerk" 36 | ) 37 | 38 | type DummyJob struct{} 39 | 40 | func (d DummyJob) Run() { 41 | fmt.Println("Every 30 seconds") 42 | } 43 | 44 | func main() { 45 | var job DummyJob 46 | c := clockwerk.New() 47 | c.Every(30 * time.Second).Do(job) 48 | c.Start() 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /clockwerk.go: -------------------------------------------------------------------------------- 1 | package clockwerk 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Job is an interface for added jobs. 8 | type Job interface { 9 | Run() 10 | } 11 | 12 | // Entry consists of a schedule and the job to execute on that schedule. 13 | type Entry struct { 14 | Period time.Duration 15 | Next time.Time 16 | Job Job 17 | } 18 | 19 | func newEntry(period time.Duration) *Entry { 20 | return &Entry{ 21 | Period: period, 22 | } 23 | } 24 | 25 | // Do adds a Job to the Entry. 26 | func (e *Entry) Do(job Job) { 27 | e.Job = job 28 | } 29 | 30 | // Clockwerk keeps track of any number of entries, invoking associated Job's Run 31 | // method as specified by the schedule. 32 | type Clockwerk struct { 33 | entries []*Entry 34 | stop chan struct{} 35 | running bool 36 | } 37 | 38 | // New returns a new Clockwerk job runner. 39 | func New() *Clockwerk { 40 | return &Clockwerk{ 41 | entries: nil, 42 | stop: make(chan struct{}), 43 | running: false, 44 | } 45 | } 46 | 47 | // Every schedules a new Entry and returns it. 48 | func (c *Clockwerk) Every(period time.Duration) *Entry { 49 | entry := newEntry(period) 50 | 51 | c.schedule(entry) 52 | 53 | if !c.running { 54 | c.entries = append(c.entries, entry) 55 | } 56 | 57 | return entry 58 | } 59 | 60 | // Start the Clockwerk in its own go-routine, or no-op if already started. 61 | func (c *Clockwerk) Start() { 62 | if c.running { 63 | return 64 | } 65 | c.running = true 66 | go c.run() 67 | } 68 | 69 | // Stop the Clockwerk if it is running. 70 | func (c *Clockwerk) Stop() { 71 | if !c.running { 72 | return 73 | } 74 | c.stop <- struct{}{} 75 | c.running = false 76 | } 77 | 78 | func (c *Clockwerk) schedule(e *Entry) { 79 | e.Next = time.Now().Add(e.Period) 80 | } 81 | 82 | func (c *Clockwerk) run() { 83 | ticker := time.NewTicker(100 * time.Millisecond) 84 | 85 | go func() { 86 | for { 87 | select { 88 | case <-ticker.C: 89 | c.runPending() 90 | continue 91 | case <-c.stop: 92 | ticker.Stop() 93 | return 94 | } 95 | } 96 | }() 97 | } 98 | 99 | func (c *Clockwerk) runPending() { 100 | go func() { 101 | for _, entry := range c.entries { 102 | if time.Now().After(entry.Next) { 103 | go c.runJob(entry) 104 | } 105 | } 106 | }() 107 | } 108 | 109 | func (c *Clockwerk) runJob(e *Entry) { 110 | defer func() { 111 | if r := recover(); r != nil { 112 | c.schedule(e) 113 | } 114 | }() 115 | 116 | c.schedule(e) 117 | e.Job.Run() 118 | } 119 | --------------------------------------------------------------------------------