mirror of
https://github.com/kubernetes/client-go.git
synced 2025-09-02 23:55:18 +00:00
cache.ErrRequeue advertised itself as a way to requeue failures on a FIFO, but it suffers the same problems as AddIfNotPresent. If we do requeue an item at the end, we'll move the informer back in time. If we requeue at the beginning we'll simply wedge FIFO. We didn't find examples in the wild, but by removing the error type those impacted will get a compile error and get to decide what action is most appropriate for their failure. Most of the time, proceeding to the next item is best. Kubernetes-commit: 238c32a1d9b2c72d648193fa8642a53a2884975f
289 lines
7.1 KiB
Go
289 lines
7.1 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package cache
|
|
|
|
import (
|
|
"reflect"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// List returns a list of all the items.
|
|
// This function was moved here because it is not consistent with normal list semantics, but is used in unit testing.
|
|
func (f *FIFO) List() []interface{} {
|
|
f.lock.RLock()
|
|
defer f.lock.RUnlock()
|
|
list := make([]interface{}, 0, len(f.items))
|
|
for _, item := range f.items {
|
|
list = append(list, item)
|
|
}
|
|
return list
|
|
}
|
|
|
|
// ListKeys returns a list of all the keys of the objects currently
|
|
// in the FIFO.
|
|
// This function was moved here because it is not consistent with normal list semantics, but is used in unit testing.
|
|
func (f *FIFO) ListKeys() []string {
|
|
f.lock.RLock()
|
|
defer f.lock.RUnlock()
|
|
list := make([]string, 0, len(f.items))
|
|
for key := range f.items {
|
|
list = append(list, key)
|
|
}
|
|
return list
|
|
}
|
|
|
|
// Get returns the requested item, or sets exists=false.
|
|
// This function was moved here because it is not consistent with normal list semantics, but is used in unit testing.
|
|
func (f *FIFO) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
|
key, err := f.keyFunc(obj)
|
|
if err != nil {
|
|
return nil, false, KeyError{obj, err}
|
|
}
|
|
return f.GetByKey(key)
|
|
}
|
|
|
|
// GetByKey returns the requested item, or sets exists=false.
|
|
// This function was moved here because it is not consistent with normal list semantics, but is used in unit testing.
|
|
func (f *FIFO) GetByKey(key string) (item interface{}, exists bool, err error) {
|
|
f.lock.RLock()
|
|
defer f.lock.RUnlock()
|
|
item, exists = f.items[key]
|
|
return item, exists, nil
|
|
}
|
|
|
|
func testFifoObjectKeyFunc(obj interface{}) (string, error) {
|
|
return obj.(testFifoObject).name, nil
|
|
}
|
|
|
|
type testFifoObject struct {
|
|
name string
|
|
val interface{}
|
|
}
|
|
|
|
func mkFifoObj(name string, val interface{}) testFifoObject {
|
|
return testFifoObject{name: name, val: val}
|
|
}
|
|
|
|
func TestFIFO_basic(t *testing.T) {
|
|
f := NewFIFO(testFifoObjectKeyFunc)
|
|
const amount = 500
|
|
go func() {
|
|
for i := 0; i < amount; i++ {
|
|
f.Add(mkFifoObj(string([]rune{'a', rune(i)}), i+1))
|
|
}
|
|
}()
|
|
go func() {
|
|
for u := uint64(0); u < amount; u++ {
|
|
f.Add(mkFifoObj(string([]rune{'b', rune(u)}), u+1))
|
|
}
|
|
}()
|
|
|
|
lastInt := int(0)
|
|
lastUint := uint64(0)
|
|
for i := 0; i < amount*2; i++ {
|
|
switch obj := Pop(f).(testFifoObject).val.(type) {
|
|
case int:
|
|
if obj <= lastInt {
|
|
t.Errorf("got %v (int) out of order, last was %v", obj, lastInt)
|
|
}
|
|
lastInt = obj
|
|
case uint64:
|
|
if obj <= lastUint {
|
|
t.Errorf("got %v (uint) out of order, last was %v", obj, lastUint)
|
|
} else {
|
|
lastUint = obj
|
|
}
|
|
default:
|
|
t.Fatalf("unexpected type %#v", obj)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFIFO_addUpdate(t *testing.T) {
|
|
f := NewFIFO(testFifoObjectKeyFunc)
|
|
f.Add(mkFifoObj("foo", 10))
|
|
f.Update(mkFifoObj("foo", 15))
|
|
|
|
if e, a := []interface{}{mkFifoObj("foo", 15)}, f.List(); !reflect.DeepEqual(e, a) {
|
|
t.Errorf("Expected %+v, got %+v", e, a)
|
|
}
|
|
if e, a := []string{"foo"}, f.ListKeys(); !reflect.DeepEqual(e, a) {
|
|
t.Errorf("Expected %+v, got %+v", e, a)
|
|
}
|
|
|
|
got := make(chan testFifoObject, 2)
|
|
go func() {
|
|
for {
|
|
got <- Pop(f).(testFifoObject)
|
|
}
|
|
}()
|
|
|
|
first := <-got
|
|
if e, a := 15, first.val; e != a {
|
|
t.Errorf("Didn't get updated value (%v), got %v", e, a)
|
|
}
|
|
select {
|
|
case unexpected := <-got:
|
|
t.Errorf("Got second value %v", unexpected.val)
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
_, exists, _ := f.Get(mkFifoObj("foo", ""))
|
|
if exists {
|
|
t.Errorf("item did not get removed")
|
|
}
|
|
}
|
|
|
|
func TestFIFO_addReplace(t *testing.T) {
|
|
f := NewFIFO(testFifoObjectKeyFunc)
|
|
f.Add(mkFifoObj("foo", 10))
|
|
f.Replace([]interface{}{mkFifoObj("foo", 15)}, "15")
|
|
got := make(chan testFifoObject, 2)
|
|
go func() {
|
|
for {
|
|
got <- Pop(f).(testFifoObject)
|
|
}
|
|
}()
|
|
|
|
first := <-got
|
|
if e, a := 15, first.val; e != a {
|
|
t.Errorf("Didn't get updated value (%v), got %v", e, a)
|
|
}
|
|
select {
|
|
case unexpected := <-got:
|
|
t.Errorf("Got second value %v", unexpected.val)
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
_, exists, _ := f.Get(mkFifoObj("foo", ""))
|
|
if exists {
|
|
t.Errorf("item did not get removed")
|
|
}
|
|
}
|
|
|
|
func TestFIFO_detectLineJumpers(t *testing.T) {
|
|
f := NewFIFO(testFifoObjectKeyFunc)
|
|
|
|
f.Add(mkFifoObj("foo", 10))
|
|
f.Add(mkFifoObj("bar", 1))
|
|
f.Add(mkFifoObj("foo", 11))
|
|
f.Add(mkFifoObj("foo", 13))
|
|
f.Add(mkFifoObj("zab", 30))
|
|
|
|
if e, a := 13, Pop(f).(testFifoObject).val; a != e {
|
|
t.Fatalf("expected %d, got %d", e, a)
|
|
}
|
|
|
|
f.Add(mkFifoObj("foo", 14)) // ensure foo doesn't jump back in line
|
|
|
|
if e, a := 1, Pop(f).(testFifoObject).val; a != e {
|
|
t.Fatalf("expected %d, got %d", e, a)
|
|
}
|
|
|
|
if e, a := 30, Pop(f).(testFifoObject).val; a != e {
|
|
t.Fatalf("expected %d, got %d", e, a)
|
|
}
|
|
|
|
if e, a := 14, Pop(f).(testFifoObject).val; a != e {
|
|
t.Fatalf("expected %d, got %d", e, a)
|
|
}
|
|
}
|
|
|
|
func TestFIFO_HasSynced(t *testing.T) {
|
|
tests := []struct {
|
|
actions []func(f *FIFO)
|
|
expectedSynced bool
|
|
}{
|
|
{
|
|
actions: []func(f *FIFO){},
|
|
expectedSynced: false,
|
|
},
|
|
{
|
|
actions: []func(f *FIFO){
|
|
func(f *FIFO) { f.Add(mkFifoObj("a", 1)) },
|
|
},
|
|
expectedSynced: true,
|
|
},
|
|
{
|
|
actions: []func(f *FIFO){
|
|
func(f *FIFO) { f.Replace([]interface{}{}, "0") },
|
|
},
|
|
expectedSynced: true,
|
|
},
|
|
{
|
|
actions: []func(f *FIFO){
|
|
func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") },
|
|
},
|
|
expectedSynced: false,
|
|
},
|
|
{
|
|
actions: []func(f *FIFO){
|
|
func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") },
|
|
func(f *FIFO) { Pop(f) },
|
|
},
|
|
expectedSynced: false,
|
|
},
|
|
{
|
|
actions: []func(f *FIFO){
|
|
func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") },
|
|
func(f *FIFO) { Pop(f) },
|
|
func(f *FIFO) { Pop(f) },
|
|
},
|
|
expectedSynced: true,
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
f := NewFIFO(testFifoObjectKeyFunc)
|
|
|
|
for _, action := range test.actions {
|
|
action(f)
|
|
}
|
|
if e, a := test.expectedSynced, f.HasSynced(); a != e {
|
|
t.Errorf("test case %v failed, expected: %v , got %v", i, e, a)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestFIFO_PopShouldUnblockWhenClosed checks that any blocking Pop on an empty queue
|
|
// should unblock and return after Close is called.
|
|
func TestFIFO_PopShouldUnblockWhenClosed(t *testing.T) {
|
|
f := NewFIFO(testFifoObjectKeyFunc)
|
|
|
|
c := make(chan struct{})
|
|
const jobs = 10
|
|
for i := 0; i < jobs; i++ {
|
|
go func() {
|
|
f.Pop(func(obj interface{}, isInInitialList bool) error {
|
|
return nil
|
|
})
|
|
c <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
runtime.Gosched()
|
|
f.Close()
|
|
|
|
for i := 0; i < jobs; i++ {
|
|
select {
|
|
case <-c:
|
|
case <-time.After(500 * time.Millisecond):
|
|
t.Fatalf("timed out waiting for Pop to return after Close")
|
|
}
|
|
}
|
|
}
|