1package instancemgmt 2 3import ( 4 "reflect" 5 "sync" 6 7 "github.com/grafana/grafana-plugin-sdk-go/backend" 8) 9 10// Instance is a marker interface for an instance. 11type Instance interface{} 12 13// InstanceDisposer is implemented by an Instance that has a Dispose method, 14// which defines that the instance is disposable. 15// 16// InstanceManager will call the Dispose method before an Instance is replaced 17// with a new Instance. This allows an Instance to clean up resources in use, 18// if any. 19type InstanceDisposer interface { 20 Dispose() 21} 22 23// InstanceCallbackFunc defines the callback function of the InstanceManager.Do method. 24// The argument provided will of type Instance. 25type InstanceCallbackFunc interface{} 26 27// InstanceManager manages the lifecycle of instances. 28type InstanceManager interface { 29 // Get returns an Instance. 30 // 31 // If Instance is cached and not updated it's returned. If Instance is not cached or 32 // updated, a new Instance is created and cached before returned. 33 Get(pluginContext backend.PluginContext) (Instance, error) 34 35 // Do provides an Instance as argument to fn. 36 // 37 // If Instance is cached and not updated provides as argument to fn. If Instance is not cached or 38 // updated, a new Instance is created and cached before provided as argument to fn. 39 Do(pluginContext backend.PluginContext, fn InstanceCallbackFunc) error 40} 41 42// CachedInstance a cached Instance. 43type CachedInstance struct { 44 PluginContext backend.PluginContext 45 instance Instance 46} 47 48// InstanceProvider defines an instance provider, providing instances. 49type InstanceProvider interface { 50 // GetKey returns a cache key to be used for caching an Instance. 51 GetKey(pluginContext backend.PluginContext) (interface{}, error) 52 53 // NeedsUpdate returns whether a cached Instance have been updated. 54 NeedsUpdate(pluginContext backend.PluginContext, cachedInstance CachedInstance) bool 55 56 // NewInstance creates a new Instance. 57 NewInstance(pluginContext backend.PluginContext) (Instance, error) 58} 59 60// New create a new instance manager. 61func New(provider InstanceProvider) InstanceManager { 62 if provider == nil { 63 panic("provider cannot be nil") 64 } 65 66 return &instanceManager{ 67 provider: provider, 68 cache: sync.Map{}, 69 locker: newLocker(), 70 } 71} 72 73type instanceManager struct { 74 locker *locker 75 provider InstanceProvider 76 cache sync.Map 77} 78 79func (im *instanceManager) Get(pluginContext backend.PluginContext) (Instance, error) { 80 cacheKey, err := im.provider.GetKey(pluginContext) 81 if err != nil { 82 return nil, err 83 } 84 // Double-checked locking for update/create criteria 85 im.locker.RLock(cacheKey) 86 item, ok := im.cache.Load(cacheKey) 87 im.locker.RUnlock(cacheKey) 88 89 if ok { 90 ci := item.(CachedInstance) 91 needsUpdate := im.provider.NeedsUpdate(pluginContext, ci) 92 93 if !needsUpdate { 94 return ci.instance, nil 95 } 96 } 97 98 im.locker.Lock(cacheKey) 99 defer im.locker.Unlock(cacheKey) 100 101 if item, ok := im.cache.Load(cacheKey); ok { 102 ci := item.(CachedInstance) 103 needsUpdate := im.provider.NeedsUpdate(pluginContext, ci) 104 105 if !needsUpdate { 106 return ci.instance, nil 107 } 108 109 if disposer, valid := ci.instance.(InstanceDisposer); valid { 110 disposer.Dispose() 111 } 112 } 113 114 instance, err := im.provider.NewInstance(pluginContext) 115 if err != nil { 116 return nil, err 117 } 118 im.cache.Store(cacheKey, CachedInstance{ 119 PluginContext: pluginContext, 120 instance: instance, 121 }) 122 123 return instance, nil 124} 125 126func (im *instanceManager) Do(pluginContext backend.PluginContext, fn InstanceCallbackFunc) error { 127 if fn == nil { 128 panic("fn cannot be nil") 129 } 130 131 instance, err := im.Get(pluginContext) 132 if err != nil { 133 return err 134 } 135 136 callInstanceHandlerFunc(fn, instance) 137 return nil 138} 139 140func callInstanceHandlerFunc(fn InstanceCallbackFunc, instance interface{}) { 141 var params = []reflect.Value{} 142 params = append(params, reflect.ValueOf(instance)) 143 reflect.ValueOf(fn).Call(params) 144} 145