diff options
author | Valery Piashchynski <[email protected]> | 2020-02-17 19:13:15 +0300 |
---|---|---|
committer | Valery Piashchynski <[email protected]> | 2020-02-17 19:13:15 +0300 |
commit | 50b46c8d3c0e1f13623e2cd7cbb1302ae66ed308 (patch) | |
tree | 71f1ac71fe3e5bf024b199136584e601016ac0c7 | |
parent | 25ef7646e2149b6e35945eb4ce50c19db2ef8e27 (diff) |
Add reload service
Start to implement Watcher
-rw-r--r-- | cmd/rr/main.go | 2 | ||||
-rw-r--r-- | service/reload/config.go | 41 | ||||
-rw-r--r-- | service/reload/config_test.go | 1 | ||||
-rw-r--r-- | service/reload/service.go | 40 | ||||
-rw-r--r-- | service/reload/service_test.go | 1 | ||||
-rw-r--r-- | service/reload/watcher.go | 235 |
6 files changed, 320 insertions, 0 deletions
diff --git a/cmd/rr/main.go b/cmd/rr/main.go index ef393426..46431e49 100644 --- a/cmd/rr/main.go +++ b/cmd/rr/main.go @@ -24,6 +24,7 @@ package main import ( rr "github.com/spiral/roadrunner/cmd/rr/cmd" + "github.com/spiral/roadrunner/service/reload" // services (plugins) "github.com/spiral/roadrunner/service/env" @@ -51,6 +52,7 @@ func main() { rr.Container.Register(limit.ID, &limit.Service{}) rr.Container.Register(health.ID, &health.Service{}) rr.Container.Register(gzip.ID, &gzip.Service{}) + rr.Container.Register(reload.ID, &reload.Service{}) // you can register additional commands using cmd.CLI rr.Execute() diff --git a/service/reload/config.go b/service/reload/config.go new file mode 100644 index 00000000..338c6eba --- /dev/null +++ b/service/reload/config.go @@ -0,0 +1,41 @@ +package reload + +import "github.com/spiral/roadrunner/service" + +// Config is a Reload configuration point. +type Config struct { + // Enable or disable Reload extension, default disable. + Enabled bool + + // Watch is general pattern of files to watch. It will be applied to every directory in project + Watch []string + + // Services is set of services which would be reloaded in case of FS changes + Services map[string]ServiceConfig +} + +type ServiceConfig struct { + // Watch is per-service specific files to watch + Watch []string + // Dirs is per-service specific dirs which will be combined with Watch + Dirs []string + // Ignore is set of files which would not be watched + Ignore []string +} + + +// Hydrate must populate Config values using given Config source. Must return error if Config is not valid. +func (c *Config) Hydrate(cfg service.Config) error { + if err := cfg.Unmarshal(c); err != nil { + return err + } + return nil +} + +// InitDefaults sets missing values to their default values. +func (c *Config) InitDefaults() error { + //c.Interval = time.Second + + return nil +} + diff --git a/service/reload/config_test.go b/service/reload/config_test.go new file mode 100644 index 00000000..7cad4a5d --- /dev/null +++ b/service/reload/config_test.go @@ -0,0 +1 @@ +package reload diff --git a/service/reload/service.go b/service/reload/service.go new file mode 100644 index 00000000..db10b6f4 --- /dev/null +++ b/service/reload/service.go @@ -0,0 +1,40 @@ +package reload + +import "github.com/spiral/roadrunner/service" + +// ID contains default service name. +const ID = "reload" + +type Service struct { + reloadConfig *Config +} + +// Init controller service +func (s *Service) Init(cfg *Config, c service.Container) (bool, error) { + // mount Services to designated services + //for id, watcher := range cfg.Controllers(s.throw) { + // svc, _ := c.Get(id) + // if ctrl, ok := svc.(controllable); ok { + // ctrl.Attach(watcher) + // } + //} + + s.reloadConfig = cfg + + return true, nil +} + +func (s *Service) Serve() error { + w, err := NewWatcher(s.reloadConfig, SetMaxFileEvents(100)) + if err != nil { + return err + } + + _ = w + + return nil +} + +func (s *Service) Stop() { + +}
\ No newline at end of file diff --git a/service/reload/service_test.go b/service/reload/service_test.go new file mode 100644 index 00000000..7cad4a5d --- /dev/null +++ b/service/reload/service_test.go @@ -0,0 +1 @@ +package reload diff --git a/service/reload/watcher.go b/service/reload/watcher.go new file mode 100644 index 00000000..e81ce56f --- /dev/null +++ b/service/reload/watcher.go @@ -0,0 +1,235 @@ +package reload + +import ( + "errors" + "os" + "path/filepath" + "regexp" + "sync" + "time" +) + +// Config is a Reload configuration point. +//type Config struct { +// Enable or disable Reload extension, default disable. +//Enabled bool +// +// Watch is general pattern of files to watch. It will be applied to every directory in project +//Watch []string +// +// Services is set of services which would be reloaded in case of FS changes +//Services map[string]ServiceConfig +//} +// +//type ServiceConfig struct { +// Watch is per-service specific files to watch +//Watch []string +// Dirs is per-service specific dirs which will be combined with Watch +//Dirs []string +// Ignore is set of files which would not be watched +//Ignore []string +//} + +// An Op is a type that is used to describe what type +// of event has occurred during the watching process. +type Op uint32 + +// Ops +const ( + Create Op = iota + Write + Remove + Rename + Chmod + Move +) + +var ops = map[Op]string{ + Create: "CREATE", + Write: "WRITE", + Remove: "REMOVE", + Rename: "RENAME", + Chmod: "CHMOD", + Move: "MOVE", +} + +var ( + // ErrDurationTooShort occurs when calling the watcher's Start + // method with a duration that's less than 1 nanosecond. + ErrDurationTooShort = errors.New("error: duration is less than 1ns") + + // ErrWatcherRunning occurs when trying to call the watcher's + // Start method and the polling cycle is still already running + // from previously calling Start and not yet calling Close. + ErrWatcherRunning = errors.New("error: watcher is already running") + + // ErrWatchedFileDeleted is an error that occurs when a file or folder that was + // being watched has been deleted. + ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted") + + // ErrSkip is less of an error, but more of a way for path hooks to skip a file or + // directory. + ErrSkip = errors.New("error: skipping file") +) + +// FilterFileHookFunc is a function that is called to filter files during listings. +// If a file is ok to be listed, nil is returned otherwise ErrSkip is returned. +type FilterFileHookFunc func(info os.FileInfo, fullPath string) error + +// RegexFilterHook is a function that accepts or rejects a file +// for listing based on whether it's filename or full path matches +// a regular expression. +func RegexFilterHook(r *regexp.Regexp, useFullPath bool) FilterFileHookFunc { + return func(info os.FileInfo, fullPath string) error { + str := info.Name() + + if useFullPath { + str = fullPath + } + + // Match + if r.MatchString(str) { + return nil + } + + // No match. + return ErrSkip + } +} + +// An Event describes an event that is received when files or directory +// changes occur. It includes the os.FileInfo of the changed file or +// directory and the type of event that's occurred and the full path of the file. +type Event struct { + Op + Path string + OldPath string + os.FileInfo +} + +type Watcher struct { + Event chan Event + errors chan error + wg *sync.WaitGroup + + filterHooks []FilterFileHookFunc + + workingDir string + maxFileWatchEvents int + ops map[Op]struct{} // Op filtering. + files map[string]string //files by service, http, grpc, etc.. + ignored map[string]string //ignored files or directories +} + +// Options is used to set Watcher Options +type Options func(*Watcher) + +func NewWatcher(options ...Options) (*Watcher, error) { + dir, err := os.Getwd() + if err != nil { + return nil, err + } + + w := &Watcher{ + workingDir: dir, + } + + for _, option := range options { + option(w) + } + + // dir --> /home/valery/Projects/opensource/roadrunner + return w, nil +} + +// https://en.wikipedia.org/wiki/Inotify +// SetMaxFileEvents sets max file notify events for Watcher +// In case of file watch errors, this value can be increased system-wide +// For linux: set --> fs.inotify.max_user_watches = 600000 (under /etc/<choose_name_here>.conf) +// Add apply: sudo sysctl -p --system +func SetMaxFileEvents(events int) Options { + return func(watcher *Watcher) { + watcher.maxFileWatchEvents = events + } + +} + +// Add +// name will be +func (w *Watcher) Add(name string) error { + name, err := filepath.Abs(name) + if err != nil { + + } + + // Ignored files + // map is to have O(1) when search for file + _, ignored := w.ignored[name] + if ignored { + return nil + } + + // small optimization for smallvector + fileList := make(map[string]os.FileInfo, 10) + err = w.addDirectoryContent(" ", fileList) + if err != nil { + return err + } + + +} + +func (w *Watcher) addDirectoryContent(name string, filelist map[string]os.FileInfo) error { + fileInfo, err := os.Stat(name) + if err != nil { + return err + } + + filelist[name] = fileInfo + + // if it's not a dir, return + if !fileInfo.IsDir() { + return nil + } + + + + +} + +func (w *Watcher) search(map[string]os.FileInfo) error { + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + |