github.com/ngicks/gokugen@v0.0.5/README.md (about)

     1  # gokugen [![Godoc](https://godoc.org/github.com/ngicks/gokugen?status.svg)](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  ![simplified_architecture](./arch.drawio.svg)
    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.