diff options
author | Valery Piashchynski <[email protected]> | 2020-02-19 17:30:05 +0300 |
---|---|---|
committer | Valery Piashchynski <[email protected]> | 2020-02-19 17:30:05 +0300 |
commit | 9cff24e4d515d684cefdf46624da90d22224aeaa (patch) | |
tree | b49142bcb1fd207065259f7693e36ab67f8eed58 | |
parent | 24ec01aa7f0225098e4c750a8c51843cc41bbf8b (diff) |
First concept of reload
-rw-r--r-- | service/reload/samefile.go | 9 | ||||
-rw-r--r-- | service/reload/samefile_windows.go | 12 | ||||
-rw-r--r-- | service/reload/service.go | 58 | ||||
-rw-r--r-- | service/reload/watcher.go | 399 |
4 files changed, 450 insertions, 28 deletions
diff --git a/service/reload/samefile.go b/service/reload/samefile.go new file mode 100644 index 00000000..80df0431 --- /dev/null +++ b/service/reload/samefile.go @@ -0,0 +1,9 @@ +// +build !windows + +package reload + +import "os" + +func sameFile(fi1, fi2 os.FileInfo) bool { + return os.SameFile(fi1, fi2) +} diff --git a/service/reload/samefile_windows.go b/service/reload/samefile_windows.go new file mode 100644 index 00000000..5f70d327 --- /dev/null +++ b/service/reload/samefile_windows.go @@ -0,0 +1,12 @@ +// +build windows + +package reload + +import "os" + +func sameFile(fi1, fi2 os.FileInfo) bool { + return fi1.ModTime() == fi2.ModTime() && + fi1.Size() == fi2.Size() && + fi1.Mode() == fi2.Mode() && + fi1.IsDir() == fi2.IsDir() +} diff --git a/service/reload/service.go b/service/reload/service.go index ee2aed99..78f147b7 100644 --- a/service/reload/service.go +++ b/service/reload/service.go @@ -3,6 +3,7 @@ package reload import ( "github.com/spiral/roadrunner/service" "os" + "time" ) // ID contains default service name. @@ -10,18 +11,12 @@ const ID = "reload" type Service struct { reloadConfig *Config + container service.Container } // 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.container = c s.reloadConfig = cfg return true, nil @@ -33,11 +28,52 @@ func (s *Service) Serve() error { return err } - name , _ := os.Getwd() + name , err := os.Getwd() + if err != nil { + return err + } + + err = w.AddSingle(name) + if err != nil { + return err + } - w.AddSingle(name) + go func() { + err = w.StartPolling(time.Second) + if err != nil { + + } + }() + + + + // read events and restart corresponding services + + + for { + select { + case e := <- w.Event: + println(e.Name()) + } + } + //for e = range w.Event { + // + // println("event") + // // todo use status + // //svc, _ := s.container.Get("http") + // //if svc != nil { + // // if srv, ok := svc.(service.Service); ok { + // // srv.Stop() + // // err = srv.Serve() + // // if err != nil { + // // return err + // // } + // // } + // //} + // + // //println("event skipped due to service is nil") + //} - println("test") return nil } diff --git a/service/reload/watcher.go b/service/reload/watcher.go index ecc8c3ce..8d344b4c 100644 --- a/service/reload/watcher.go +++ b/service/reload/watcher.go @@ -6,8 +6,9 @@ import ( "os" "path/filepath" "regexp" + "strings" "sync" - "syscall" + "time" ) // Config is a Reload configuration point. @@ -92,18 +93,27 @@ func SetFileHooks(fileHook ...FilterFileHookFunc) Options { // 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 string // type of event, http, grpc, etc... } type Watcher struct { Event chan Event errors chan error - wg *sync.WaitGroup + close chan struct{} + Closed chan struct{} + + mu *sync.Mutex + //wg *sync.WaitGroup filterHooks []FilterFileHookFunc + started bool // indicates is walker started or not + workingDir string maxFileWatchEvents int operations map[Op]struct{} // Op filtering. @@ -121,6 +131,11 @@ func NewWatcher(options ...Options) (*Watcher, error) { } w := &Watcher{ + Event: make(chan Event), + mu: &sync.Mutex{}, + //wg: &sync.WaitGroup{}, + Closed: make(chan struct{}), + close: make(chan struct{}), workingDir: dir, operations: make(map[Op]struct{}), files: make(map[string]os.FileInfo), @@ -170,8 +185,8 @@ func (w *Watcher) AddSingle(name string) error { } // small optimization for smallvector - fileList := make(map[string]os.FileInfo, 10) - err = w.addDirectoryContent(name, fileList) + //fileList := make(map[string]os.FileInfo, 10) + fileList, err := w.retrieveSingleDirectoryContent(name) if err != nil { return err } @@ -184,23 +199,59 @@ func (w *Watcher) AddSingle(name string) error { } -// pass map from outside -func (w *Watcher) addDirectoryContent(name string, filelist map[string]os.FileInfo) error { - fileInfo, err := os.Stat(name) +func (w *Watcher) AddRecursive(name string) error { + name, err := filepath.Abs(name) if err != nil { return err } - filelist[name] = fileInfo + filesList := make(map[string]os.FileInfo, 10) + + err = w.retrieveFilesRecursive(name, filesList) + if err != nil { + return err + } + + for k, v := range filesList { + w.files[k] = v + } + + return nil +} + +// pass map from outside +func (w *Watcher) retrieveSingleDirectoryContent(path string) (map[string]os.FileInfo, error) { + stat, err := os.Stat(path) + if err != nil { + return nil, err + } + + filesList := make(map[string]os.FileInfo, 10) + + filesList[path] = stat // if it's not a dir, return - if !fileInfo.IsDir() { - return nil + if !stat.IsDir() { + return filesList, nil } - fileInfoList, err := ioutil.ReadDir(name) + //err = filepath.Walk(name, func(path string, info os.FileInfo, err error) error { + // if info.IsDir() { + // return nil + // } + // + // fileList[path] = info + // + // return nil + //}) + // + //if err != nil { + // return err + //} + + fileInfoList, err := ioutil.ReadDir(path) if err != nil { - return err + return nil, err } // recursive calls are slow in compare to goto @@ -212,27 +263,341 @@ outer: // BCE check elimination // https://go101.org/article/bounds-check-elimination.html if len(fileInfoList) != 0 && len(fileInfoList) >= i { - path = filepath.Join(name, fileInfoList[i].Name()) + path = filepath.Join(path, fileInfoList[i].Name()) } else { - return errors.New("file info list len") + return nil, errors.New("file info list len") } // if file in ignored --> continue - if _, ignored := w.ignored[name]; ignored { + if _, ignored := w.ignored[path]; ignored { continue } for _, fh := range w.filterHooks { - err := fh(fileInfo, path) + err := fh(fileInfoList[i], path) if err != nil { // if err is not nil, move to the start of the cycle since the path not match the hook continue outer } } - filelist[path] = fileInfo + filesList[path] = fileInfoList[i] + + } + + return filesList, nil +} + +func (w *Watcher) StartPolling(duration time.Duration) error { + if duration < time.Second { + return errors.New("too short duration, please use at least 1 second") + } + + w.mu.Lock() + if w.started { + w.mu.Unlock() + return errors.New("already started") + } + + w.started = true + w.mu.Unlock() + + //w.wg.Done() + + return w.waitEvent(duration) +} + +// this is blocking operation +func (w *Watcher) waitEvent(d time.Duration) error { + for { + // done lets the inner polling cycle loop know when the + // current cycle's method has finished executing. + //done := make(chan struct{}) + + // Any events that are found are first piped to evt before + // being sent to the main Event channel. + //evt := make(chan Event) + + // Retrieve the file list for all watched file's and dirs. + //fileList := w.files + + // cancel can be used to cancel the current event polling function. + cancel := make(chan struct{}) + + // Look for events. + //go func() { + // w.pollEvents(w.files, evt, cancel) + // done <- struct{}{} + //}() + + // numEvents holds the number of events for the current cycle. + //numEvents := 0 + + ticker := time.NewTicker(d) + for { + select { + case <-w.close: + close(cancel) + close(w.Closed) + return nil + case <-ticker.C: + //fileList := make(map[string]os.FileInfo, 100) + //w.mu.Lock() + fileList, _ := w.retrieveFileList(w.workingDir, false) + w.pollEvents(fileList, cancel) + //w.mu.Unlock() + default: + + } + } + + ticker.Stop() + //inner: + // for { + // select { + // case <-w.close: + // close(cancel) + // close(w.Closed) + // return nil + // case event := <-evt: + // //if len(w.operations) > 0 { // Filter Ops. + // // _, found := w.operations[event.Op] + // // if !found { + // // continue + // // } + // //} + // //numEvents++ + // //if w.maxFileWatchEvents > 0 && numEvents > w.maxFileWatchEvents { + // // close(cancel) + // // break inner + // //} + // w.Event <- event + // case <-done: // Current cycle is finished. + // break inner + // } + // } + + //// Update the file's list. + //w.mu.Lock() + //w.files = fileList + //w.mu.Unlock() + + //time.Sleep(d) + //sleepLoop: + // for { + // select { + // case <-w.close: + // close(cancel) + // return nil + // case <-time.After(d): + // break sleepLoop + // } + // } //end Sleep for + } +} + +func (w *Watcher) retrieveFileList(path string, recursive bool) (map[string]os.FileInfo, error) { + + //fileList := make(map[string]os.FileInfo) + + //list := make(map[string]os.FileInfo, 100) + //var err error + + if recursive { + //fileList, err := w.retrieveFilesRecursive(path) + //if err != nil { + //if os.IsNotExist(err) { + // w.mu.Unlock() + // // todo path error + // _, ok := err.(*os.PathError) + // if ok { + // w.RemoveRecursive(path) + // } + // w.mu.Lock() + //} else { + // w.errors <- err + //} + //} + + //for k, v := range fileList { + // fileList[k] = v + //} + //return fileList, nil + return nil, nil + } else { + fileList, err := w.retrieveSingleDirectoryContent(path) + if err != nil { + //if os.IsNotExist(err) { + // w.mu.Unlock() + // _, ok := err.(*os.PathError) + // if ok { + // w.RemoveRecursive(path) + // } + // w.mu.Lock() + //} else { + // w.errors <- err + //} + } + + for k, v := range fileList { + fileList[k] = v + } + return fileList, nil + } + // Add the file's to the file list. + + //return nil +} + +// RemoveRecursive removes either a single file or a directory recursively from +// the file's list. +func (w *Watcher) RemoveRecursive(name string) (err error) { + w.mu.Lock() + defer w.mu.Unlock() + name, err = filepath.Abs(name) + if err != nil { + return err } + // If name is a single file, remove it and return. + info, found := w.files[name] + if !found { + return nil // Doesn't exist, just return. + } + if !info.IsDir() { + delete(w.files, name) + return nil + } + + // If it's a directory, delete all of it's contents recursively + // from w.files. + for path := range w.files { + if strings.HasPrefix(path, name) { + delete(w.files, path) + } + } return nil } + +func (w *Watcher) retrieveFilesRecursive(name string, fileList map[string]os.FileInfo) error { + return filepath.Walk(name, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + for _, f := range w.filterHooks { + err := f(info, path) + if err == ErrorSkip { + return nil + } + if err != nil { + return err + } + } + + // If path is ignored and it's a directory, skip the directory. If it's + // ignored and it's a single file, skip the file. + _, ignored := w.ignored[path] + + if ignored { + if info.IsDir() { + return filepath.SkipDir + } + return nil + } + // Add the path and it's info to the file list. + fileList[path] = info + return nil + }) +} + +// Wait blocks until the watcher is started. +//func (w *Watcher) Wait() { +// w.wg.Wait() +//} + +func (w *Watcher) pollEvents(files map[string]os.FileInfo, cancel chan struct{}) { + w.mu.Lock() + defer w.mu.Unlock() + + // Store create and remove events for use to check for rename events. + creates := make(map[string]os.FileInfo) + removes := make(map[string]os.FileInfo) + + // Check for removed files. + for path, info := range w.files { + if _, found := files[path]; !found { + removes[path] = info + } + } + + // Check for created files, writes and chmods. + for path, info := range files { + oldInfo, found := w.files[path] + if !found { + // A file was created. + creates[path] = info + continue + } + if oldInfo.ModTime() != info.ModTime() { + w.files[path] = info + select { + case <-cancel: + return + case w.Event <- Event{Write, path, path, info, "http"}: + } + } + if oldInfo.Mode() != info.Mode() { + select { + case <-cancel: + return + case w.Event <- Event{Chmod, path, path, info, "http"}: + } + } + } + + // Check for renames and moves. + for path1, info1 := range removes { + for path2, info2 := range creates { + if sameFile(info1, info2) { + e := Event{ + Op: Move, + Path: path2, + OldPath: path1, + FileInfo: info1, + } + // If they are from the same directory, it's a rename + // instead of a move event. + if filepath.Dir(path1) == filepath.Dir(path2) { + e.Op = Rename + } + + delete(removes, path1) + delete(creates, path2) + + select { + case <-cancel: + return + case w.Event <- e: + } + } + } + } + + // Send all the remaining create and remove events. + for path, info := range creates { + select { + case <-cancel: + return + case w.Event <- Event{Create, path, "", info, "http"}: + } + } + for path, info := range removes { + select { + case <-cancel: + return + case w.Event <- Event{Remove, path, path, info, "http"}: + } + } +} |