Skip to content

✨ WithLowPriorityWhenUnchanged: Set Priority for all add methods #3290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 39 additions & 35 deletions pkg/handler/eventhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ package handler
import (
"context"
"reflect"
"time"

"k8s.io/client-go/util/workqueue"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue"
"sigs.k8s.io/controller-runtime/pkg/event"
Expand Down Expand Up @@ -126,20 +128,14 @@ func (h TypedFuncs[object, request]) Create(ctx context.Context, e event.TypedCr
h.CreateFunc(ctx, e, q)
return
}
wq := workqueueWithCustomAddFunc[request]{
TypedRateLimitingInterface: q,

wq := workqueueWithDefaultPriority[request]{
// We already know that we have a priority queue, that event.Object implements
// client.Object and that its not nil
addFunc: func(item request, q workqueue.TypedRateLimitingInterface[request]) {
var priority int
if e.IsInInitialList {
priority = LowPriority
}
q.(priorityqueue.PriorityQueue[request]).AddWithOpts(
priorityqueue.AddOpts{Priority: &priority},
item,
)
},
PriorityQueue: q.(priorityqueue.PriorityQueue[request]),
}
if e.IsInInitialList {
wq.priority = ptr.To(LowPriority)
}
h.CreateFunc(ctx, e, wq)
}
Expand All @@ -160,20 +156,13 @@ func (h TypedFuncs[object, request]) Update(ctx context.Context, e event.TypedUp
return
}

wq := workqueueWithCustomAddFunc[request]{
TypedRateLimitingInterface: q,
wq := workqueueWithDefaultPriority[request]{
// We already know that we have a priority queue, that event.ObjectOld and ObjectNew implement
// client.Object and that they are not nil
addFunc: func(item request, q workqueue.TypedRateLimitingInterface[request]) {
var priority int
if any(e.ObjectOld).(client.Object).GetResourceVersion() == any(e.ObjectNew).(client.Object).GetResourceVersion() {
priority = LowPriority
}
q.(priorityqueue.PriorityQueue[request]).AddWithOpts(
priorityqueue.AddOpts{Priority: &priority},
item,
)
},
PriorityQueue: q.(priorityqueue.PriorityQueue[request]),
}
if any(e.ObjectOld).(client.Object).GetResourceVersion() == any(e.ObjectNew).(client.Object).GetResourceVersion() {
wq.priority = ptr.To(LowPriority)
}
h.UpdateFunc(ctx, e, wq)
}
Expand Down Expand Up @@ -201,13 +190,28 @@ func WithLowPriorityWhenUnchanged[object client.Object, request comparable](u Ty
}
}

type workqueueWithCustomAddFunc[request comparable] struct {
workqueue.TypedRateLimitingInterface[request]
addFunc func(item request, q workqueue.TypedRateLimitingInterface[request])
type workqueueWithDefaultPriority[request comparable] struct {
priorityqueue.PriorityQueue[request]
priority *int
}

func (w workqueueWithDefaultPriority[request]) Add(item request) {
w.PriorityQueue.AddWithOpts(priorityqueue.AddOpts{Priority: w.priority}, item)
}

func (w workqueueWithCustomAddFunc[request]) Add(item request) {
w.addFunc(item, w.TypedRateLimitingInterface)
func (w workqueueWithDefaultPriority[request]) AddAfter(item request, after time.Duration) {
w.PriorityQueue.AddWithOpts(priorityqueue.AddOpts{Priority: w.priority, After: after}, item)
}

func (w workqueueWithDefaultPriority[request]) AddRateLimited(item request) {
w.PriorityQueue.AddWithOpts(priorityqueue.AddOpts{Priority: w.priority, RateLimited: true}, item)
}

func (w workqueueWithDefaultPriority[request]) AddWithOpts(o priorityqueue.AddOpts, items ...request) {
if o.Priority == nil {
o.Priority = w.priority
}
w.PriorityQueue.AddWithOpts(o, items...)
}

// addToQueueCreate adds the reconcile.Request to the priorityqueue in the handler
Expand All @@ -219,11 +223,11 @@ func addToQueueCreate[T client.Object, request comparable](q workqueue.TypedRate
return
}

var priority int
var priority *int
if evt.IsInInitialList {
priority = LowPriority
priority = ptr.To(LowPriority)
}
priorityQueue.AddWithOpts(priorityqueue.AddOpts{Priority: &priority}, item)
priorityQueue.AddWithOpts(priorityqueue.AddOpts{Priority: priority}, item)
}

// addToQueueUpdate adds the reconcile.Request to the priorityqueue in the handler
Expand All @@ -235,9 +239,9 @@ func addToQueueUpdate[T client.Object, request comparable](q workqueue.TypedRate
return
}

var priority int
var priority *int
if evt.ObjectOld.GetResourceVersion() == evt.ObjectNew.GetResourceVersion() {
priority = LowPriority
priority = ptr.To(LowPriority)
}
priorityQueue.AddWithOpts(priorityqueue.AddOpts{Priority: &priority}, item)
priorityQueue.AddWithOpts(priorityqueue.AddOpts{Priority: priority}, item)
}
142 changes: 130 additions & 12 deletions pkg/handler/eventhandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package handler_test

