mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-08 11:38:15 +00:00
Fix memory leak in endpointSliceTracker
endpointSliceTracker creates a set of resource versions for each service, the resource versions in the set could be deleted when endpointslices are deleted, but the set and its key in the map is never deleted, leading to memory leak. This patch deletes the set if the service is deleted, and stops initializing an empty set when "read-only" methods "Has" and "Stale" are called.
This commit is contained in:
parent
865cbf0bdf
commit
087682584d
@ -66,6 +66,7 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/rand:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/rand:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
|
@ -307,6 +307,7 @@ func (c *Controller) syncService(key string) error {
|
|||||||
if apierrors.IsNotFound(err) {
|
if apierrors.IsNotFound(err) {
|
||||||
c.triggerTimeTracker.DeleteService(namespace, name)
|
c.triggerTimeTracker.DeleteService(namespace, name)
|
||||||
c.reconciler.deleteService(namespace, name)
|
c.reconciler.deleteService(namespace, name)
|
||||||
|
c.endpointSliceTracker.DeleteService(namespace, name)
|
||||||
// The service has been deleted, return nil so that it won't be retried.
|
// The service has been deleted, return nil so that it won't be retried.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,11 @@ func (est *endpointSliceTracker) Has(endpointSlice *discovery.EndpointSlice) boo
|
|||||||
est.lock.Lock()
|
est.lock.Lock()
|
||||||
defer est.lock.Unlock()
|
defer est.lock.Unlock()
|
||||||
|
|
||||||
rrv := est.relatedResourceVersions(endpointSlice)
|
rrv, ok := est.relatedResourceVersions(endpointSlice)
|
||||||
_, ok := rrv[endpointSlice.Name]
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok = rrv[endpointSlice.Name]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +66,10 @@ func (est *endpointSliceTracker) Stale(endpointSlice *discovery.EndpointSlice) b
|
|||||||
est.lock.Lock()
|
est.lock.Lock()
|
||||||
defer est.lock.Unlock()
|
defer est.lock.Unlock()
|
||||||
|
|
||||||
rrv := est.relatedResourceVersions(endpointSlice)
|
rrv, ok := est.relatedResourceVersions(endpointSlice)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return rrv[endpointSlice.Name] != endpointSlice.ResourceVersion
|
return rrv[endpointSlice.Name] != endpointSlice.ResourceVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,33 +79,42 @@ func (est *endpointSliceTracker) Update(endpointSlice *discovery.EndpointSlice)
|
|||||||
est.lock.Lock()
|
est.lock.Lock()
|
||||||
defer est.lock.Unlock()
|
defer est.lock.Unlock()
|
||||||
|
|
||||||
rrv := est.relatedResourceVersions(endpointSlice)
|
rrv, ok := est.relatedResourceVersions(endpointSlice)
|
||||||
|
if !ok {
|
||||||
|
rrv = endpointSliceResourceVersions{}
|
||||||
|
est.resourceVersionsByService[getServiceNN(endpointSlice)] = rrv
|
||||||
|
}
|
||||||
rrv[endpointSlice.Name] = endpointSlice.ResourceVersion
|
rrv[endpointSlice.Name] = endpointSlice.ResourceVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteService removes the set of resource versions tracked for the Service.
|
||||||
|
func (est *endpointSliceTracker) DeleteService(namespace, name string) {
|
||||||
|
est.lock.Lock()
|
||||||
|
defer est.lock.Unlock()
|
||||||
|
|
||||||
|
serviceNN := types.NamespacedName{Name: name, Namespace: namespace}
|
||||||
|
delete(est.resourceVersionsByService, serviceNN)
|
||||||
|
}
|
||||||
|
|
||||||
// Delete removes the resource version in this endpointSliceTracker for the
|
// Delete removes the resource version in this endpointSliceTracker for the
|
||||||
// provided EndpointSlice.
|
// provided EndpointSlice.
|
||||||
func (est *endpointSliceTracker) Delete(endpointSlice *discovery.EndpointSlice) {
|
func (est *endpointSliceTracker) Delete(endpointSlice *discovery.EndpointSlice) {
|
||||||
est.lock.Lock()
|
est.lock.Lock()
|
||||||
defer est.lock.Unlock()
|
defer est.lock.Unlock()
|
||||||
|
|
||||||
rrv := est.relatedResourceVersions(endpointSlice)
|
rrv, ok := est.relatedResourceVersions(endpointSlice)
|
||||||
|
if ok {
|
||||||
delete(rrv, endpointSlice.Name)
|
delete(rrv, endpointSlice.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// relatedResourceVersions returns the set of resource versions tracked for the
|
// relatedResourceVersions returns the set of resource versions tracked for the
|
||||||
// Service corresponding to the provided EndpointSlice. If no resource versions
|
// Service corresponding to the provided EndpointSlice, and a bool to indicate
|
||||||
// are currently tracked for this service, an empty set is initialized.
|
// if it exists.
|
||||||
func (est *endpointSliceTracker) relatedResourceVersions(endpointSlice *discovery.EndpointSlice) endpointSliceResourceVersions {
|
func (est *endpointSliceTracker) relatedResourceVersions(endpointSlice *discovery.EndpointSlice) (endpointSliceResourceVersions, bool) {
|
||||||
serviceNN := getServiceNN(endpointSlice)
|
serviceNN := getServiceNN(endpointSlice)
|
||||||
vers, ok := est.resourceVersionsByService[serviceNN]
|
vers, ok := est.resourceVersionsByService[serviceNN]
|
||||||
|
return vers, ok
|
||||||
if !ok {
|
|
||||||
vers = endpointSliceResourceVersions{}
|
|
||||||
est.resourceVersionsByService[serviceNN] = vers
|
|
||||||
}
|
|
||||||
|
|
||||||
return vers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getServiceNN returns a namespaced name for the Service corresponding to the
|
// getServiceNN returns a namespaced name for the Service corresponding to the
|
||||||
|
@ -19,8 +19,11 @@ package endpointslice
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
discovery "k8s.io/api/discovery/v1beta1"
|
discovery "k8s.io/api/discovery/v1beta1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEndpointSliceTrackerUpdate(t *testing.T) {
|
func TestEndpointSliceTrackerUpdate(t *testing.T) {
|
||||||
@ -47,30 +50,51 @@ func TestEndpointSliceTrackerUpdate(t *testing.T) {
|
|||||||
checksParam *discovery.EndpointSlice
|
checksParam *discovery.EndpointSlice
|
||||||
expectHas bool
|
expectHas bool
|
||||||
expectStale bool
|
expectStale bool
|
||||||
|
expectResourceVersionsByService map[types.NamespacedName]endpointSliceResourceVersions
|
||||||
}{
|
}{
|
||||||
"same slice": {
|
"same slice": {
|
||||||
updateParam: epSlice1,
|
updateParam: epSlice1,
|
||||||
checksParam: epSlice1,
|
checksParam: epSlice1,
|
||||||
expectHas: true,
|
expectHas: true,
|
||||||
expectStale: false,
|
expectStale: false,
|
||||||
|
expectResourceVersionsByService: map[types.NamespacedName]endpointSliceResourceVersions{
|
||||||
|
{Namespace: epSlice1.Namespace, Name: "svc1"}: {
|
||||||
|
epSlice1.Name: epSlice1.ResourceVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"different namespace": {
|
"different namespace": {
|
||||||
updateParam: epSlice1,
|
updateParam: epSlice1,
|
||||||
checksParam: epSlice1DifferentNS,
|
checksParam: epSlice1DifferentNS,
|
||||||
expectHas: false,
|
expectHas: false,
|
||||||
expectStale: true,
|
expectStale: true,
|
||||||
|
expectResourceVersionsByService: map[types.NamespacedName]endpointSliceResourceVersions{
|
||||||
|
{Namespace: epSlice1.Namespace, Name: "svc1"}: {
|
||||||
|
epSlice1.Name: epSlice1.ResourceVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"different service": {
|
"different service": {
|
||||||
updateParam: epSlice1,
|
updateParam: epSlice1,
|
||||||
checksParam: epSlice1DifferentService,
|
checksParam: epSlice1DifferentService,
|
||||||
expectHas: false,
|
expectHas: false,
|
||||||
expectStale: true,
|
expectStale: true,
|
||||||
|
expectResourceVersionsByService: map[types.NamespacedName]endpointSliceResourceVersions{
|
||||||
|
{Namespace: epSlice1.Namespace, Name: "svc1"}: {
|
||||||
|
epSlice1.Name: epSlice1.ResourceVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"different resource version": {
|
"different resource version": {
|
||||||
updateParam: epSlice1,
|
updateParam: epSlice1,
|
||||||
checksParam: epSlice1DifferentRV,
|
checksParam: epSlice1DifferentRV,
|
||||||
expectHas: true,
|
expectHas: true,
|
||||||
expectStale: true,
|
expectStale: true,
|
||||||
|
expectResourceVersionsByService: map[types.NamespacedName]endpointSliceResourceVersions{
|
||||||
|
{Namespace: epSlice1.Namespace, Name: "svc1"}: {
|
||||||
|
epSlice1.Name: epSlice1.ResourceVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +108,7 @@ func TestEndpointSliceTrackerUpdate(t *testing.T) {
|
|||||||
if esTracker.Stale(tc.checksParam) != tc.expectStale {
|
if esTracker.Stale(tc.checksParam) != tc.expectStale {
|
||||||
t.Errorf("tc.tracker.Stale(%+v) == %t, expected %t", tc.checksParam, esTracker.Stale(tc.checksParam), tc.expectStale)
|
t.Errorf("tc.tracker.Stale(%+v) == %t, expected %t", tc.checksParam, esTracker.Stale(tc.checksParam), tc.expectStale)
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, tc.expectResourceVersionsByService, esTracker.resourceVersionsByService)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,3 +197,69 @@ func TestEndpointSliceTrackerDelete(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEndpointSliceTrackerDeleteService(t *testing.T) {
|
||||||
|
svcName1, svcNS1 := "svc1", "ns1"
|
||||||
|
svcName2, svcNS2 := "svc2", "ns2"
|
||||||
|
epSlice1 := &discovery.EndpointSlice{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "example-1",
|
||||||
|
Namespace: svcNS1,
|
||||||
|
ResourceVersion: "rv1",
|
||||||
|
Labels: map[string]string{discovery.LabelServiceName: svcName1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
updateParam *discovery.EndpointSlice
|
||||||
|
deleteServiceParam *types.NamespacedName
|
||||||
|
expectHas bool
|
||||||
|
expectStale bool
|
||||||
|
expectResourceVersionsByService map[types.NamespacedName]endpointSliceResourceVersions
|
||||||
|
}{
|
||||||
|
"same service": {
|
||||||
|
updateParam: epSlice1,
|
||||||
|
deleteServiceParam: &types.NamespacedName{Namespace: svcNS1, Name: svcName1},
|
||||||
|
expectHas: false,
|
||||||
|
expectStale: true,
|
||||||
|
expectResourceVersionsByService: map[types.NamespacedName]endpointSliceResourceVersions{},
|
||||||
|
},
|
||||||
|
"different namespace": {
|
||||||
|
updateParam: epSlice1,
|
||||||
|
deleteServiceParam: &types.NamespacedName{Namespace: svcNS2, Name: svcName1},
|
||||||
|
expectHas: true,
|
||||||
|
expectStale: false,
|
||||||
|
expectResourceVersionsByService: map[types.NamespacedName]endpointSliceResourceVersions{
|
||||||
|
{Namespace: epSlice1.Namespace, Name: "svc1"}: {
|
||||||
|
epSlice1.Name: epSlice1.ResourceVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"different service": {
|
||||||
|
updateParam: epSlice1,
|
||||||
|
deleteServiceParam: &types.NamespacedName{Namespace: svcNS1, Name: svcName2},
|
||||||
|
expectHas: true,
|
||||||
|
expectStale: false,
|
||||||
|
expectResourceVersionsByService: map[types.NamespacedName]endpointSliceResourceVersions{
|
||||||
|
{Namespace: epSlice1.Namespace, Name: "svc1"}: {
|
||||||
|
epSlice1.Name: epSlice1.ResourceVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
esTracker := newEndpointSliceTracker()
|
||||||
|
esTracker.Update(tc.updateParam)
|
||||||
|
esTracker.DeleteService(tc.deleteServiceParam.Namespace, tc.deleteServiceParam.Name)
|
||||||
|
if esTracker.Has(tc.updateParam) != tc.expectHas {
|
||||||
|
t.Errorf("tc.tracker.Has(%+v) == %t, expected %t", tc.updateParam, esTracker.Has(tc.updateParam), tc.expectHas)
|
||||||
|
}
|
||||||
|
if esTracker.Stale(tc.updateParam) != tc.expectStale {
|
||||||
|
t.Errorf("tc.tracker.Stale(%+v) == %t, expected %t", tc.updateParam, esTracker.Stale(tc.updateParam), tc.expectStale)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc.expectResourceVersionsByService, esTracker.resourceVersionsByService)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -983,7 +983,7 @@ func expectActions(t *testing.T, actions []k8stesting.Action, num int, verb, res
|
|||||||
}
|
}
|
||||||
|
|
||||||
func expectTrackedResourceVersion(t *testing.T, tracker *endpointSliceTracker, slice *discovery.EndpointSlice, expectedRV string) {
|
func expectTrackedResourceVersion(t *testing.T, tracker *endpointSliceTracker, slice *discovery.EndpointSlice, expectedRV string) {
|
||||||
rrv := tracker.relatedResourceVersions(slice)
|
rrv, _ := tracker.relatedResourceVersions(slice)
|
||||||
rv, tracked := rrv[slice.Name]
|
rv, tracked := rrv[slice.Name]
|
||||||
if !tracked {
|
if !tracked {
|
||||||
t.Fatalf("Expected EndpointSlice %s to be tracked", slice.Name)
|
t.Fatalf("Expected EndpointSlice %s to be tracked", slice.Name)
|
||||||
|
Loading…
Reference in New Issue
Block a user