github.com/qichengzx/mattermost-server@v4.5.1-0.20180604164826-2c75247c97d0+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/mlog" 16 "github.com/mattermost/mattermost-server/model" 17 "github.com/mattermost/mattermost-server/plugin" 18 ) 19 20 const ( 21 MaxProcessRestarts = 3 22 ) 23 24 // Supervisor implements a plugin.Supervisor that launches the plugin in a separate process and 25 // communicates via RPC. 26 // 27 // If the plugin unexpectedly exits, the supervisor will relaunch it after a short delay, but will 28 // only restart a plugin at most three times. 29 type Supervisor struct { 30 hooks atomic.Value 31 done chan bool 32 cancel context.CancelFunc 33 newProcess func(context.Context) (Process, io.ReadWriteCloser, error) 34 pluginId string 35 pluginErr error 36 } 37 38 var _ plugin.Supervisor = (*Supervisor)(nil) 39 40 // Starts the plugin. This method will block until the plugin is successfully launched for the first 41 // time and will return an error if the plugin cannot be launched at all. 42 func (s *Supervisor) Start(api plugin.API) error { 43 ctx, cancel := context.WithCancel(context.Background()) 44 s.done = make(chan bool, 1) 45 start := make(chan error, 1) 46 go s.run(ctx, start, api) 47 48 select { 49 case <-time.After(time.Second * 3): 50 cancel() 51 <-s.done 52 return fmt.Errorf("timed out waiting for plugin") 53 case err := <-start: 54 s.cancel = cancel 55 return err 56 } 57 } 58 59 // Waits for the supervisor to stop (on demand or of its own accord), returning any error that 60 // triggered the supervisor to stop. 61 func (s *Supervisor) Wait() error { 62 <-s.done 63 return s.pluginErr 64 } 65 66 // Stops the plugin. 67 func (s *Supervisor) Stop() error { 68 s.cancel() 69 <-s.done 70 return nil 71 } 72 73 // Returns the hooks used to communicate with the plugin. The hooks may change if the plugin is 74 // restarted, so the return value should not be cached. 75 func (s *Supervisor) Hooks() plugin.Hooks { 76 return s.hooks.Load().(plugin.Hooks) 77 } 78 79 func (s *Supervisor) run(ctx context.Context, start chan<- error, api plugin.API) { 80 defer func() { 81 close(s.done) 82 }() 83 done := ctx.Done() 84 for i := 0; i <= MaxProcessRestarts; i++ { 85 s.runPlugin(ctx, start, api) 86 select { 87 case <-done: 88 return 89 default: 90 start = nil 91 if i < MaxProcessRestarts { 92 mlog.Error("Plugin terminated unexpectedly", mlog.String("plugin_id", s.pluginId)) 93 time.Sleep(time.Duration((1 + i*i)) * time.Second) 94 } else { 95 s.pluginErr = fmt.Errorf("plugin terminated unexpectedly too many times") 96 mlog.Error("Plugin shutdown", mlog.String("plugin_id", s.pluginId), mlog.Int("max_process_restarts", MaxProcessRestarts), mlog.Err(s.pluginErr)) 97 } 98 } 99 } 100 } 101 102 func (s *Supervisor) runPlugin(ctx context.Context, start chan<- error, api plugin.API) error { 103 if start == nil { 104 mlog.Debug("Restarting plugin", mlog.String("plugin_id", s.pluginId)) 105 } 106 107 p, ipc, err := s.newProcess(ctx) 108 if err != nil { 109 if start != nil { 110 start <- err 111 } 112 return err 113 } 114 115 muxer := NewMuxer(ipc, false) 116 closeMuxer := make(chan bool, 1) 117 muxerClosed := make(chan error, 1) 118 go func() { 119 select { 120 case <-ctx.Done(): 121 break 122 case <-closeMuxer: 123 break 124 } 125 muxerClosed <- muxer.Close() 126 }() 127 128 hooks, err := ConnectMain(muxer, s.pluginId) 129 if err == nil { 130 err = hooks.OnActivate(api) 131 } 132 133 if err != nil { 134 if start != nil { 135 start <- err 136 } 137 closeMuxer <- true 138 <-muxerClosed 139 p.Wait() 140 return err 141 } 142 143 s.hooks.Store(hooks) 144 145 if start != nil { 146 start <- nil 147 } 148 p.Wait() 149 closeMuxer <- true 150 <-muxerClosed 151 152 return nil 153 } 154 155 func SupervisorProvider(bundle *model.BundleInfo) (plugin.Supervisor, error) { 156 return SupervisorWithNewProcessFunc(bundle, func(ctx context.Context) (Process, io.ReadWriteCloser, error) { 157 executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable)) 158 if strings.HasPrefix(executable, "..") { 159 return nil, nil, fmt.Errorf("invalid backend executable") 160 } 161 return NewProcess(ctx, filepath.Join(bundle.Path, executable)) 162 }) 163 } 164 165 func SupervisorWithNewProcessFunc(bundle *model.BundleInfo, newProcess func(context.Context) (Process, io.ReadWriteCloser, error)) (plugin.Supervisor, error) { 166 if bundle.Manifest == nil { 167 return nil, fmt.Errorf("no manifest available") 168 } else if bundle.Manifest.Backend == nil || bundle.Manifest.Backend.Executable == "" { 169 return nil, fmt.Errorf("no backend executable specified") 170 } 171 executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable)) 172 if strings.HasPrefix(executable, "..") { 173 return nil, fmt.Errorf("invalid backend executable") 174 } 175 return &Supervisor{pluginId: bundle.Manifest.Id, newProcess: newProcess}, nil 176 }