Compare commits

..

7 Commits

Author SHA1 Message Date
Kubernetes Publisher
28e1f35180 Update dependencies to v0.15.12 tag 2020-05-07 00:30:30 +00:00
Kubernetes Publisher
75e09fce8f Merge pull request #89978 from liggitt/relist-timeout-1.15
Manual cherry pick of #89652: Fix client watch reestablishment handling of client-side timeouts

Kubernetes-commit: 7e52f61f4bc80eb99cd8ef351c435cc6799a0a6e
2020-04-10 02:30:15 +00:00
Jordan Liggitt
2b2ae3c6ee Fix client watch reestablishment handling of client-side timeouts
Kubernetes-commit: 0c829a9093e52928fb932c241fb4cc6fef3cc759
2020-03-30 10:36:01 -04:00
Kubernetes Publisher
076fbc5c36 Merge pull request #88005 from patrickshan/automated-cherry-pick-of-#84970-upstream-release-1.15
Automated cherry pick of #84970: - Delete backing string set from a threadSafeMap index when the string set length reaches 0

Kubernetes-commit: 89a7468341d588301c6c8be360ed2abb871009e1
2020-02-28 04:33:04 +00:00
Kubernetes Publisher
7f576abe0e Merge pull request #88082 from liggitt/automated-cherry-pick-of-#88079-upstream-release-1.15
Automated cherry pick of #88079: Set up connection onClose prior to adding to connection map

Kubernetes-commit: f5a527f6260e2a90c6445daf2714db92aeb026b9
2020-02-20 20:56:42 -08:00
Jordan Liggitt
99258cdbd0 Set up connection onClose prior to adding to connection map
Kubernetes-commit: 102bfa547ee5ff9646790893a5cd65b4ffd7ece6
2020-02-12 11:14:22 -05:00
Pete de Zwart
e21b6cb636 - Delete backing string set from a threadSafeMap index when the string set length reaches 0.
- Added thread_safe_store_test exercising new index backing string set delete at 0 functionality.

- TestThreadSafeStoreDeleteRemovesEmptySetsFromIndex logic nesting inverted.

- Added test case for usage of an index where post element delete there is non-zero count of elements and expect the set to still exist.

- Fixed date.

- Fixed awprice nits.

- Fix bazel.

Kubernetes-commit: b3ac469e2d77182fd636f437bfffc45d428e194e
2019-11-08 16:57:06 +11:00
11 changed files with 190 additions and 21 deletions

4
Godeps/Godeps.json generated
View File

