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