Skip to content

Commit f898114

Browse files
author
Kubernetes Submit Queue
authored
Merge pull request kubernetes#61195 from grantr/use-race-free-fake-watcher
Automatic merge from submit-queue (batch tested with PRs 61195, 61479). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Use RaceFreeFakeWatcher in ObjectTracker to fix racy watch panics **What this PR does / why we need it**: The `FakeWatcher` added to `ObjectTracker` in kubernetes#57504 allows sends on the result channel after it's closed; for example calling `Stop()` then `Add(obj)` will cause a panic. In my experience this has led to flaky tests when informers and controllers are running. Replacing `FakeWatcher` with `RaceFreeFakeWatcher` fixes the problem, since `RaceFreeFakeWatcher` ignores additional events that occur after the watcher is stopped. It also panics instead of blocking when the result channel is full, which seems like a more useful behavior in tests than blocking. I removed the `FakeWatchBufferSize` constant since `RaceFreeFakeWatcher` doesn't take a buffer size argument anymore. This seems fine since the `DefaultChanSize` constant is close to the `FakeWatchBufferSize` value (100 vs 128). **Special notes for your reviewer**: I can provide a minimal repro of a flaky test caused by the earlier behavior if necessary. **Release note**: ```release-note Fix racy panics when using fake watches with ObjectTracker ```
2 parents 06e3fef + b84ad88 commit f898114

File tree

2 files changed

+41
-16
lines changed

2 files changed

+41
-16
lines changed

staging/src/k8s.io/client-go/testing/fixture.go

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ import (
2929
restclient "k8s.io/client-go/rest"
3030
)
3131

32-
// FakeWatchBufferSize is the max num of watch event can be buffered in the
33-
// watch channel. Note that when watch event overflows or exceed this buffer
34-
// size, manipulations via fake client may be blocked.
35-
const FakeWatchBufferSize = 128
36-
3732
// ObjectTracker keeps track of objects. It is intended to be used to
3833
// fake calls to a server by returning objects based on their kind,
3934
// namespace and name.
@@ -142,12 +137,11 @@ type tracker struct {
142137
lock sync.RWMutex
143138
objects map[schema.GroupVersionResource][]runtime.Object
144139
// The value type of watchers is a map of which the key is either a namespace or
145-
// all/non namespace aka "" and its value is list of fake watchers. Each of
146-
// fake watcher holds a buffered channel of size "FakeWatchBufferSize" which
147-
// is default to 128. Manipulations on resources will broadcast the notification
148-
// events into the watchers' channel and note that too many unhandled event may
149-
// potentially block the tracker.
150-
watchers map[schema.GroupVersionResource]map[string][]*watch.FakeWatcher
140+
// all/non namespace aka "" and its value is list of fake watchers.
141+
// Manipulations on resources will broadcast the notification events into the
142+
// watchers' channel. Note that too many unhandled events (currently 100,
143+
// see apimachinery/pkg/watch.DefaultChanSize) will cause a panic.
144+
watchers map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher
151145
}
152146

153147
var _ ObjectTracker = &tracker{}
@@ -159,7 +153,7 @@ func NewObjectTracker(scheme ObjectScheme, decoder runtime.Decoder) ObjectTracke
159153
scheme: scheme,
160154
decoder: decoder,
161155
objects: make(map[schema.GroupVersionResource][]runtime.Object),
162-
watchers: make(map[schema.GroupVersionResource]map[string][]*watch.FakeWatcher),
156+
watchers: make(map[schema.GroupVersionResource]map[string][]*watch.RaceFreeFakeWatcher),
163157
}
164158
}
165159

@@ -206,10 +200,10 @@ func (t *tracker) Watch(gvr schema.GroupVersionResource, ns string) (watch.Inter
206200
t.lock.Lock()
207201
defer t.lock.Unlock()
208202

209-
fakewatcher := watch.NewFakeWithChanSize(FakeWatchBufferSize, true)
203+
fakewatcher := watch.NewRaceFreeFake()
210204

211205
if _, exists := t.watchers[gvr]; !exists {
212-
t.watchers[gvr] = make(map[string][]*watch.FakeWatcher)
206+
t.watchers[gvr] = make(map[string][]*watch.RaceFreeFakeWatcher)
213207
}
214208
t.watchers[gvr][ns] = append(t.watchers[gvr][ns], fakewatcher)
215209
return fakewatcher, nil
@@ -293,8 +287,8 @@ func (t *tracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns
293287
return t.add(gvr, obj, ns, true)
294288
}
295289

296-
func (t *tracker) getWatches(gvr schema.GroupVersionResource, ns string) []*watch.FakeWatcher {
297-
watches := []*watch.FakeWatcher{}
290+
func (t *tracker) getWatches(gvr schema.GroupVersionResource, ns string) []*watch.RaceFreeFakeWatcher {
291+
watches := []*watch.RaceFreeFakeWatcher{}
298292
if t.watchers[gvr] != nil {
299293
if w := t.watchers[gvr][ns]; w != nil {
300294
watches = append(watches, w...)

staging/src/k8s.io/client-go/testing/fixture_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,34 @@ func TestWatchCallMultipleInvocation(t *testing.T) {
190190
}
191191
wg.Wait()
192192
}
193+
194+
func TestWatchAddAfterStop(t *testing.T) {
195+
testResource := schema.GroupVersionResource{Group: "", Version: "test_version", Resource: "test_kind"}
196+
testObj := getArbitraryResource(testResource, "test_name", "test_namespace")
197+
accessor, err := meta.Accessor(testObj)
198+
if err != nil {
199+
t.Fatalf("unexpected error: %v", err)
200+
}
201+
202+
ns := accessor.GetNamespace()
203+
scheme := runtime.NewScheme()
204+
codecs := serializer.NewCodecFactory(scheme)
205+
o := NewObjectTracker(scheme, codecs.UniversalDecoder())
206+
watch, err := o.Watch(testResource, ns)
207+
if err != nil {
208+
t.Errorf("watch creation failed: %v", err)
209+
}
210+
211+
// When the watch is stopped it should ignore later events without panicking.
212+
defer func() {
213+
if r := recover(); r != nil {
214+
t.Errorf("Watch panicked when it should have ignored create after stop: %v", r)
215+
}
216+
}()
217+
218+
watch.Stop()
219+
err = o.Create(testResource, testObj, ns)
220+
if err != nil {
221+
t.Errorf("test resource creation failed: %v", err)
222+
}
223+
}

0 commit comments

Comments
 (0)