github.com/tschmi5/nomad@v0.11.8/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 }