github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/helper/pluginutils/singleton/singleton.go (about)

     1  package singleton
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	log "github.com/hashicorp/go-hclog"
     8  	plugin "github.com/hashicorp/go-plugin"
     9  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    10  	"github.com/hashicorp/nomad/plugins/base"
    11  )
    12  
    13  var (
    14  	// SingletonPluginExited is returned when the dispense is called and the
    15  	// existing plugin has exited. The caller should retry, and this will issue
    16  	// a new plugin instance.
    17  	SingletonPluginExited = fmt.Errorf("singleton plugin exited")
    18  )
    19  
    20  // SingletonLoader is used to only load a single external plugin at a time.
    21  type SingletonLoader struct {
    22  	// Loader is the underlying plugin loader that we wrap to give a singleton
    23  	// behavior.
    24  	loader loader.PluginCatalog
    25  
    26  	// instances is a mapping of the plugin to a future which holds a plugin
    27  	// instance
    28  	instances    map[loader.PluginID]*future
    29  	instanceLock sync.Mutex
    30  
    31  	// logger is the logger used by the singleton
    32  	logger log.Logger
    33  }
    34  
    35  // NewSingletonLoader wraps a plugin catalog and provides singleton behavior on
    36  // top by caching running instances.
    37  func NewSingletonLoader(logger log.Logger, catalog loader.PluginCatalog) *SingletonLoader {
    38  	return &SingletonLoader{
    39  		loader:    catalog,
    40  		logger:    logger.Named("singleton_plugin_loader"),
    41  		instances: make(map[loader.PluginID]*future, 4),
    42  	}
    43  }
    44  
    45  // Catalog returns the catalog of all plugins keyed by plugin type
    46  func (s *SingletonLoader) Catalog() map[string][]*base.PluginInfoResponse {
    47  	return s.loader.Catalog()
    48  }
    49  
    50  // Dispense returns the plugin given its name and type. This will also
    51  // configure the plugin. If there is an instance of an already running plugin,
    52  // this is used.
    53  func (s *SingletonLoader) Dispense(name, pluginType string, config *base.AgentConfig, logger log.Logger) (loader.PluginInstance, error) {
    54  	return s.getPlugin(false, name, pluginType, logger, config, nil)
    55  }
    56  
    57  // Reattach is used to reattach to a previously launched external plugin.
    58  func (s *SingletonLoader) Reattach(name, pluginType string, config *plugin.ReattachConfig) (loader.PluginInstance, error) {
    59  	return s.getPlugin(true, name, pluginType, nil, nil, config)
    60  }
    61  
    62  // getPlugin is a helper that either dispenses or reattaches to a plugin using
    63  // futures to ensure only a single instance is retrieved
    64  func (s *SingletonLoader) getPlugin(reattach bool, name, pluginType string, logger log.Logger,
    65  	nomadConfig *base.AgentConfig, config *plugin.ReattachConfig) (loader.PluginInstance, error) {
    66  
    67  	// Lock the instance map to prevent races
    68  	s.instanceLock.Lock()
    69  
    70  	// Check if there is a future already
    71  	id := loader.PluginID{Name: name, PluginType: pluginType}
    72  	f, ok := s.instances[id]
    73  
    74  	// Create the future and go get a plugin
    75  	if !ok {
    76  		f = newFuture()
    77  		s.instances[id] = f
    78  
    79  		if reattach {
    80  			go s.reattach(f, name, pluginType, config)
    81  		} else {
    82  			go s.dispense(f, name, pluginType, nomadConfig, logger)
    83  		}
    84  	}
    85  
    86  	// Unlock so that the created future can be shared
    87  	s.instanceLock.Unlock()
    88  
    89  	i, err := f.wait().result()
    90  	if err != nil {
    91  		s.clearFuture(id, f)
    92  		return nil, err
    93  	}
    94  
    95  	if i.Exited() {
    96  		s.clearFuture(id, f)
    97  		return nil, SingletonPluginExited
    98  	}
    99  
   100  	return i, nil
   101  }
   102  
   103  // dispense should be called in a go routine to not block and creates the
   104  // desired plugin, setting the results in the future.
   105  func (s *SingletonLoader) dispense(f *future, name, pluginType string, config *base.AgentConfig, logger log.Logger) {
   106  	i, err := s.loader.Dispense(name, pluginType, config, logger)
   107  	f.set(i, err)
   108  }
   109  
   110  // reattach should be called in a go routine to not block and reattaches to the
   111  // desired plugin, setting the results in the future.
   112  func (s *SingletonLoader) reattach(f *future, name, pluginType string, config *plugin.ReattachConfig) {
   113  	i, err := s.loader.Reattach(name, pluginType, config)
   114  	f.set(i, err)
   115  }
   116  
   117  // clearFuture clears the future from the instances map only if the futures
   118  // match. This prevents clearing the unintented instance.
   119  func (s *SingletonLoader) clearFuture(id loader.PluginID, f *future) {
   120  	s.instanceLock.Lock()
   121  	defer s.instanceLock.Unlock()
   122  	if f.equal(s.instances[id]) {
   123  		delete(s.instances, id)
   124  	}
   125  }