import (
"context"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -776,8 +777,11 @@ var _ = Describe("Eventhandler", func() {

Describe("WithLowPriorityWhenUnchanged", func() {
handlerPriorityTests := []struct {
name string
handler func() handler.EventHandler
name string
handler func() handler.EventHandler
after time.Duration
ratelimited bool
overridePriority int
}{
{
name: "WithLowPriorityWhenUnchanged wrapper",
Expand Down Expand Up @@ -837,6 +841,103 @@ var _ = Describe("Eventhandler", func() {
})
},
},
{
name: "WithLowPriorityWhenUnchanged - Add",
handler: func() handler.EventHandler {
return handler.WithLowPriorityWhenUnchanged(
handler.TypedFuncs[client.Object, reconcile.Request]{
CreateFunc: func(ctx context.Context, tce event.TypedCreateEvent[client.Object], wq workqueue.TypedRateLimitingInterface[reconcile.Request]) {
wq.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tce.Object.GetNamespace(),
Name: tce.Object.GetName(),
}})
},
UpdateFunc: func(ctx context.Context, tue event.TypedUpdateEvent[client.Object], wq workqueue.TypedRateLimitingInterface[reconcile.Request]) {
wq.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tue.ObjectNew.GetNamespace(),
Name: tue.ObjectNew.GetName(),
}})
},
})
},
},
{
name: "WithLowPriorityWhenUnchanged - AddAfter",
handler: func() handler.EventHandler {
return handler.WithLowPriorityWhenUnchanged(
handler.TypedFuncs[client.Object, reconcile.Request]{
CreateFunc: func(ctx context.Context, tce event.TypedCreateEvent[client.Object], wq workqueue.TypedRateLimitingInterface[reconcile.Request]) {
wq.AddAfter(reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tce.Object.GetNamespace(),
Name: tce.Object.GetName(),
}}, time.Second)
},
UpdateFunc: func(ctx context.Context, tue event.TypedUpdateEvent[client.Object], wq workqueue.TypedRateLimitingInterface[reconcile.Request]) {
wq.AddAfter(reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tue.ObjectNew.GetNamespace(),
Name: tue.ObjectNew.GetName(),
}}, time.Second)
},
})
},
after: time.Second,
},
{
name: "WithLowPriorityWhenUnchanged - AddRateLimited",
handler: func() handler.EventHandler {
return handler.WithLowPriorityWhenUnchanged(
handler.TypedFuncs[client.Object, reconcile.Request]{
CreateFunc: func(ctx context.Context, tce event.TypedCreateEvent[client.Object], wq workqueue.TypedRateLimitingInterface[reconcile.Request]) {
wq.AddRateLimited(reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tce.Object.GetNamespace(),
Name: tce.Object.GetName(),
}})
},
UpdateFunc: func(ctx context.Context, tue event.TypedUpdateEvent[client.Object], wq workqueue.TypedRateLimitingInterface[reconcile.Request]) {
wq.AddRateLimited(reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tue.ObjectNew.GetNamespace(),
Name: tue.ObjectNew.GetName(),
}})
},
})
},
ratelimited: true,
},
{
name: "WithLowPriorityWhenUnchanged - AddWithOpts priority is retained",
handler: func() handler.EventHandler {
return handler.WithLowPriorityWhenUnchanged(
handler.TypedFuncs[client.Object, reconcile.Request]{
CreateFunc: func(ctx context.Context, tce event.TypedCreateEvent[client.Object], wq workqueue.TypedRateLimitingInterface[reconcile.Request]) {
if pq, isPQ := wq.(priorityqueue.PriorityQueue[reconcile.Request]); isPQ {
pq.AddWithOpts(priorityqueue.AddOpts{Priority: ptr.To(100)}, reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tce.Object.GetNamespace(),
Name: tce.Object.GetName(),
}})
return
}
wq.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tce.Object.GetNamespace(),
Name: tce.Object.GetName(),
}})
},
UpdateFunc: func(ctx context.Context, tue event.TypedUpdateEvent[client.Object], wq workqueue.TypedRateLimitingInterface[reconcile.Request]) {
if pq, isPQ := wq.(priorityqueue.PriorityQueue[reconcile.Request]); isPQ {
pq.AddWithOpts(priorityqueue.AddOpts{Priority: ptr.To(100)}, reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tue.ObjectNew.GetNamespace(),
Name: tue.ObjectNew.GetName(),
}})
return
}
wq.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Namespace: tue.ObjectNew.GetNamespace(),
Name: tue.ObjectNew.GetName(),
}})
},
})
},
overridePriority: 100,
},
}
for _, test := range handlerPriorityTests {
When("handler is "+test.name, func() {
Expand All @@ -862,7 +963,16 @@ var _ = Describe("Eventhandler", func() {
IsInInitialList: true,
}, wq)

Expect(actualOpts).To(Equal(priorityqueue.AddOpts{Priority: ptr.To(handler.LowPriority)}))
expected := handler.LowPriority
if test.overridePriority != 0 {
expected = test.overridePriority
}

Expect(actualOpts).To(Equal(priorityqueue.AddOpts{
Priority: ptr.To(expected),
After: test.after,
RateLimited: test.ratelimited,
}))
Expect(actualRequests).To(Equal([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "my-pod"}}}))
})

