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