Skip to content

Commit a84f89a

Browse files
committed
fix leaking of matcher cache entries
Signed-off-by: Ethan Hunter <[email protected]>
1 parent cad5fa5 commit a84f89a

File tree

2 files changed

+123
-10
lines changed

2 files changed

+123
-10
lines changed

silence/silence.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ var ErrNotFound = errors.New("silence not found")
4949
// ErrInvalidState is returned if the state isn't valid.
5050
var ErrInvalidState = errors.New("invalid state")
5151

52-
type matcherCache map[*pb.Silence]labels.Matchers
52+
type matcherCache map[string]labels.Matchers
5353

5454
// Get retrieves the matchers for a given silence. If it is a missed cache
5555
// access, it compiles and adds the matchers of the requested silence to the
5656
// cache.
5757
func (c matcherCache) Get(s *pb.Silence) (labels.Matchers, error) {
58-
if m, ok := c[s]; ok {
58+
if m, ok := c[s.Id]; ok {
5959
return m, nil
6060
}
6161
return c.add(s)
@@ -88,7 +88,7 @@ func (c matcherCache) add(s *pb.Silence) (labels.Matchers, error) {
8888
ms[i] = matcher
8989
}
9090

91-
c[s] = ms
91+
c[s.Id] = ms
9292
return ms, nil
9393
}
9494

@@ -478,7 +478,7 @@ func (s *Silences) GC() (int, error) {
478478
}
479479
if !sil.ExpiresAt.After(now) {
480480
delete(s.st, id)
481-
delete(s.mc, sil.Silence)
481+
delete(s.mc, sil.Silence.Id)
482482
n++
483483
}
484484
}

silence/silence_test.go

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,16 @@ func TestSilencesGC(t *testing.T) {
9191
s.clock = clock.NewMock()
9292
now := s.nowUTC()
9393

94-
newSilence := func(exp time.Time) *pb.MeshSilence {
95-
return &pb.MeshSilence{ExpiresAt: exp}
94+
newSilence := func(id string, exp time.Time) *pb.MeshSilence {
95+
return &pb.MeshSilence{Silence: &pb.Silence{Id: id}, ExpiresAt: exp}
9696
}
9797
s.st = state{
98-
"1": newSilence(now),
99-
"2": newSilence(now.Add(-time.Second)),
100-
"3": newSilence(now.Add(time.Second)),
98+
"1": newSilence("1", now),
99+
"2": newSilence("2", now.Add(-time.Second)),
100+
"3": newSilence("3", now.Add(time.Second)),
101101
}
102102
want := state{
103-
"3": newSilence(now.Add(time.Second)),
103+
"3": newSilence("3", now.Add(time.Second)),
104104
}
105105

106106
n, err := s.GC()
@@ -109,6 +109,119 @@ func TestSilencesGC(t *testing.T) {
109109
require.Equal(t, want, s.st)
110110
}
111111

112+
func TestSilenceGCOverTime(t *testing.T) {
113+
type silenceEntry struct {
114+
s *pb.Silence
115+
expectPresentAfterGc bool
116+
}
117+
type testCase struct {
118+
initialState []silenceEntry
119+
updates []silenceEntry
120+
expectedGCCount int
121+
}
122+
123+
c := clock.NewMock()
124+
now := c.Now().UTC()
125+
126+
newSilence := func(id string, exp time.Time) *pb.Silence {
127+
return &pb.Silence{
128+
Id: id,
129+
StartsAt: now.Add(-time.Second),
130+
EndsAt: exp,
131+
Matchers: []*pb.Matcher{
132+
{Name: "foo", Type: pb.Matcher_REGEXP, Pattern: "bar"},
133+
}}
134+
}
135+
136+
cases := map[string]testCase{
137+
"gc does not clean active silences": {
138+
initialState: []silenceEntry{
139+
{s: newSilence("1", now), expectPresentAfterGc: false},
140+
{s: newSilence("2", now.Add(-time.Second)), expectPresentAfterGc: false},
141+
{s: newSilence("3", now.Add(time.Second)), expectPresentAfterGc: true},
142+
},
143+
},
144+
"silences added with Set are handled correctly": {
145+
initialState: []silenceEntry{
146+
{s: newSilence("1", now), expectPresentAfterGc: false},
147+
},
148+
updates: []silenceEntry{
149+
{s: newSilence("", now.Add(time.Second)), expectPresentAfterGc: true},
150+
{s: newSilence("", now.Add(-time.Second)), expectPresentAfterGc: false},
151+
},
152+
},
153+
"silence update does not leak state": {
154+
initialState: []silenceEntry{
155+
{s: newSilence("1", now), expectPresentAfterGc: false},
156+
},
157+
updates: []silenceEntry{
158+
{s: newSilence("1", now.Add(time.Second)), expectPresentAfterGc: true},
159+
},
160+
},
161+
}
162+
163+
for name, tc := range cases {
164+
t.Run(name, func(t *testing.T) {
165+
silences, err := New(Options{})
166+
silClock := clock.NewMock()
167+
silences.clock = silClock
168+
require.NoError(t, err)
169+
170+
expectedRemaining := []string{}
171+
expectedGCCount := 0
172+
for _, sil := range tc.initialState {
173+
silences.st[sil.s.Id] = &pb.MeshSilence{
174+
Silence: sil.s,
175+
ExpiresAt: sil.s.EndsAt,
176+
}
177+
if sil.expectPresentAfterGc {
178+
expectedRemaining = append(expectedRemaining, sil.s.Id)
179+
} else {
180+
expectedGCCount += 1
181+
}
182+
// simulate this silences being seen in a query
183+
silences.mc.Get(silences.st[sil.s.Id].Silence)
184+
}
185+
silClock.Add(-time.Second)
186+
for _, sil := range tc.updates {
187+
if sil.s.Id != "" {
188+
// we're replacing a silence which now will not get GC'd
189+
expectedGCCount -= 1
190+
}
191+
err := silences.Set(sil.s)
192+
require.NoError(t, err)
193+
if sil.expectPresentAfterGc {
194+
expectedRemaining = append(expectedRemaining, sil.s.Id)
195+
} else {
196+
expectedGCCount += 1
197+
}
198+
// simulate this silences being seen in a query
199+
silences.mc.Get(silences.st[sil.s.Id].Silence)
200+
}
201+
202+
silClock.Add(time.Second)
203+
204+
n, err := silences.GC()
205+
require.NoError(t, err)
206+
require.Equal(t, expectedGCCount, n)
207+
208+
for _, id := range expectedRemaining {
209+
foundSil, inState := silences.st[id]
210+
if !inState {
211+
require.Failf(t, "silence %s was missing from final state", id)
212+
}
213+
if _, ok := silences.mc[foundSil.Silence.Id]; !ok {
214+
require.Failf(t, "silence %s's matchers were missing from the matcherCache", id)
215+
}
216+
}
217+
require.Equalf(t, len(expectedRemaining), len(silences.st), "there are extra silences in the final state")
218+
require.Equalf(t, len(expectedRemaining), len(silences.mc), "there are extra entries in the matcher cache")
219+
220+
})
221+
}
222+
223+
}
224+
112225
func TestSilencesSnapshot(t *testing.T) {
113226
// Check whether storing and loading the snapshot is symmetric.
114227
now := clock.NewMock().Now().UTC()

0 commit comments

Comments
 (0)