Skip to content

Commit 4110d1d

Browse files
committed
WIP: Implementing TTL for cached datasource instances
1 parent 6965c31 commit 4110d1d

File tree

4 files changed

+77
-19
lines changed

4 files changed

+77
-19
lines changed

backend/instancemgmt/instance_manager.go

+27-19
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package instancemgmt
22

33
import (
44
"context"
5+
"fmt"
56
"reflect"
6-
"sync"
77
"time"
88

99
"github.com/prometheus/client_golang/prometheus"
1010
"github.com/prometheus/client_golang/prometheus/promauto"
1111

1212
"github.com/grafana/grafana-plugin-sdk-go/backend"
13+
gocache "github.com/patrickmn/go-cache"
1314
)
1415

1516
var (
@@ -19,6 +20,9 @@ var (
1920
Help: "The number of active plugin instances",
2021
})
2122
disposeTTL = 5 * time.Second
23+
24+
instanceTTL = 1 * time.Hour
25+
instanceCleanup = 2 * time.Hour
2226
)
2327

2428
// Instance is a marker interface for an instance.
@@ -76,18 +80,28 @@ func New(provider InstanceProvider) InstanceManager {
7680
if provider == nil {
7781
panic("provider cannot be nil")
7882
}
83+
cache := gocache.New(instanceTTL, instanceCleanup)
84+
cache.OnEvicted(func(_ string, i interface{}) {
85+
ci := i.(CachedInstance)
86+
if disposer, valid := ci.instance.(InstanceDisposer); valid {
87+
time.AfterFunc(disposeTTL, func() {
88+
disposer.Dispose()
89+
})
90+
}
91+
activeInstances.Dec()
92+
})
7993

8094
return &instanceManager{
8195
provider: provider,
82-
cache: sync.Map{},
96+
cache: cache,
8397
locker: newLocker(),
8498
}
8599
}
86100

87101
type instanceManager struct {
88102
locker *locker
89103
provider InstanceProvider
90-
cache sync.Map
104+
cache *gocache.Cache
91105
}
92106

93107
func (im *instanceManager) Get(ctx context.Context, pluginContext backend.PluginContext) (Instance, error) {
@@ -96,9 +110,10 @@ func (im *instanceManager) Get(ctx context.Context, pluginContext backend.Plugin
96110
return nil, err
97111
}
98112
// Double-checked locking for update/create criteria
99-
im.locker.RLock(cacheKey)
100-
item, ok := im.cache.Load(cacheKey)
101-
im.locker.RUnlock(cacheKey)
113+
strKey := fmt.Sprintf("%v", cacheKey)
114+
im.locker.RLock(strKey)
115+
item, ok := im.cache.Get(strKey)
116+
im.locker.RUnlock(strKey)
102117

103118
if ok {
104119
ci := item.(CachedInstance)
@@ -109,35 +124,28 @@ func (im *instanceManager) Get(ctx context.Context, pluginContext backend.Plugin
109124
}
110125
}
111126

112-
im.locker.Lock(cacheKey)
113-
defer im.locker.Unlock(cacheKey)
127+
im.locker.Lock(strKey)
128+
defer im.locker.Unlock(strKey)
114129

115-
if item, ok := im.cache.Load(cacheKey); ok {
130+
if item, ok := im.cache.Get(strKey); ok {
116131
ci := item.(CachedInstance)
117132
needsUpdate := im.provider.NeedsUpdate(ctx, pluginContext, ci)
118133

119134
if !needsUpdate {
120135
return ci.instance, nil
121136
}
122137

123-
if disposer, valid := ci.instance.(InstanceDisposer); valid {
124-
time.AfterFunc(disposeTTL, func() {
125-
disposer.Dispose()
126-
activeInstances.Dec()
127-
})
128-
} else {
129-
activeInstances.Dec()
130-
}
138+
im.cache.Delete(strKey)
131139
}
132140

133141
instance, err := im.provider.NewInstance(ctx, pluginContext)
134142
if err != nil {
135143
return nil, err
136144
}
137-
im.cache.Store(cacheKey, CachedInstance{
145+
im.cache.Set(strKey, CachedInstance{
138146
PluginContext: pluginContext,
139147
instance: instance,
140-
})
148+
}, gocache.DefaultExpiration)
141149
activeInstances.Inc()
142150

143151
return instance, nil

backend/instancemgmt/instance_manager_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,53 @@ func TestInstanceManager(t *testing.T) {
7070
})
7171
}
7272

73+
func TestInstanceManagerExpiration(t *testing.T) {
74+
ctx := context.Background()
75+
pCtx := backend.PluginContext{
76+
OrgID: 1,
77+
AppInstanceSettings: &backend.AppInstanceSettings{
78+
Updated: time.Now(),
79+
},
80+
}
81+
82+
origInstanceTTL := instanceTTL
83+
instanceTTL = time.Millisecond
84+
origInstanceCleanup := instanceCleanup
85+
instanceCleanup = 2 * time.Millisecond
86+
t.Cleanup(func() {
87+
instanceTTL = origInstanceTTL
88+
instanceCleanup = origInstanceCleanup
89+
})
90+
91+
tip := &testInstanceProvider{}
92+
im := New(tip)
93+
94+
instance, err := im.Get(ctx, pCtx)
95+
require.NoError(t, err)
96+
require.NotNil(t, instance)
97+
require.Equal(t, pCtx.OrgID, instance.(*testInstance).orgID)
98+
require.Equal(t, pCtx.AppInstanceSettings.Updated, instance.(*testInstance).updated)
99+
100+
t.Run("After expiration", func(t *testing.T) {
101+
instance.(*testInstance).wg.Wait()
102+
require.True(t, instance.(*testInstance).disposed.Load())
103+
require.Equal(t, int64(1), instance.(*testInstance).disposedTimes.Load())
104+
105+
newInstance, err := im.Get(ctx, pCtx)
106+
107+
t.Run("New instance should be created", func(t *testing.T) {
108+
require.NoError(t, err)
109+
require.NotNil(t, newInstance)
110+
require.Equal(t, pCtx.OrgID, newInstance.(*testInstance).orgID)
111+
require.Equal(t, pCtx.AppInstanceSettings.Updated, newInstance.(*testInstance).updated)
112+
})
113+
114+
t.Run("New instance should not be the same as old instance", func(t *testing.T) {
115+
require.NotSame(t, instance, newInstance)
116+
})
117+
})
118+
}
119+
73120
func TestInstanceManagerConcurrency(t *testing.T) {
74121
t.Run("Check possible race condition issues when initially creating instance", func(t *testing.T) {
75122
ctx := context.Background()

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ require (
9696
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
9797
github.com/oklog/run v1.0.0 // indirect
9898
github.com/oklog/ulid v1.3.1 // indirect
99+
github.com/patrickmn/go-cache v2.1.0+incompatible
99100
github.com/perimeterx/marshmallow v1.1.5 // indirect
100101
github.com/pierrec/lz4/v4 v4.1.22 // indirect
101102
github.com/pmezard/go-difflib v1.0.0 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
180180
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
181181
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
182182
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
183+
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
184+
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
183185
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
184186
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
185187
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=

0 commit comments

Comments
 (0)