mirror of
https://github.com/kubernetes/client-go.git
synced 2026-06-10 03:07:20 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a60e03017 | ||
|
|
b063729e49 | ||
|
|
c94387a2ca | ||
|
|
37e11edd1a | ||
|
|
45223f1cdc | ||
|
|
e3bfc01275 | ||
|
|
5d151b2263 | ||
|
|
a2779a270f | ||
|
|
9a5f31f5c8 | ||
|
|
d50e3bcd66 | ||
|
|
537443fada | ||
|
|
7f8b49ad0a | ||
|
|
98bfbf9390 | ||
|
|
1770217076 | ||
|
|
3d75290917 |
10
Godeps/Godeps.json
generated
10
Godeps/Godeps.json
generated
@@ -264,7 +264,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto",
|
||||
"Rev": "60c769a6c586"
|
||||
"Rev": "bac4c82f6975"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/exp",
|
||||
@@ -340,7 +340,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/yaml.v2",
|
||||
"Rev": "v2.2.4"
|
||||
"Rev": "v2.2.8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "honnef.co/go/tools",
|
||||
@@ -348,11 +348,11 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api",
|
||||
"Rev": "v0.16.5"
|
||||
"Rev": "v0.16.13"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery",
|
||||
"Rev": "v0.16.5"
|
||||
"Rev": "v0.16.13"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/gengo",
|
||||
@@ -364,7 +364,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/kube-openapi",
|
||||
"Rev": "743ec37842bf"
|
||||
"Rev": "594e756bea31"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/utils",
|
||||
|
||||
10
go.mod
10
go.mod
@@ -21,13 +21,13 @@ require (
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.3.0
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||
google.golang.org/appengine v1.5.0 // indirect
|
||||
k8s.io/api v0.16.5
|
||||
k8s.io/apimachinery v0.16.5
|
||||
k8s.io/api v0.16.13
|
||||
k8s.io/apimachinery v0.16.13
|
||||
k8s.io/klog v1.0.0
|
||||
k8s.io/utils v0.0.0-20190801114015-581e00157fb1
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
@@ -35,6 +35,6 @@ require (
|
||||
|
||||
replace (
|
||||
golang.org/x/tools => golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7
|
||||
k8s.io/api => k8s.io/api v0.16.5
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.16.5
|
||||
k8s.io/api => k8s.io/api v0.16.13
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.16.13
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@@ -125,8 +125,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -192,19 +192,19 @@ 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.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/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=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.16.5/go.mod h1:6ijJb2BQAkGSn+8Z3173M0LXe2gBAH+i/JAJptd/rJo=
|
||||
k8s.io/apimachinery v0.16.5/go.mod h1:mhhO3hoLkWO+2eCvqjPtH2Ly92l9nJDwsswzWKpkN2w=
|
||||
k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs=
|
||||
k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ=
|
||||
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kube-openapi v0.0.0-20200410163147-594e756bea31 h1:PsbYeEz2x7ll6JYUzBEG+DT78910DDTlvn5Ma10F5/E=
|
||||
k8s.io/kube-openapi v0.0.0-20200410163147-594e756bea31/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE=
|
||||
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
|
||||
@@ -287,7 +287,7 @@ func (ts *azureTokenSource) refreshToken(token *azureToken) (*azureToken, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfigWithAPIVersion(env.ActiveDirectoryEndpoint, token.tenantID, nil)
|
||||
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, token.tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building the OAuth configuration for token refresh: %v", err)
|
||||
}
|
||||
@@ -344,7 +344,7 @@ func newAzureTokenSourceDeviceCode(environment azure.Environment, clientID strin
|
||||
}
|
||||
|
||||
func (ts *azureTokenSourceDeviceCode) Token() (*azureToken, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfigWithAPIVersion(ts.environment.ActiveDirectoryEndpoint, ts.tenantID, nil)
|
||||
oauthConfig, err := adal.NewOAuthConfig(ts.environment.ActiveDirectoryEndpoint, ts.tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building the OAuth configuration for device code authentication: %v", err)
|
||||
}
|
||||
|
||||
@@ -592,7 +592,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
|
||||
|
||||
7
tools/cache/reflector.go
vendored
7
tools/cache/reflector.go
vendored
@@ -269,6 +269,8 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
AllowWatchBookmarks: true,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -290,7 +292,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 {
|
||||
switch {
|
||||
case apierrs.IsResourceExpired(err):
|
||||
@@ -314,8 +316,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
|
||||
|
||||
6
tools/cache/reflector_test.go
vendored
6
tools/cache/reflector_test.go
vendored
@@ -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)
|
||||
}
|
||||
|
||||
7
tools/cache/thread_safe_store.go
vendored
7
tools/cache/thread_safe_store.go
vendored
@@ -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
92
tools/cache/thread_safe_store_test.go
vendored
Normal 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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user