github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+incompatible/plugin/rpcplugin/supervisor.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package rpcplugin 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 "path/filepath" 11 "strings" 12 "sync/atomic" 13 "time" 14 15 "github.com/mattermost/mattermost-server/model" 16 "github.com/mattermost/mattermost-server/plugin" 17 ) 18 19 // Supervisor implements a plugin.Supervisor that launches the plugin in a separate process and 20 // communicates via RPC. 21 // 22 // If the plugin unexpectedly exists, the supervisor will relaunch it after a short delay. 23 type Supervisor struct { 24 hooks atomic.Value 25 done chan bool 26 cancel context.CancelFunc 27 newProcess func(context.Context) (Process, io.ReadWriteCloser, error) 28 } 29 30 var _ plugin.Supervisor = (*Supervisor)(nil) 31 32 // Starts the plugin. This method will block until the plugin is successfully launched for the first 33 // time and will return an error if the plugin cannot be launched at all. 34 func (s *Supervisor) Start(api plugin.API) error { 35 ctx, cancel := context.WithCancel(context.Background()) 36 s.done = make(chan bool, 1) 37 start := make(chan error, 1) 38 go s.run(ctx, start, api) 39 40 select { 41 case <-time.After(time.Second * 3): 42 cancel() 43 <-s.done 44 return fmt.Errorf("timed out waiting for plugin") 45 case err := <-start: 46 s.cancel = cancel 47 return err 48 } 49 } 50 51 // Stops the plugin. 52 func (s *Supervisor) Stop() error { 53 s.cancel() 54 <-s.done 55 return nil 56 } 57 58 // Returns the hooks used to communicate with the plugin. The hooks may change if the plugin is 59 // restarted, so the return value should not be cached. 60 func (s *Supervisor) Hooks() plugin.Hooks { 61 return s.hooks.Load().(plugin.Hooks) 62 } 63 64 func (s *Supervisor) run(ctx context.Context, start chan<- error, api plugin.API) { 65 defer func() { 66 s.done <- true 67 }() 68 done := ctx.Done() 69 for { 70 s.runPlugin(ctx, start, api) 71 select { 72 case <-done: 73 return 74 default: 75 start = nil 76 time.Sleep(time.Second) 77 } 78 } 79 } 80 81 func (s *Supervisor) runPlugin(ctx context.Context, start chan<- error, api plugin.API) error { 82 p, ipc, err := s.newProcess(ctx) 83 if err != nil { 84 if start != nil { 85 start <- err 86 } 87 return err 88 } 89 90 muxer := NewMuxer(ipc, false) 91 closeMuxer := make(chan bool, 1) 92 muxerClosed := make(chan error, 1) 93 go func() { 94 select { 95 case <-ctx.Done(): 96 break 97 case <-closeMuxer: 98 break 99 } 100 muxerClosed <- muxer.Close() 101 }() 102 103 hooks, err := ConnectMain(muxer) 104 if err == nil { 105 err = hooks.OnActivate(api) 106 } 107 108 if err != nil { 109 if start != nil { 110 start <- err 111 } 112 closeMuxer <- true 113 <-muxerClosed 114 p.Wait() 115 return err 116 } 117 118 s.hooks.Store(hooks) 119 120 if start != nil { 121 start <- nil 122 } 123 p.Wait() 124 closeMuxer <- true 125 <-muxerClosed 126 127 return nil 128 } 129 130 func SupervisorProvider(bundle *model.BundleInfo) (plugin.Supervisor, error) { 131 return SupervisorWithNewProcessFunc(bundle, func(ctx context.Context) (Process, io.ReadWriteCloser, error) { 132 executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable)) 133 if strings.HasPrefix(executable, "..") { 134 return nil, nil, fmt.Errorf("invalid backend executable") 135 } 136 return NewProcess(ctx, filepath.Join(bundle.Path, executable)) 137 }) 138 } 139 140 func SupervisorWithNewProcessFunc(bundle *model.BundleInfo, newProcess func(context.Context) (Process, io.ReadWriteCloser, error)) (plugin.Supervisor, error) { 141 if bundle.Manifest == nil { 142 return nil, fmt.Errorf("no manifest available") 143 } else if bundle.Manifest.Backend == nil || bundle.Manifest.Backend.Executable == "" { 144 return nil, fmt.Errorf("no backend executable specified") 145 } 146 executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable)) 147 if strings.HasPrefix(executable, "..") { 148 return nil, fmt.Errorf("invalid backend executable") 149 } 150 return &Supervisor{newProcess: newProcess}, nil 151 }