github.com/ngicks/gokugen@v0.0.5/README.md (about) 1 # gokugen [](https://godoc.org/github.com/ngicks/gokugen) 2 3 go+刻限(kokugen) 4 5 Gokugen is in-memory scheduler and middleware applicator for it. 6 7 Term 刻限(kokugen) is japanese word equivalent of appointed time, scheduled time, or due. 8 9 ## Caveats 10 11 - Do not use untested packages. 12 - This is not stable release. Public APIs may or may not change without any notification. 13 14 ## Idea 15 16 The idea is based on [this article](https://qiita.com/kawasin73/items/7af6766c7898a656b1ee)(written in japanese). 17 18 `./scheduler` contains similar but modified implementation. 19 20 ### Differences 21 22 - It removes cancelled tasks from min-heap at every one minute. 23 - It passes an instance of context.Context to a task, which would be `Done` if scheduler is ended and/or the task is cancelled. 24 - It has a countermeasurement for abnormally-returned work (i.e. calling runtime.Goexit or panicking). But not tested yet! 25 - Task cancellations are controlled by Cancel method of a struct instance returned from `Schedule`. 26 - Cancellation of scheduler is controlled by context.Context. 27 28 ### Additonal Properties 29 30 See below packages section. 31 32 ## Architecture 33 34 simplified architecture. 35 36  37 38 ## TODO 39 40 - [x] Reimplement funtionality 41 - [x] in-memory shceduler 42 - [x] single node task storage middleware 43 - [x] cron-like interface 44 - [x] Implement multi node task storage middleware 45 - [x] Refactoring 46 - [x] example package 47 - [ ] Add detailed careful test. 48 49 ## Packages 50 51 ### ./scheduler 52 53 Scheduler is in-memory scheduler. 54 55 With WorkerPool, scheduler limits how many tasks can be concurrently worked on. 56 And with min-heap backed TaskQueue, task retrieval complexity is O(log n) where n is number of currently scheduled task. 57 58 See `./example/simple/main.go ` for exmpale usage. 59 60 #### Illustrative code sample 61 62 ```go 63 package main 64 65 import ( 66 "context" 67 "fmt" 68 "time" 69 70 "github.com/ngicks/gokugen/scheduler" 71 ) 72 73 func main() { 74 // 1st arg is worker num inittially created 75 // 2nd arg is max of internal min-heap. 0 is unlimited. 76 var initialWorkerNun, queueMax uint 77 sched := scheduler.NewScheduler(initialWorkerNun, queueMax) 78 79 ctx, cancel := context.WithCancel(context.Background()) 80 // Start starts scheduler. 81 go sched.Start(ctx) 82 83 var then time.Time 84 var work scheduler.WorkFn 85 task := scheduler.NewTask(then, work) 86 controller, err := sched.Schedule(task) 87 if err != nil { 88 // scheduling failed. 89 // Queue max or trying to schedule after scheduler already ended. 90 panic(err) 91 } 92 93 // You can check if task is cancelled 94 controller.IsCancelled() 95 // You can check if task is done 96 controller.IsDone() 97 // You can cancel 98 cancelled := controller.Cancel() 99 if !cancelled { 100 fmt.Println("task is already cancelled") 101 } 102 103 // You can check how many workers are actively working on task. 104 fmt.Println(sched.ActiveWorkerNum()) 105 // You can increase the number of Workers in WorkerPool 106 sched.AddWorker(5) 107 // You can decrease the number of Workers in WorkerPool. 108 sched.RemoveWorker(5) 109 110 // some time later... 111 112 // cancel ctx before calling End. 113 cancel() 114 // Call End to tear down scheduler and all internal objects. 115 // and to wait until all goroutines terminate. 116 sched.End() 117 } 118 ``` 119 120 ### ./heap 121 122 Min-heap with added Exclude and Peek method. 123 124 Used in scheduler as TaskQueue. 125 126 ### ./cron 127 128 Cron package contains Row, cron row like struct, and rescheduler for Row. 129 130 See `./example/cron/main.go ` for exmpale usage. 131 132 #### Illustrative code sample 133 134 ```go 135 package main 136 137 import ( 138 "context" 139 "time" 140 141 "github.com/ngicks/gokugen" 142 "github.com/ngicks/gokugen/cron" 143 "github.com/ngicks/gokugen/scheduler" 144 ) 145 146 func main() { 147 scheduler := gokugen.NewMiddlewareApplicator(scheduler.NewScheduler(5, 0)) 148 149 ctx, cancel := context.WithCancel(context.Background()) 150 defer func() { 151 cancel() 152 scheduler.Scheduler().End() 153 }() 154 go scheduler.Scheduler().Start(ctx) 155 156 // Do command every year, Jan, Feb, Mar, every day, 12:30. 157 row := cron.Builder{}. 158 Month(1, 2, 3). 159 Day(). 160 Hour(12). 161 Minute(30). 162 Command([]string{"command"}). 163 Build() 164 // whence is time when scheduling target starts from. 165 var whence time.Time 166 // reshedule occurs if shouldReschedule returns true. 167 var shouldReschedule func(workErr error, callCount int) bool 168 // workRegistry is used to retrieve work function associated to command 169 var workRegisry interface { 170 Load(key string) (value cron.WorkFnWParam, ok bool) 171 } 172 controller := cron.NewCronLikeRescheduler( 173 row, 174 whence, 175 shouldReschedule, 176 scheduler, 177 workRegisry, 178 ) 179 180 // Scheduling starts. 181 // After task is done, row will be recheduled for next time matched to row's configuration. 182 err := controller.Schedule() 183 if err != nil { 184 // somehow Schedule error 185 panic(err) 186 } 187 188 // some time later... 189 190 // Cancell cancells current task and rescheduling. 191 controller.Cancel() 192 } 193 ``` 194 195 ### ./task_storage 196 197 TaskStorage provides middlewares that stores task information to external persistent data storage. 198 199 See `./example/persistent_shceduler/main.go` for example usage. 200 201 #### Illustrative code sample 202 203 ```go 204 package main 205 206 import ( 207 "context" 208 "fmt" 209 "time" 210 211 "github.com/ngicks/gokugen" 212 "github.com/ngicks/gokugen/scheduler" 213 taskstorage "github.com/ngicks/gokugen/task_storage" 214 ) 215 216 func main() { 217 scheduler := gokugen.NewMiddlewareApplicator(scheduler.NewScheduler(5, 0)) 218 219 // Repository interface. 220 // External data storage is manipulated through this interface. 221 var repository taskstorage.RepositoryUpdater 222 // When Sync-ing, this cb is used to determine task should be restored 223 // and re-scheduled in internal scheduler. 224 // (e.g. ignore tasks if they are too old and overdue.) 225 var shouldRestore func(taskstorage.TaskInfo) bool 226 // workRegistry is used to retrieve work function associated to WorkId. 227 // Using `impl/workregistry`.ParamUnmarshaller is safe for almost all users.(untested) 228 var workRegisry interface { 229 Load(key string) (value taskstorage.WorkFnWParam, ok bool) 230 } 231 // Context wrapper applicator function used in Sync. 232 // In Sync newly created ctx is used to reschedule. 233 // So without this function context wrapper 234 // that should be applied in upper user code is totally ignored. 235 var syncCtxWrapper func(gokugen.SchedulerContext) gokugen.SchedulerContext 236 237 taskStorage := taskstorage.NewSingleNodeTaskStorage( 238 repository, 239 shouldRestore, 240 workRegisry, 241 syncCtxWrapper, 242 ) 243 244 // Correct usage is as middleware. 245 scheduler.Use(taskStorage.Middleware(true)...) 246 247 // Sync syncs itnernal state with external. 248 // Normally TaskStorage does it reversely through middlewares, 249 // mirroring internal state to external data storage. 250 // But after rebooting system, or repository is changed externally, 251 // Sync is needed to fetch back external data. 252 rescheduled, schedulingErr, err := taskStorage.Sync(scheduler.Schedule) 253 if err != nil { 254 panic(err) 255 } 256 257 for taskId, taskController := range rescheduled { 258 fmt.Printf( 259 "id = %s, is scheduled for = %s\n", 260 taskId, 261 taskController.GetScheduledTime().Format(time.RFC3339Nano), 262 ) 263 } 264 for taskId, schedulingErr := range schedulingErr { 265 fmt.Printf("id = %s, err = %s\n", taskId, schedulingErr) 266 } 267 268 ctx, cancel := context.WithCancel(context.Background()) 269 go scheduler.Scheduler().Start(ctx) 270 271 var scheduleTarget time.Time 272 task, err := scheduler.Schedule( 273 // To store correct data to external repository, 274 // WorkId, Param is additionally needed. 275 gokugen.BuildContext( 276 scheduleTarget, 277 nil, 278 nil, 279 gokugen.WithWorkId("func1"), 280 gokugen.WithParam([]string{"param", "param"}), 281 ), 282 ) 283 if err != nil { 284 panic(err) 285 } 286 287 // This is wrapped scheduler.TaskController. 288 task.IsCancelled() 289 290 // some time later... 291 292 // cancel ctx and tear down scheduler. 293 cancel() 294 scheduler.Scheduler().End() 295 } 296 ``` 297 298 ### ./example 299 300 example contains some example executables. 301 302 ### ./impl 303 304 impl contains some helper implementations.