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  }