├── .gitignore ├── LICENSE ├── README.md ├── examples └── subprocess_test.go ├── go.mod ├── go.sum └── vindicator.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Owen Gong (phith0n) and contributors. 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vindicator 2 | 3 | Vindicator is a lightweight Golang library that is designed to hold and check any blocking function, e.g. subprocess, 4 | network connection... 5 | 6 | ## Contents 7 | 8 | - [Installation](#installation) 9 | - [Quick Start](#quick-start) 10 | - [API Reference](#api-reference) 11 | - [Vindicator](#vindicator-struct) 12 | - [Worker Interface](#worker-interface) 13 | - [Event](#event) 14 | - [FAQ](#faq) 15 | - [Contributing](#contributing) 16 | - [License](#license) 17 | 18 | ## Installation 19 | 20 | ```go 21 | go get -u github.com/phith0n/vindicator 22 | ``` 23 | 24 | ## Quick Start 25 | 26 | You have to write a struct I call it "Worker", that implements `vindicator.Worker`: 27 | 28 | ```go 29 | type Worker interface { 30 | Work(ctx context.Context) error // the worker() must be a blocking function 31 | GetRunning() bool 32 | SetRunning(bool) 33 | } 34 | ``` 35 | 36 | There are 3 functions in the `Worker` interface: 37 | 38 | - `Work (ctx context.Context) error` this function must be a blocking function. the monitor will start a new worker if 39 | this function exit unexpected. 40 | - `SetRunning(run bool)` this function should set the running status of the worker 41 | - `GetRunning() bool` this function should return current running status 42 | 43 | The `Work` function accepts a context object, it must control the lifecycle of your worker. 44 | 45 | For example, if you want to start a subprocess and check it regularly if it is still running, here is the struct 46 | implement: 47 | 48 | ```go 49 | type ProcessWorker struct { 50 | isRunning bool 51 | } 52 | 53 | // Work must be a blocking function 54 | func (pw *ProcessWorker) Work(ctx context.Context) error { 55 | cmd := exec.CommandContext(ctx, "sleep", "1h") 56 | if err := cmd.Start(); err != nil { 57 | return err 58 | } 59 | 60 | if err := cmd.Wait(); err != nil { 61 | return err 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func (pw *ProcessWorker) SetRunning(run bool) { 68 | pw.isRunning = run 69 | } 70 | 71 | func (pw *ProcessWorker) GetRunning() bool { 72 | return pw.isRunning 73 | } 74 | ``` 75 | 76 | Then, use `Vindicator` to start and monitor the `ProcessWorker`: 77 | 78 | ```go 79 | ctx := context.Background() 80 | worker := ProcessWorker{} 81 | v := vindicator.NewVindicator(&worker, 2) 82 | 83 | // you can use event listener to execute some callback function 84 | v.On("monitor:start", func(v *vindicator.Vindicator, args ...interface{}) { 85 | fmt.Println("start monitor") 86 | }) 87 | v.On("monitor:working", func (v *vindicator.Vindicator, args ...interface{}) { 88 | fmt.Println("process is working normally...") 89 | }) 90 | v.On("monitor:interrupt", func (v *vindicator.Vindicator, args ...interface{}) { 91 | fmt.Println("process is stopped unexpected, try to restart it...") 92 | }) 93 | v.On("monitor:stop", func (v *vindicator.Vindicator, args ...interface{}) { 94 | fmt.Println("stop monitor") 95 | }) 96 | v.On("worker:start", func (v *vindicator.Vindicator, args ...interface{}) { 97 | fmt.Println("start worker") 98 | }) 99 | v.On("worker:stop", func (v *vindicator.Vindicator, args ...interface{}) { 100 | fmt.Println("stop worker") 101 | }) 102 | 103 | // run worker and monitor in background 104 | go v.Start(ctx) 105 | go v.Monitor(ctx) 106 | 107 | // to wait sometime... 108 | timer := time.NewTimer(time.Second * 10) 109 | <-timer.C 110 | 111 | // demonstrate how to stop the worker and the monitor manual 112 | v.Stop() 113 | ``` 114 | 115 | This example checks the process running status every 2 seconds, and stop it after 10 seconds. 116 | 117 | The output: 118 | 119 | ``` 120 | start worker 121 | start monitor 122 | process is working normally... 123 | process is working normally... 124 | process is working normally... 125 | process is working normally... 126 | process is working normally... 127 | stop monitor 128 | stop worker 129 | ``` 130 | 131 | The full example code you can find [here](examples/subprocess_test.go). 132 | 133 | ## API Reference 134 | 135 | ### Vindicator Struct 136 | 137 | Create a new `Vindicator`: 138 | 139 | ```go 140 | v := vindicator.NewVindicator(&worker, 2) 141 | ``` 142 | 143 | The first argument is your custom `Worker` implements, the second argument is the monitor cycle time by seconds. 144 | 145 | ### Worker Interface 146 | 147 | ```go 148 | type Worker interface { 149 | Work(ctx context.Context) error // the worker() must be a blocking function 150 | GetRunning() bool 151 | SetRunning(bool) 152 | } 153 | ``` 154 | 155 | ### Event 156 | 157 | There are several events that you can listen and execute custom callback functions: 158 | 159 | - `monitor:start` trigger when monitor is started 160 | - `monitor:stop` trigger when monitor is stopped manual 161 | - `monitor:interrupt` trigger when monitor is stopped unexpected 162 | - `monitor:working` trigger when monitor works at cycle run 163 | - `worker:start` trigger when worker is started 164 | - `worker:stop` trigger when worker is stopped 165 | - `worker:error` trigger when an error is raised by worker 166 | 167 | Use `Vindicator.On` to register a listener: 168 | 169 | ```go 170 | type VindicatorFn func(v *Vindicator, args ...interface{}) 171 | 172 | func (v *Vindicator) On(eventName string, callback VindicatorFn) { 173 | // ... 174 | } 175 | ``` 176 | 177 | ## FAQ 178 | 179 | **When should I use this library?** 180 | 181 | You can use **github.com/phith0n/vindicator** when you are going to run a blocking function and maintain its status. For example, the subprocess, the TCP long connection, the Websocket connection, and any other program like these. 182 | 183 | **Is there a document for this library?** 184 | 185 | No yet. But there are only 100+ lines code for this project, you can kindly read the code and understand it by yourself. 186 | 187 | ## Contributing 188 | 189 | If you'd like to help out with the project. You can put up a Pull Request. 190 | 191 | ## License 192 | 193 | The Vindicator is open-sourced software licensed under the [MIT License](LICENSE). 194 | -------------------------------------------------------------------------------- /examples/subprocess_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/phith0n/vindicator" 7 | "os/exec" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type ProcessWorker struct { 13 | isRunning bool 14 | } 15 | 16 | // Work must be a blocking function 17 | func (pw *ProcessWorker) Work(ctx context.Context) error { 18 | cmd := exec.CommandContext(ctx, "sleep", "1h") 19 | if err := cmd.Start(); err != nil { 20 | return err 21 | } 22 | 23 | if err := cmd.Wait(); err != nil { 24 | return err 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func (pw *ProcessWorker) SetRunning(run bool) { 31 | pw.isRunning = run 32 | } 33 | 34 | func (pw *ProcessWorker) GetRunning() bool { 35 | return pw.isRunning 36 | } 37 | 38 | func TestSubprocess(t *testing.T) { 39 | ctx := context.Background() 40 | worker := ProcessWorker{} 41 | v := vindicator.NewVindicator(&worker, 2) 42 | v.On("monitor:start", func(v *vindicator.Vindicator, args ...interface{}) { 43 | fmt.Println("start monitor") 44 | }) 45 | v.On("monitor:working", func(v *vindicator.Vindicator, args ...interface{}) { 46 | fmt.Println("process is working normally...") 47 | }) 48 | v.On("monitor:interrupt", func(v *vindicator.Vindicator, args ...interface{}) { 49 | fmt.Println("process is stopped unexpected, try to restart it...") 50 | }) 51 | v.On("monitor:stop", func(v *vindicator.Vindicator, args ...interface{}) { 52 | fmt.Println("stop monitor") 53 | }) 54 | v.On("worker:start", func(v *vindicator.Vindicator, args ...interface{}) { 55 | fmt.Println("start worker") 56 | }) 57 | v.On("worker:stop", func(v *vindicator.Vindicator, args ...interface{}) { 58 | fmt.Println("stop worker") 59 | }) 60 | 61 | go v.Start(ctx) 62 | go v.Monitor(ctx) 63 | 64 | timer := time.NewTimer(time.Second * 10) 65 | <-timer.C 66 | 67 | // stop the worker and the monitor manual 68 | v.Stop() 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/phith0n/vindicator 2 | 3 | go 1.18 4 | 5 | require github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM= 2 | github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII= 3 | -------------------------------------------------------------------------------- /vindicator.go: -------------------------------------------------------------------------------- 1 | package vindicator 2 | 3 | import ( 4 | "context" 5 | "github.com/asaskevich/EventBus" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type Worker interface { 11 | Work(ctx context.Context) error // the worker() must be a blocking function 12 | GetRunning() bool 13 | SetRunning(bool) 14 | } 15 | 16 | type Vindicator struct { 17 | interval int // check worker every Interval seconds 18 | worker Worker 19 | lock sync.Mutex 20 | stopWorker func() // stop the worker 21 | stopMonitor func() // stop the monitor 22 | bus EventBus.Bus 23 | } 24 | 25 | type VindicatorFn func(v *Vindicator, args ...interface{}) 26 | 27 | func NewVindicator(worker Worker, interval int) *Vindicator { 28 | return &Vindicator{ 29 | interval: interval, 30 | worker: worker, 31 | bus: EventBus.New(), 32 | } 33 | } 34 | 35 | func (v *Vindicator) Start(ctx context.Context) error { 36 | v.SetRunning() 37 | defer v.SetStopped() 38 | 39 | newCtx, cancel := context.WithCancel(ctx) 40 | v.stopWorker = cancel 41 | v.bus.Publish("worker:start", v) 42 | defer v.bus.Publish("worker:stop", v) 43 | 44 | if err := v.worker.Work(newCtx); err != nil { 45 | v.bus.Publish("worker:error", v, err) 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func (v *Vindicator) Monitor(ctx context.Context) { 53 | v.bus.Publish("monitor:start", v) 54 | 55 | newCtx, cancel := context.WithCancel(ctx) 56 | v.stopMonitor = cancel 57 | timer := time.NewTicker(time.Duration(v.interval) * time.Second) 58 | defer timer.Stop() 59 | for { 60 | select { 61 | case <-newCtx.Done(): 62 | v.bus.Publish("monitor:stop", v) 63 | return 64 | case <-timer.C: 65 | if !v.worker.GetRunning() { 66 | v.bus.Publish("monitor:interrupt", v) 67 | go func() { 68 | _ = v.Start(ctx) 69 | }() 70 | } else { 71 | v.bus.Publish("monitor:working", v) 72 | } 73 | } 74 | } 75 | } 76 | 77 | func (v *Vindicator) Stop() { 78 | if v.stopMonitor != nil { 79 | v.stopMonitor() 80 | } 81 | 82 | if v.stopWorker != nil { 83 | v.stopWorker() 84 | 85 | // block the Stop function until the worker is stopped 86 | v.Wait() 87 | } 88 | } 89 | 90 | func (v *Vindicator) SetRunning() { 91 | v.lock.Lock() 92 | v.worker.SetRunning(true) 93 | } 94 | 95 | func (v *Vindicator) SetStopped() { 96 | v.worker.SetRunning(false) 97 | v.lock.Unlock() 98 | } 99 | 100 | func (v *Vindicator) Wait() { 101 | v.lock.Lock() 102 | v.lock.Unlock() 103 | } 104 | 105 | func (v *Vindicator) On(eventName string, callback VindicatorFn) { 106 | _ = v.bus.Subscribe(eventName, callback) 107 | } 108 | --------------------------------------------------------------------------------