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  }