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