Expand All @@ -888,10 +998,12 @@ var _ = Describe("Eventhandler", func() {
IsInInitialList: false,
}, wq)

Expect(actualOpts).To(Or(
Equal(priorityqueue.AddOpts{}),
Equal(priorityqueue.AddOpts{Priority: ptr.To(0)}),
))
var expectedPriority *int
if test.overridePriority != 0 {
expectedPriority = &test.overridePriority
}

Expect(actualOpts).To(Equal(priorityqueue.AddOpts{After: test.after, RateLimited: test.ratelimited, Priority: expectedPriority}))
Expect(actualRequests).To(Equal([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "my-pod"}}}))
})

Expand Down Expand Up @@ -922,7 +1034,12 @@ var _ = Describe("Eventhandler", func() {
}},
}, wq)

Expect(actualOpts).To(Equal(priorityqueue.AddOpts{Priority: ptr.To(handler.LowPriority)}))
expectedPriority := handler.LowPriority
if test.overridePriority != 0 {
expectedPriority = test.overridePriority
}

Expect(actualOpts).To(Equal(priorityqueue.AddOpts{After: test.after, RateLimited: test.ratelimited, Priority: ptr.To(expectedPriority)}))
Expect(actualRequests).To(Equal([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "my-pod"}}}))
})

Expand Down Expand Up @@ -954,10 +1071,11 @@ var _ = Describe("Eventhandler", func() {
}},
}, wq)

Expect(actualOpts).To(Or(
Equal(priorityqueue.AddOpts{}),
Equal(priorityqueue.AddOpts{Priority: ptr.To(0)}),
))
var expectedPriority *int
if test.overridePriority != 0 {
expectedPriority = &test.overridePriority
}
Expect(actualOpts).To(Equal(priorityqueue.AddOpts{After: test.after, RateLimited: test.ratelimited, Priority: expectedPriority}))
Expect(actualRequests).To(Equal([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "my-pod"}}}))
})

Expand Down