summaryrefslogtreecommitdiff
path: root/service/reload/service.go
diff options
context:
space:
mode:
Diffstat (limited to 'service/reload/service.go')
-rw-r--r--service/reload/service.go162
1 files changed, 162 insertions, 0 deletions
diff --git a/service/reload/service.go b/service/reload/service.go
new file mode 100644
index 00000000..9c615e0b
--- /dev/null
+++ b/service/reload/service.go
@@ -0,0 +1,162 @@
+package reload
+
+import (
+ "errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spiral/roadrunner"
+ "github.com/spiral/roadrunner/service"
+ "os"
+ "strings"
+ "time"
+)
+
+// ID contains default service name.
+const ID = "reload"
+
+type Service struct {
+ cfg *Config
+ log *logrus.Logger
+ watcher *Watcher
+ stopc chan struct{}
+}
+
+// Init controller service
+func (s *Service) Init(cfg *Config, log *logrus.Logger, c service.Container) (bool, error) {
+ if cfg == nil || len(cfg.Services) == 0 {
+ return false, nil
+ }
+
+ s.cfg = cfg
+ s.log = log
+ s.stopc = make(chan struct{})
+
+ var configs []WatcherConfig
+
+ // mount Services to designated services
+ for serviceName := range cfg.Services {
+ svc, _ := c.Get(serviceName)
+ if ctrl, ok := svc.(roadrunner.Controllable); ok {
+ tmp := cfg.Services[serviceName]
+ tmp.service = &ctrl
+ cfg.Services[serviceName] = tmp
+ }
+ }
+
+ for serviceName, config := range s.cfg.Services {
+ if cfg.Services[serviceName].service == nil {
+ continue
+ }
+ ignored, err := ConvertIgnored(config.Ignore)
+ if err != nil {
+ return false, err
+ }
+ configs = append(configs, WatcherConfig{
+ serviceName: serviceName,
+ recursive: config.Recursive,
+ directories: config.Dirs,
+ filterHooks: func(filename string, patterns []string) error {
+ for i := 0; i < len(patterns); i++ {
+ if strings.Contains(filename, patterns[i]) {
+ return nil
+ }
+ }
+ return ErrorSkip
+ },
+ files: make(map[string]os.FileInfo),
+ ignored: ignored,
+ filePatterns: append(config.Patterns, cfg.Patterns...),
+ })
+ }
+
+ var err error
+ s.watcher, err = NewWatcher(configs)
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
+
+func (s *Service) Serve() error {
+ if s.cfg.Interval < time.Second {
+ return errors.New("reload interval is too fast")
+ }
+
+ // make a map with unique services
+ // so, if we would have a 100 events from http service
+ // in map we would see only 1 key and it's config
+ treshholdc := make(chan struct {
+ serviceConfig ServiceConfig
+ service string
+ }, 100)
+
+ // use the same interval
+ ticker := time.NewTicker(s.cfg.Interval)
+
+ // drain channel in case of leaved messages
+ defer func() {
+ go func() {
+ for range treshholdc {
+
+ }
+ }()
+ }()
+
+ go func() {
+ for e := range s.watcher.Event {
+ treshholdc <- struct {
+ serviceConfig ServiceConfig
+ service string
+ }{serviceConfig: s.cfg.Services[e.service], service: e.service}
+ }
+ }()
+
+ // map with configs by services
+ updated := make(map[string]ServiceConfig, 100)
+
+ go func() {
+ for {
+ select {
+ case config := <-treshholdc:
+ // replace previous value in map by more recent without adding new one
+ updated[config.service] = config.serviceConfig
+ // stop ticker
+ ticker.Stop()
+ // restart
+ // logic is following:
+ // if we getting a lot of events, we should't restart particular service on each of it (user doing bug move or very fast typing)
+ // instead, we are resetting the ticker and wait for Interval time
+ // If there is no more events, we restart service only once
+ ticker = time.NewTicker(s.cfg.Interval)
+ case <-ticker.C:
+ if len(updated) > 0 {
+ for k, v := range updated {
+ sv := *v.service
+ err := sv.Server().Reset()
+ if err != nil {
+ s.log.Error(err)
+ }
+ s.log.Debugf("[%s] found %v file(s) changes, reloading", k, len(updated))
+ }
+ // zero map
+ updated = make(map[string]ServiceConfig, 100)
+ }
+ case <-s.stopc:
+ ticker.Stop()
+ return
+ }
+ }
+ }()
+
+ err := s.watcher.StartPolling(s.cfg.Interval)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *Service) Stop() {
+ s.watcher.Stop()
+ s.stopc <- struct{}{}
+}