@@ -188,11 +188,11 @@
},
{
"ImportPath": "k8s.io/api",
"Rev": "v0.15.10"
"Rev": "v0.15.12"
},
{
"ImportPath": "k8s.io/apimachinery",
"Rev": "v0.15.10"
"Rev": "v0.15.12"
},
{
"ImportPath": "k8s.io/klog",

8
go.mod
View File

@@ -26,8 +26,8 @@ require (
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d
google.golang.org/appengine v1.5.0 // indirect
k8s.io/api v0.15.10
k8s.io/apimachinery v0.15.10
k8s.io/api v0.15.12
k8s.io/apimachinery v0.15.12
k8s.io/klog v0.3.1
k8s.io/utils v0.0.0-20190221042446-c2654d5206da
sigs.k8s.io/yaml v1.1.0
@@ -37,6 +37,6 @@ replace (
golang.org/x/sync => golang.org/x/sync v0.0.0-20181108010431-42b317875d0f
golang.org/x/sys => golang.org/x/sys v0.0.0-20190209173611-3b5209105503
golang.org/x/tools => golang.org/x/tools v0.0.0-20190313210603-aa82965741a9
k8s.io/api => k8s.io/api v0.15.10
k8s.io/apimachinery => k8s.io/apimachinery v0.15.10
k8s.io/api => k8s.io/api v0.15.12
k8s.io/apimachinery => k8s.io/apimachinery v0.15.12
)

4
go.sum
View File

@@ -93,8 +93,8 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.15.10/go.mod h1:PffiEKNf0aFiv2naEYSGTFHIGA9V8Qwt22DZIAokOzQ=
k8s.io/apimachinery v0.15.10/go.mod h1:ZRw+v83FjgEqlzqaBkxL3XB21MSLYdzjsY9Bgxclhdw=
k8s.io/api v0.15.12/go.mod h1:S1SvCPVhZhYj/dWFUo86dk0Ej/NKuoGuJFhAJiDLYEI=
k8s.io/apimachinery v0.15.12/go.mod h1:ZRw+v83FjgEqlzqaBkxL3XB21MSLYdzjsY9Bgxclhdw=
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI=

View File

@@ -582,7 +582,7 @@ func (r *Request) WatchWithSpecificDecoders(wrapperDecoderFn func(io.ReadCloser)
if err != nil {
// The watch stream mechanism handles many common partial data errors, so closed
// connections can be retried in many cases.
if net.IsProbableEOF(err) {
if net.IsProbableEOF(err) || net.IsTimeout(err) {
return watch.NewEmptyWatch(), nil
}
return nil, err

View File

@@ -272,6 +272,8 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
AllowWatchBookmarks: false,
}
// start the clock before sending the request, since some proxies won't flush headers until after the first watch event is sent
start := r.clock.Now()
w, err := r.listerWatcher.Watch(options)
if err != nil {
switch err {
@@ -297,7 +299,7 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
return nil
}
if err := r.watchHandler(w, &resourceVersion, resyncerrc, stopCh); err != nil {
if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {
if err != errorStopRequested {
klog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedType, err)
}
@@ -316,8 +318,7 @@ func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) err
}
// watchHandler watches w and keeps *resourceVersion up to date.
func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
start := r.clock.Now()
func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
eventCount := 0
// Stopping the watcher should be idempotent and if we return from this function there's no way

View File

@@ -132,7 +132,7 @@ func TestReflectorWatchHandlerError(t *testing.T) {
fw.Stop()
}()
var resumeRV string
err := g.watchHandler(fw, &resumeRV, nevererrc, wait.NeverStop)
err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, wait.NeverStop)
if err == nil {
t.Errorf("unexpected non-error")
}
@@ -152,7 +152,7 @@ func TestReflectorWatchHandler(t *testing.T) {
fw.Stop()
}()
var resumeRV string
err := g.watchHandler(fw, &resumeRV, nevererrc, wait.NeverStop)
err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, wait.NeverStop)
if err != nil {
t.Errorf("unexpected error %v", err)
}
@@ -201,7 +201,7 @@ func TestReflectorStopWatch(t *testing.T) {
var resumeRV string
stopWatch := make(chan struct{}, 1)
stopWatch <- struct{}{}
err := g.watchHandler(fw, &resumeRV, nevererrc, stopWatch)
err := g.watchHandler(time.Now(), fw, &resumeRV, nevererrc, stopWatch)
if err != errorStopRequested {
t.Errorf("expected stop error, got %q", err)
}

View File

@@ -292,6 +292,13 @@ func (c *threadSafeMap) deleteFromIndices(obj interface{}, key string) {
set := index[indexValue]
if set != nil {
set.Delete(key)
// If we don't delete the set when zero, indices with high cardinality
// short lived resources can cause memory to increase over time from
// unused empty sets. See `kubernetes/kubernetes/issues/84959`.
if len(set) == 0 {
delete(index, indexValue)
}
}
}
}

92
tools/cache/thread_safe_store_test.go vendored Normal file
View File

@@ -0,0 +1,92 @@
/*
Copyright 2019 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 (
"testing"
)
func TestThreadSafeStoreDeleteRemovesEmptySetsFromIndex(t *testing.T) {
testIndexer := "testIndexer"
indexers := Indexers{
testIndexer: func(obj interface{}) (strings []string, e error) {
indexes := []string{obj.(string)}
return indexes, nil
},
}
indices := Indices{}
store := NewThreadSafeStore(indexers, indices).(*threadSafeMap)
testKey := "testKey"
store.Add(testKey, testKey)
// Assumption check, there should be a set for the `testKey` with one element in the added index
set := store.indices[testIndexer][testKey]
if len(set) != 1 {
t.Errorf("Initial assumption of index backing string set having 1 element failed. Actual elements: %d", len(set))
return
}
store.Delete(testKey)
set, present := store.indices[testIndexer][testKey]
if present {
t.Errorf("Index backing string set not deleted from index. Set length: %d", len(set))
}
}
func TestThreadSafeStoreAddKeepsNonEmptySetPostDeleteFromIndex(t *testing.T) {
testIndexer := "testIndexer"
testIndex := "testIndex"
indexers := Indexers{
testIndexer: func(obj interface{}) (strings []string, e error) {
indexes := []string{testIndex}
return indexes, nil
},
}
indices := Indices{}
store := NewThreadSafeStore(indexers, indices).(*threadSafeMap)
store.Add("retain", "retain")
store.Add("delete", "delete")
// Assumption check, there should be a set for the `testIndex` with two elements
set := store.indices[testIndexer][testIndex]
if len(set) != 2 {
t.Errorf("Initial assumption of index backing string set having 2 elements failed. Actual elements: %d", len(set))
return
}
store.Delete("delete")
set, present := store.indices[testIndexer][testIndex]
if !present {
t.Errorf("Index backing string set erroneously deleted from index.")
return
}
if len(set) != 1 {
t.Errorf("Index backing string set has incorrect length, expect 1. Set length: %d", len(set))
}
}

View File

@@ -120,7 +120,7 @@ func (rw *RetryWatcher) doReceive() (bool, time.Duration) {
default:
msg := "Watch failed: %v"
if net.IsProbableEOF(err) {
if net.IsProbableEOF(err) || net.IsTimeout(err) {
klog.V(5).Infof(msg, err)
// Retry
return false, 0

View File

@@ -77,11 +77,6 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.
closable := &closableConn{Conn: conn}
// Start tracking the connection
d.mu.Lock()
d.conns[closable] = struct{}{}
d.mu.Unlock()
// When the connection is closed, remove it from the map. This will
// be no-op if the connection isn't in the map, e.g. if CloseAll()
// is called.
@@ -91,6 +86,11 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.
d.mu.Unlock()
}
// Start tracking the connection
d.mu.Lock()
d.conns[closable] = struct{}{}
d.mu.Unlock()
return closable, nil
}

View File

@@ -19,6 +19,8 @@ package connrotation
import (
"context"
"net"
"sync"
"sync/atomic"
"testing"
"time"
)
@@ -50,6 +52,73 @@ func TestCloseAll(t *testing.T) {
}
}
// TestCloseAllRace ensures CloseAll works with connections being simultaneously dialed
func TestCloseAllRace(t *testing.T) {
conns := int64(0)
dialer := NewDialer(func(ctx context.Context, network, address string) (net.Conn, error) {
return closeOnlyConn{onClose: func() { atomic.AddInt64(&conns, -1) }}, nil
})
done := make(chan struct{})
wg := &sync.WaitGroup{}
// Close all as fast as we can
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-done:
return
default:
dialer.CloseAll()
}
}
}()
// Dial as fast as we can
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-done:
return
default:
if _, err := dialer.Dial("", ""); err != nil {
t.Error(err)
return
}
atomic.AddInt64(&conns, 1)
}
}
}()
// Soak to ensure no races
time.Sleep(time.Second)
// Signal completion
close(done)
// Wait for goroutines
wg.Wait()
// Ensure CloseAll ran after all dials
dialer.CloseAll()
// Expect all connections to close within 5 seconds
for start := time.Now(); time.Now().Sub(start) < 5*time.Second; time.Sleep(10 * time.Millisecond) {
// Ensure all connections were closed
if c := atomic.LoadInt64(&conns); c == 0 {
break
} else {
t.Logf("got %d open connections, want 0, will retry", c)
}
}
// Ensure all connections were closed
if c := atomic.LoadInt64(&conns); c != 0 {
t.Fatalf("got %d open connections, want 0", c)
}
}
type closeOnlyConn struct {
net.Conn
onClose func()