summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValery Piashchynski <[email protected]>2020-02-19 17:30:05 +0300
committerValery Piashchynski <[email protected]>2020-02-19 17:30:05 +0300
commit9cff24e4d515d684cefdf46624da90d22224aeaa (patch)
treeb49142bcb1fd207065259f7693e36ab67f8eed58
parent24ec01aa7f0225098e4c750a8c51843cc41bbf8b (diff)
First concept of reload
-rw-r--r--service/reload/samefile.go9
-rw-r--r--service/reload/samefile_windows.go12
-rw-r--r--service/reload/service.go58
-rw-r--r--service/reload/watcher.go399
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"}:
+ }
+ }
+}