Repository: kubernetes-sigs/kueue

Issue:
controller-manager is stuck in a CrashLoopBackOff state when a different release name is used.
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!

If the matter is security related, please disclose it privately via https://kubernetes.io/security/
-->


**What happened**:

If you use a release name other than "kueue" while installing Kueue, the controller-manager will be stuck in a CrashLoopBackOff state.

**What you expected to happen**:

**How to reproduce it (as minimally and precisely as possible)**:

```
$ helm upgrade --install something oci://registry.k8s.io/kueue/charts/kueue \
  --version=0.13.3 \
  --namespace  kueue \
  --create-namespace \
  --wait --timeout 300s
```

```
$ k get po -n kueue         
NAME                                                  READY   STATUS             RESTARTS      AGE
something-kueue-controller-manager-595cf866fb-gr7pj   0/1     CrashLoopBackOff   5 (48s ago)   3m37s
```

Now uninstall the failed release, and install again with "kueue" release name:
```
$ helm uninstall -n kueue something 
release "something" uninstalled

$ helm upgrade --install kueue oci://registry.k8s.io/kueue/charts/kueue \
  --version=0.13.3 \
  --namespace  kueue \
  --create-namespace \
  --wait --timeout 300s

$ k get po -n kueue
NAME                                        READY   STATUS    RESTARTS   AGE
kueue-controller-manager-864ddb8d8b-22ssk   1/1     Running   0          6m13s

```



**Anything else we need to know?**:

Logs of failed release:
<details>

```
{"level":"info","ts":"2025-09-11T14:20:52.485575817Z","logger":"setup","caller":"kueue/main.go:482","msg":"Successfully loaded configuration","config":"apiVersion: config.kueue.x-k8s.io/v1beta1\nclientConnection:\n  burst: 100\n  qps: 50\ncontroller:\n  groupKindConcurrency:\n    ClusterQueue.kueue.x-k8s.io: 1\n    Job.batch: 5\n    LocalQueue.kueue.x-k8s.io: 1\n    Pod: 5\n    ResourceFlavor.kueue.x-k8s.io: 1\n    Workload.kueue.x-k8s.io: 5\nhealth:\n  healthProbeBindAddress: :8081\nintegrations:\n  frameworks:\n  - batch/job\n  - kubeflow.org/mpijob\n  - ray.io/rayjob\n  - ray.io/raycluster\n  - jobset.x-k8s.io/jobset\n  - kubeflow.org/paddlejob\n  - kubeflow.org/pytorchjob\n  - kubeflow.org/tfjob\n  - kubeflow.org/xgboostjob\n  - kubeflow.org/jaxjob\n  - workload.codeflare.dev/appwrapper\ninternalCertManagement:\n  enable: true\n  webhookSecretName: kueue-webhook-server-cert\n  webhookServiceName: kueue-webhook-service\nkind: Configuration\nleaderElection:\n  leaderElect: true\n  leaseDuration: 15s\n  renewDeadline: 10s\n  resourceLock: leases\n  resourceName: c1f6bfd2.kueue.x-k8s.io\n  resourceNamespace: \"\"\n  retryPeriod: 2s\nmanageJobsWithoutQueueName: false\nmanagedJobsNamespaceSelector:\n  matchExpressions:\n  - key: kubernetes.io/metadata.name\n    operator: NotIn\n    values:\n    - kube-system\n    - kueue\nmetrics:\n  bindAddress: :8443\nmultiKueue:\n  dispatcherName: kueue.x-k8s.io/multikueue-dispatcher-all-at-once\n  gcInterval: 1m0s\n  origin: multikueue\n  workerLostTimeout: 15m0s\nnamespace: kueue\nqueueVisibility:\n  clusterQueues:\n    maxCount: 10\n  updateIntervalSeconds: 5\nwaitForPodsReady: {}\nwebhook:\n  certDir: /tmp/k8s-webhook-server/serving-certs\n  port: 9443\n"}
{"level":"info","ts":"2025-09-11T14:20:52.485742674Z","logger":"setup","caller":"kueue/main.go:147","msg":"Initializing","gitVersion":"v0.13.3","gitCommit":"0d74c06edafeb143736e7e6768cba24635a33cf7","buildDate":"2025-08-22T12:20:19Z"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.48586062Z","logger":"setup","caller":"features/kube_features.go:317","msg":"Loaded feature gates","featureGates":{"AdmissionFairSharing":false,"ConfigurableResourceTransformations":true,"ElasticJobsViaWorkloadSlices":false,"ExposeFlavorsInLocalQueue":true,"FlavorFungibility":true,"FlavorFungibilityImplicitPreferenceDefault":false,"HierarchicalCohorts":true,"LendingLimit":true,"LocalQueueDefaulting":true,"LocalQueueMetrics":false,"ManagedJobsNamespaceSelector":true,"ManagedJobsNamespaceSelectorAlwaysRespected":false,"MultiKueue":true,"MultiKueueBatchJobWithManagedBy":false,"ObjectRetentionPolicies":true,"PartialAdmission":true,"PrioritySortingWithinCohort":true,"ProvisioningACC":true,"QueueVisibility":false,"TASFailedNodeReplacement":false,"TASFailedNodeReplacementFailFast":false,"TASProfileLeastFreeCapacity":false,"TASProfileMixed":false,"TASReplaceNodeOnPodTermination":false,"TopologyAwareScheduling":false,"VisibilityOnDemand":true}}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.486147431Z","logger":"setup","caller":"kueue/main.go:196","msg":"K8S Client","qps":50,"burst":100}
{"level":"error","ts":"2025-09-11T14:20:52.506185001Z","logger":"setup","caller":"kueue/main.go:294","msg":"Skipping admission check controller setup: Provisioning Requests not supported (Possible cause: missing or unsupported cluster-autoscaler)","error":"no matches for kind \"ProvisioningRequest\" in version \"autoscaling.x-k8s.io/v1\"","stacktrace":"main.setupIndexes\n\t/workspace/cmd/kueue/main.go:294\nmain.main\n\t/workspace/cmd/kueue/main.go:234\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:283"}
{"level":"info","ts":"2025-09-11T14:20:52.507875561Z","logger":"setup","caller":"kueue/main.go:423","msg":"Probe endpoints are configured on healthz and readyz"}
{"level":"info","ts":"2025-09-11T14:20:52.507968159Z","logger":"setup","caller":"kueue/main.go:278","msg":"Starting manager"}
{"level":"info","ts":"2025-09-11T14:20:52.507974504Z","logger":"setup","caller":"cert/cert.go:70","msg":"Waiting for certificate generation to complete"}
{"level":"info","ts":"2025-09-11T14:20:52.508115671Z","logger":"controller-runtime.metrics","caller":"server/server.go:208","msg":"Starting metrics server"}
{"level":"info","ts":"2025-09-11T14:20:52.508706505Z","caller":"manager/server.go:83","msg":"starting server","name":"health probe","addr":"[::]:8081"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.511637244Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"*v1beta1.MultiKueueConfig","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.512359327Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"*v1beta1.MultiKueueCluster","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.512656931Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"*v1beta1.AdmissionCheck","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.512750575Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"*v1.LimitRange","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.513125254Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"*v1beta1.LocalQueue","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.513275694Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"*v1.Job","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.516085201Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"*v1beta1.Workload","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"info","ts":"2025-09-11T14:20:52.6093736Z","logger":"cert-rotation","caller":"rotator/rotator.go:290","msg":"starting cert rotator controller"}
I0911 14:20:52.609535       1 leaderelection.go:257] attempting to acquire leader lease kueue/c1f6bfd2.kueue.x-k8s.io...
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.647993933Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"admissionregistration.k8s.io/v1, Kind=MutatingWebhookConfiguration","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.659947794Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"*v1.Secret","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"Level(-2)","ts":"2025-09-11T14:20:52.664329453Z","logger":"controller-runtime.cache","caller":"cache/reflector.go:430","msg":"Caches populated","type":"admissionregistration.k8s.io/v1, Kind=ValidatingWebhookConfiguration","reflector":"sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go:114"}
{"level":"error","ts":"2025-09-11T14:20:52.70994724Z","logger":"cert-rotation","caller":"rotator/rotator.go:293","msg":"could not refresh cert on startup","error":"acquiring secret to update certificates: Secret \"kueue-webhook-server-cert\" not found","errorVerbose":"Secret \"kueue-webhook-server-cert\" not found\nacquiring secret to update certificates\ngithub.com/open-policy-agent/cert-controller/pkg/rotator.(*CertRotator).refreshCertIfNeeded.func1\n\t/workspace/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go:331\nk8s.io/apimachinery/pkg/util/wait.runConditionWithCrashProtection\n\t/workspace/vendor/k8s.io/apimachinery/pkg/util/wait/wait.go:150\nk8s.io/apimachinery/pkg/util/wait.ExponentialBackoff\n\t/workspace/vendor/k8s.io/apimachinery/pkg/util/wait/backoff.go:477\ngithub.com/open-policy-agent/cert-controller/pkg/rotator.(*CertRotator).refreshCertIfNeeded\n\t/workspace/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go:364\ngithub.com/open-policy-agent/cert-controller/pkg/rotator.(*CertRotator).Start\n\t/workspace/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go:292\nsigs.k8s.io/controller-runtime/pkg/manager.(*runnableGroup).reconcile.func1\n\t/workspace/vendor/sigs.k8s.io/controller-runtime/pkg/manager/runnable_group.go:226\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1700","stacktrace":"github.com/open-policy-agent/cert-controller/pkg/rotator.(*CertRotator).Start\n\t/workspace/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go:293\nsigs.k8s.io/controller-runtime/pkg/manager.(*runnableGroup).reconcile.func1\n\t/workspace/vendor/sigs.k8s.io/controller-runtime/pkg/manager/runnable_group.go:226"}
{"level":"info","ts":"2025-09-11T14:20:52.710070416Z","logger":"cert-rotation","caller":"rotator/rotator.go:294","msg":"stopping cert rotator controller"}
{"level":"info","ts":"2025-09-11T14:20:52.710099684Z","caller":"manager/internal.go:538","msg":"Stopping and waiting for non leader election runnables"}
{"level":"info","ts":"2025-09-11T14:20:52.710166085Z","caller":"manager/internal.go:542","msg":"Stopping and waiting for leader election runnables"}
{"level":"info","ts":"2025-09-11T14:20:52.710233626Z","caller":"manager/internal.go:550","msg":"Stopping and waiting for caches"}
{"level":"info","ts":"2025-09-11T14:20:52.710431402Z","caller":"manager/internal.go:554","msg":"Stopping and waiting for webhooks"}
{"level":"info","ts":"2025-09-11T14:20:52.710464056Z","caller":"manager/internal.go:557","msg":"Stopping and waiting for HTTP servers"}
{"level":"info","ts":"2025-09-11T14:20:52.710493338Z","caller":"manager/server.go:68","msg":"shutting down server","name":"health probe","addr":"[::]:8081"}
W0911 14:20:53.051911       1 requestheader_controller.go:204] Unable to get configmap/extension-apiserver-authentication in kube-system.  Usually fixed by 'kubectl create rolebinding -n kube-system ROLEBINDING_NAME --role=extension-apiserver-authentication-reader --serviceaccount=YOUR_NS:YOUR_SA'
{"level":"error","ts":"2025-09-11T14:20:53.051939699Z","logger":"setup","caller":"kueue/main.go:267","msg":"Unable to create and start visibility server","error":"unable to apply VisibilityServerOptions: unable to load configmap based request-header-client-ca-file: configmaps \"extension-apiserver-authentication\" is forbidden: User \"system:serviceaccount:kueue:something-kueue-controller-manager\" cannot get resource \"configmaps\" in API group \"\" in the namespace \"kube-system\"","stacktrace":"main.main.func3\n\t/workspace/cmd/kueue/main.go:267"}
```

</details>

**Environment**:
- Kubernetes version (use `kubectl version`):
- Kueue version (use `git describe --tags --dirty --always`):
- Cloud provider or hardware configuration:
- OS (e.g: `cat /etc/os-release`):
- Kernel (e.g. `uname -a`):
- Install tools:
- Others:

Discussion:
@mbobrovskyi: /assign@buraksekili: @mbobrovskyi thank you for your quick action! Is it possible to mitigate this issue until the fix is released?@mbobrovskyi: > Is it possible to mitigate this issue until the fix is released?

Yeah, I think we should include this changes on the next release. 
@mimowo WDYT?@mimowo: We are planning to release today, hope the fix can be merged then. I will review today@mimowo: cc @tenzen-y it looks like another case where we cannot verify without e2e tests, because the yaml itself is correct.

x-ref: https://github.com/kubernetes-sigs/kueue/issues/5145

Solution patch:
diff --git a/.golangci.yaml b/.golangci.yaml
index b090b5d66..5c873a09c 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -21,6 +21,7 @@ linters:
     - perfsprint
     - revive
     - unconvert
+    - forbidigo
   settings:
     gocritic:
       enabled-checks:
@@ -70,6 +71,10 @@ linters:
         - name: var-naming
         - name: redundant-import-alias
         - name: use-any
+    forbidigo:
+      forbid:
+        - pattern: ^sort\.Slice$
+          msg: Please use slices.SortFunc instead of sort.Slice for better performance and consistency
   exclusions:
     generated: lax
     presets:
diff --git a/charts/kueue/templates/visibility/role_binding.yaml b/charts/kueue/templates/visibility/role_binding.yaml
index f9ac2baf0..963e5e83e 100644
--- a/charts/kueue/templates/visibility/role_binding.yaml
+++ b/charts/kueue/templates/visibility/role_binding.yaml
@@ -13,5 +13,5 @@ roleRef:
   name: extension-apiserver-authentication-reader
 subjects:
   - kind: ServiceAccount
-    name: kueue-controller-manager
+    name: '{{ include "kueue.fullname" . }}-controller-manager'
     namespace: '{{ .Release.Namespace }}'
diff --git a/config/components/visibility/kustomization.yaml b/config/components/visibility/kustomization.yaml
index cd29a9935..18bd0173c 100644
--- a/config/components/visibility/kustomization.yaml
+++ b/config/components/visibility/kustomization.yaml
@@ -2,3 +2,6 @@ resources:
 - apiservice_v1beta1.yaml
 - role_binding.yaml
 - service.yaml
+
+transformers:
+- subject_transformer.yaml
diff --git a/config/components/visibility/role_binding.yaml b/config/components/visibility/role_binding.yaml
index 7e146e908..c216438f8 100644
--- a/config/components/visibility/role_binding.yaml
+++ b/config/components/visibility/role_binding.yaml
@@ -9,6 +9,5 @@ roleRef:
   name: extension-apiserver-authentication-reader
 subjects:
 - kind: ServiceAccount
-  name: kueue-controller-manager
-  namespace: kueue-system
-  
\ No newline at end of file
+  name: controller-manager
+  namespace: system
diff --git a/config/components/visibility/subject_transformer.yaml b/config/components/visibility/subject_transformer.yaml
new file mode 100644
index 000000000..4704e27bf
--- /dev/null
+++ b/config/components/visibility/subject_transformer.yaml
@@ -0,0 +1,12 @@
+apiVersion: builtin
+kind: PrefixSuffixTransformer
+metadata:
+  name: add-prefix-to-subject-name
+prefix: kueue-
+fieldSpecs:
+- kind: RoleBinding
+  group: rbac.authorization.k8s.io
+  path: subjects/name
+- kind: RoleBinding
+  group: rbac.authorization.k8s.io
+  path: subjects/namespace
diff --git a/hack/processing-plan.yaml b/hack/processing-plan.yaml
index 956387d57..380057a2d 100644
--- a/hack/processing-plan.yaml
+++ b/hack/processing-plan.yaml
@@ -73,6 +73,7 @@ files:
     removeComments: true
     excludes:
       - kustomization.yaml
+      - subject_transformer.yaml
     continueOnError: true
     operations:
       - type: APPEND
@@ -92,6 +93,9 @@ files:
       - type: UPDATE
         key: .subjects.[].namespace
         value: '"{{ .Release.Namespace }}"'
+      - type: APPEND
+        key: .subjects.[].name
+        value: '"{{ include \"kueue.fullname\" . }}-"'
     postOperations:
       - type: INSERT_TEXT
         key: .metadata
diff --git a/pkg/cache/queue/cluster_queue.go b/pkg/cache/queue/cluster_queue.go
index ae9ba40f1..a38c1b305 100644
--- a/pkg/cache/queue/cluster_queue.go
+++ b/pkg/cache/queue/cluster_queue.go
@@ -18,7 +18,7 @@ package queue
 
 import (
 	"context"
-	"sort"
+	"slices"
 	"sync"
 
 	corev1 "k8s.io/api/core/v1"
@@ -404,8 +404,11 @@ func (c *ClusterQueue) DumpInadmissible() ([]workload.Reference, bool) {
 // this ClusterQueue.
 func (c *ClusterQueue) Snapshot() []*workload.Info {
 	elements := c.totalElements()
-	sort.Slice(elements, func(i, j int) bool {
-		return c.lessFunc(elements[i], elements[j])
+	slices.SortFunc(elements, func(a, b *workload.Info) int {
+		if c.lessFunc(a, b) {
+			return -1
+		}
+		return 1
 	})
 	return elements
 }
diff --git a/pkg/cache/scheduler/cache.go b/pkg/cache/scheduler/cache.go
index eddc25245..8234b0894 100644
--- a/pkg/cache/scheduler/cache.go
+++ b/pkg/cache/scheduler/cache.go
@@ -17,10 +17,11 @@ limitations under the License.
 package scheduler
 
 import (
+	"cmp"
 	"context"
 	"errors"
 	"fmt"
-	"sort"
+	"slices"
 	"sync"
 
 	"github.com/go-logr/logr"
@@ -790,8 +791,8 @@ func getUsage(frq resources.FlavorResourceQuantities, cq *clusterQueue) []kueue.
 				outFlvUsage.Resources = append(outFlvUsage.Resources, rUsage)
 			}
 			// The resourceUsages should be in a stable order to avoid endless creation of update events.
-			sort.Slice(outFlvUsage.Resources, func(i, j int) bool {
-				return outFlvUsage.Resources[i].Name < outFlvUsage.Resources[j].Name
+			slices.SortFunc(outFlvUsage.Resources, func(a, b kueue.ResourceUsage) int {
+				return cmp.Compare(a.Name, b.Name)
 			})
 			usage = append(usage, outFlvUsage)
 		}
@@ -885,8 +886,8 @@ func filterLocalQueueUsage(orig resources.FlavorResourceQuantities, resourceGrou
 				})
 			}
 			// The resourceUsages should be in a stable order to avoid endless creation of update events.
-			sort.Slice(outFlvUsage.Resources, func(i, j int) bool {
-				return outFlvUsage.Resources[i].Name < outFlvUsage.Resources[j].Name
+			slices.SortFunc(outFlvUsage.Resources, func(a, b kueue.LocalQueueResourceUsage) int {
+				return cmp.Compare(a.Name, b.Name)
 			})
 			qFlvUsages = append(qFlvUsages, outFlvUsage)
 		}
diff --git a/pkg/cache/scheduler/tas_flavor_snapshot.go b/pkg/cache/scheduler/tas_flavor_snapshot.go
index eefc4a8ab..02d69fe04 100644
--- a/pkg/cache/scheduler/tas_flavor_snapshot.go
+++ b/pkg/cache/scheduler/tas_flavor_snapshot.go
@@ -24,7 +24,6 @@ import (
 	"maps"
 	"math"
 	"slices"
-	"sort"
 	"strconv"
 	"strings"
 
@@ -775,10 +774,10 @@ func (s *TASFlavorSnapshot) mergeTopologyAssignments(a, b *kueue.TopologyAssignm
 	sortedDomains := make([]kueue.TopologyDomainAssignment, 0, len(a.Domains)+len(b.Domains))
 	sortedDomains = append(sortedDomains, a.Domains...)
 	sortedDomains = append(sortedDomains, b.Domains...)
-	sort.Slice(sortedDomains, func(i, j int) bool {
-		a, b := sortedDomains[i], sortedDomains[j]
-		aDomain, bDomain := s.domainsPerLevel[nodeLevel][utiltas.DomainID(a.Values)], s.domainsPerLevel[nodeLevel][utiltas.DomainID(b.Values)]
-		return utiltas.DomainID(aDomain.levelValues) < utiltas.DomainID(bDomain.levelValues)
+	slices.SortFunc(sortedDomains, func(a, b kueue.TopologyDomainAssignment) int {
+		aDomain := s.domainsPerLevel[nodeLevel][utiltas.DomainID(a.Values)]
+		bDomain := s.domainsPerLevel[nodeLevel][utiltas.DomainID(b.Values)]
+		return cmp.Compare(utiltas.DomainID(aDomain.levelValues), utiltas.DomainID(bDomain.levelValues))
 	})
 	mergedDomains := make([]kueue.TopologyDomainAssignment, 0, len(sortedDomains))
 	for _, domain := range sortedDomains {
diff --git a/pkg/controller/jobs/mpijob/mpijob_webhook.go b/pkg/controller/jobs/mpijob/mpijob_webhook.go
index 56f76d591..c82edec06 100644
--- a/pkg/controller/jobs/mpijob/mpijob_webhook.go
+++ b/pkg/controller/jobs/mpijob/mpijob_webhook.go
@@ -17,8 +17,9 @@ limitations under the License.
 package mpijob
 
 import (
+	"cmp"
 	"context"
-	"sort"
+	"slices"
 
 	"github.com/kubeflow/mpi-operator/pkg/apis/kubeflow/v2beta1"
 	"k8s.io/apimachinery/pkg/labels"
@@ -103,8 +104,8 @@ func (w *MpiJobWebhook) ValidateCreate(ctx context.Context, obj runtime.Object)
 	if err != nil {
 		return nil, err
 	}
-	sort.Slice(validationErrs, func(i, j int) bool {
-		return validationErrs[i].Field < validationErrs[j].Field
+	slices.SortFunc(validationErrs, func(a, b *field.Error) int {
+		return cmp.Compare(a.Field, b.Field)
 	})
 	return nil, validationErrs.ToAggregate()
 }
@@ -121,8 +122,8 @@ func (w *MpiJobWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runti
 		return nil, err
 	}
 	allErrs = append(allErrs, validationErrs...)
-	sort.Slice(validationErrs, func(i, j int) bool {
-		return validationErrs[i].Field < validationErrs[j].Field
+	slices.SortFunc(validationErrs, func(a, b *field.Error) int {
+		return cmp.Compare(a.Field, b.Field)
 	})
 	return nil, allErrs.ToAggregate()
 }
diff --git a/pkg/controller/jobs/pod/pod_controller.go b/pkg/controller/jobs/pod/pod_controller.go
index 08c9a518a..7c6a88a1e 100644
--- a/pkg/controller/jobs/pod/pod_controller.go
+++ b/pkg/controller/jobs/pod/pod_controller.go
@@ -22,7 +22,6 @@ import (
 	"errors"
 	"fmt"
 	"slices"
-	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -52,6 +51,7 @@ import (
 	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/podset"
 	clientutil "sigs.k8s.io/kueue/pkg/util/client"
+	cmputil "sigs.k8s.io/kueue/pkg/util/cmp"
 	"sigs.k8s.io/kueue/pkg/util/expectations"
 	utilmaps "sigs.k8s.io/kueue/pkg/util/maps"
 	"sigs.k8s.io/kueue/pkg/util/parallelize"
@@ -827,22 +827,21 @@ func lastActiveTime(clock clock.Clock, p *corev1.Pod) time.Time {
 // - lastActiveTime (pods that were active last are first)
 // - creation timestamp (newer pods are first)
 func sortInactivePods(clock clock.Clock, inactivePods []corev1.Pod) {
-	sort.Slice(inactivePods, func(i, j int) bool {
-		pi := &inactivePods[i]
-		pj := &inactivePods[j]
-		iFin := slices.Contains(pi.Finalizers, podconstants.PodFinalizer)
-		jFin := slices.Contains(pj.Finalizers, podconstants.PodFinalizer)
-		if iFin != jFin {
-			return iFin
-		}
-
-		iLastActive := lastActiveTime(clock, pi)
-		jLastActive := lastActiveTime(clock, pj)
-
-		if iLastActive.Equal(jLastActive) {
-			return pi.CreationTimestamp.Before(&pj.CreationTimestamp)
-		}
-		return jLastActive.Before(iLastActive)
+	slices.SortFunc(inactivePods, func(pi, pj corev1.Pod) int {
+		return cmputil.LazyOr(
+			func() int {
+				return cmputil.CompareBool(
+					slices.Contains(pi.Finalizers, podconstants.PodFinalizer),
+					slices.Contains(pj.Finalizers, podconstants.PodFinalizer),
+				)
+			},
+			func() int {
+				return lastActiveTime(clock, &pj).Compare(lastActiveTime(clock, &pi))
+			},
+			func() int {
+				return pi.CreationTimestamp.Compare(pj.CreationTimestamp.Time)
+			},
+		)
 	})
 }
 
@@ -852,22 +851,26 @@ func sortInactivePods(clock clock.Clock, inactivePods []corev1.Pod) {
 // - creation timestamp (newer pods are last)
 func sortActivePods(activePods []corev1.Pod) {
 	// Sort active pods by creation timestamp
-	sort.Slice(activePods, func(i, j int) bool {
-		pi := &activePods[i]
-		pj := &activePods[j]
-		iFin := slices.Contains(pi.Finalizers, podconstants.PodFinalizer)
-		jFin := slices.Contains(pj.Finalizers, podconstants.PodFinalizer)
-		// Prefer to keep pods that have a finalizer.
-		if iFin != jFin {
-			return iFin
-		}
-		iGated := isGated(pi)
-		jGated := isGated(pj)
-		// Prefer to keep pods that aren't gated.
-		if iGated != jGated {
-			return !iGated
-		}
-		return pi.CreationTimestamp.Before(&pj.CreationTimestamp)
+	slices.SortFunc(activePods, func(pi, pj corev1.Pod) int {
+		return cmputil.LazyOr(
+			func() int {
+				// Prefer to keep pods that have a finalizer.
+				return cmputil.CompareBool(
+					slices.Contains(pi.Finalizers, podconstants.PodFinalizer),
+					slices.Contains(pj.Finalizers, podconstants.PodFinalizer),
+				)
+			},
+			func() int {
+				// Prefer to keep pods that aren't gated.
+				return cmputil.CompareBool(
+					isGated(&pj),
+					isGated(&pi),
+				)
+			},
+			func() int {
+				return pi.CreationTimestamp.Compare(pj.CreationTimestamp.Time)
+			},
+		)
 	})
 }
 
diff --git a/pkg/scheduler/preemption/classical/candidate_generator.go b/pkg/scheduler/preemption/classical/candidate_generator.go
index 240683edb..3e583c32d 100644
--- a/pkg/scheduler/preemption/classical/candidate_generator.go
+++ b/pkg/scheduler/preemption/classical/candidate_generator.go
@@ -17,6 +17,7 @@ limitations under the License.
 package classical
 
 import (
+	"slices"
 	"sort"
 	"time"
 
@@ -80,18 +81,18 @@ func NewCandidateIterator(
 	frsNeedPreemption sets.Set[resources.FlavorResource],
 	snapshot *schdcache.Snapshot,
 	clock clock.Clock,
-	ordering func(logr.Logger, bool, *workload.Info, *workload.Info, kueue.ClusterQueueReference, time.Time) bool,
+	ordering func(logr.Logger, bool, *workload.Info, *workload.Info, kueue.ClusterQueueReference, time.Time) int,
 ) *candidateIterator {
 	sameQueueCandidates := collectSameQueueCandidates(hierarchicalReclaimCtx)
 	hierarchyCandidates, priorityCandidates := collectCandidatesForHierarchicalReclaim(hierarchicalReclaimCtx)
-	sort.Slice(sameQueueCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, sameQueueCandidates[i].wl, sameQueueCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+	slices.SortFunc(sameQueueCandidates, func(a, b *candidateElem) int {
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, a.wl, b.wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
-	sort.Slice(priorityCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, priorityCandidates[i].wl, priorityCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+	slices.SortFunc(priorityCandidates, func(a, b *candidateElem) int {
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, a.wl, b.wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
-	sort.Slice(hierarchyCandidates, func(i, j int) bool {
-		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, hierarchyCandidates[i].wl, hierarchyCandidates[j].wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
+	slices.SortFunc(hierarchyCandidates, func(a, b *candidateElem) int {
+		return ordering(hierarchicalReclaimCtx.Log, enabledAfs, a.wl, b.wl, hierarchicalReclaimCtx.Cq.Name, clock.Now())
 	})
 
 	evictedHierarchicalReclaimCandidates, nonEvictedHierarchicalReclaimCandidates := splitEvicted(hierarchyCandidates)
diff --git a/pkg/scheduler/preemption/common/ordering.go b/pkg/scheduler/preemption/common/ordering.go
index 3c34ea3d6..c316f738d 100644
--- a/pkg/scheduler/preemption/common/ordering.go
+++ b/pkg/scheduler/preemption/common/ordering.go
@@ -17,6 +17,7 @@ limitations under the License.
 package preemptioncommon
 
 import (
+	"cmp"
 	"time"
 
 	"github.com/go-logr/logr"
@@ -25,6 +26,7 @@ import (
 	"k8s.io/klog/v2"
 
 	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
+	cmputil "sigs.k8s.io/kueue/pkg/util/cmp"
 	"sigs.k8s.io/kueue/pkg/util/priority"
 	"sigs.k8s.io/kueue/pkg/workload"
 )
@@ -36,38 +38,48 @@ import (
 // 2. (AdmissionFairSharing only) Workloads with lower LocalQueue's usage first
 // 3. Workloads with lower priority first.
 // 4. Workloads admitted more recently first.
-func CandidatesOrdering(log logr.Logger, afsEnabled bool, a, b *workload.Info, cq kueue.ClusterQueueReference, now time.Time) bool {
-	aEvicted := meta.IsStatusConditionTrue(a.Obj.Status.Conditions, kueue.WorkloadEvicted)
-	bEvicted := meta.IsStatusConditionTrue(b.Obj.Status.Conditions, kueue.WorkloadEvicted)
-	if aEvicted != bEvicted {
-		return aEvicted
-	}
-	aInCQ := a.ClusterQueue == cq
-	bInCQ := b.ClusterQueue == cq
-	if aInCQ != bInCQ {
-		return !aInCQ
-	}
-
-	if afsEnabled && resourceUsagePreemptionEnabled(a, b) {
-		if a.LocalQueueFSUsage != b.LocalQueueFSUsage {
-			log.V(3).Info("Comparing workloads by LocalQueue fair sharing usage",
-				"workloadA", klog.KObj(a.Obj), "queueA", a.Obj.Spec.QueueName, "usageA", a.LocalQueueFSUsage,
-				"workloadB", klog.KObj(b.Obj), "queueB", b.Obj.Spec.QueueName, "usageB", b.LocalQueueFSUsage)
-			return *a.LocalQueueFSUsage > *b.LocalQueueFSUsage
-		}
-	}
-	pa := priority.Priority(a.Obj)
-	pb := priority.Priority(b.Obj)
-	if pa != pb {
-		return pa < pb
-	}
-	timeA := quotaReservationTime(a.Obj, now)
-	timeB := quotaReservationTime(b.Obj, now)
-	if !timeA.Equal(timeB) {
-		return timeA.After(timeB)
-	}
-	// Arbitrary comparison for deterministic sorting.
-	return a.Obj.UID < b.Obj.UID
+func CandidatesOrdering(log logr.Logger, afsEnabled bool, a, b *workload.Info, cq kueue.ClusterQueueReference, now time.Time) int {
+	return cmputil.LazyOr(
+		func() int {
+			return cmputil.CompareBool(
+				meta.IsStatusConditionTrue(a.Obj.Status.Conditions, kueue.WorkloadEvicted),
+				meta.IsStatusConditionTrue(b.Obj.Status.Conditions, kueue.WorkloadEvicted),
+			)
+		},
+		func() int {
+			return cmputil.CompareBool(
+				b.ClusterQueue == cq,
+				a.ClusterQueue == cq,
+			)
+		},
+		func() int {
+			if afsEnabled &&
+				resourceUsagePreemptionEnabled(a, b) &&
+				a.LocalQueueFSUsage != b.LocalQueueFSUsage {
+				log.V(3).Info("Comparing workloads by LocalQueue fair sharing usage",
+					"workloadA", klog.KObj(a.Obj), "queueA", a.Obj.Spec.QueueName, "usageA", a.LocalQueueFSUsage,
+					"workloadB", klog.KObj(b.Obj), "queueB", b.Obj.Spec.QueueName, "usageB", b.LocalQueueFSUsage)
+				return cmp.Compare(*b.LocalQueueFSUsage, *a.LocalQueueFSUsage)
+			}
+			return 0
+		},
+		func() int {
+			return cmp.Compare(
+				priority.Priority(a.Obj),
+				priority.Priority(b.Obj),
+			)
+		},
+		func() int {
+			return quotaReservationTime(b.Obj, now).Compare(quotaReservationTime(a.Obj, now))
+		},
+		func() int {
+			// Arbitrary comparison for deterministic sorting.
+			return cmp.Compare(
+				a.Obj.UID,
+				b.Obj.UID,
+			)
+		},
+	)
 }
 
 func resourceUsagePreemptionEnabled(a, b *workload.Info) bool {
diff --git a/pkg/scheduler/preemption/fairsharing/ordering.go b/pkg/scheduler/preemption/fairsharing/ordering.go
index da6558e47..decc9dafa 100644
--- a/pkg/scheduler/preemption/fairsharing/ordering.go
+++ b/pkg/scheduler/preemption/fairsharing/ordering.go
@@ -152,7 +152,7 @@ func (t *TargetClusterQueueOrdering) nextTarget(cohort *schdcache.CohortSnapshot
 		case drs == highestCqDrs:
 			newCandWl := t.clusterQueueToTarget[cq.GetName()][0]
 			currentCandWl := t.clusterQueueToTarget[highestCq.GetName()][0]
-			if preemptioncommon.CandidatesOrdering(t.log, false, newCandWl, currentCandWl, t.preemptorCq.Name, time.Now()) {
+			if preemptioncommon.CandidatesOrdering(t.log, false, newCandWl, currentCandWl, t.preemptorCq.Name, time.Now()) < 0 {
 				highestCq = cq
 			}
 		case drs > highestCqDrs:
diff --git a/pkg/scheduler/preemption/preemption.go b/pkg/scheduler/preemption/preemption.go
index 89d102f66..6030bdd7a 100644
--- a/pkg/scheduler/preemption/preemption.go
+++ b/pkg/scheduler/preemption/preemption.go
@@ -19,7 +19,7 @@ package preemption
 import (
 	"context"
 	"fmt"
-	"sort"
+	"slices"
 	"sync/atomic"
 
 	"github.com/go-logr/logr"
@@ -386,8 +386,8 @@ func (p *Preemptor) fairPreemptions(preemptionCtx *preemptionCtx, strategies []f
 	if len(candidates) == 0 {
 		return nil
 	}
-	sort.Slice(candidates, func(i, j int) bool {
-		return preemptioncommon.CandidatesOrdering(preemptionCtx.log, p.enabledAfs, candidates[i], candidates[j], preemptionCtx.preemptorCQ.Name, p.clock.Now())
+	slices.SortFunc(candidates, func(a, b *workload.Info) int {
+		return preemptioncommon.CandidatesOrdering(preemptionCtx.log, p.enabledAfs, a, b, preemptionCtx.preemptorCQ.Name, p.clock.Now())
 	})
 	if logV := preemptionCtx.log.V(5); logV.Enabled() {
 		logV.Info("Simulating fair preemption", "candidates", workload.References(candidates), "resourcesRequiringPreemption", preemptionCtx.frsNeedPreemption.UnsortedList(), "preemptingWorkload", klog.KObj(preemptionCtx.preemptor.Obj))
diff --git a/pkg/util/cmp/cmp.go b/pkg/util/cmp/cmp.go
new file mode 100644
index 000000000..7ff5ceee3
--- /dev/null
+++ b/pkg/util/cmp/cmp.go
@@ -0,0 +1,47 @@
+/*
+Copyright 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 cmp
+
+// CompareBool compares two boolean values and returns:
+// - 0 if they are equal
+// - -1 if a is true and b is false
+// - 1 if a is false and b is true
+func CompareBool(a, b bool) int {
+	if a == b {
+		return 0
+	}
+
+	if a {
+		return -1
+	}
+
+	return 1
+}
+
+// LazyOr evaluates the provided functions in order and returns the first non-zero value.
+// It returns the zero value if all functions return zero values.
+// This is useful for implementing short-circuit evaluation in comparison functions.
+func LazyOr[T comparable](funcs ...func() T) T {
+	var zero T
+	for _, fn := range funcs {
+		n := fn()
+		if n != zero {
+			return n
+		}
+	}
+	return zero
+}
diff --git a/pkg/workloadslicing/workloadslicing.go b/pkg/workloadslicing/workloadslicing.go
index d11d185d2..3b21088a1 100644
--- a/pkg/workloadslicing/workloadslicing.go
+++ b/pkg/workloadslicing/workloadslicing.go
@@ -22,7 +22,6 @@ import (
 	"errors"
 	"fmt"
 	"slices"
-	"sort"
 
 	corev1 "k8s.io/api/core/v1"
 	apimeta "k8s.io/apimachinery/pkg/api/meta"
@@ -36,6 +35,7 @@ import (
 	"sigs.k8s.io/kueue/pkg/features"
 	"sigs.k8s.io/kueue/pkg/scheduler/preemption"
 	clientutil "sigs.k8s.io/kueue/pkg/util/client"
+	cmputil "sigs.k8s.io/kueue/pkg/util/cmp"
 	"sigs.k8s.io/kueue/pkg/util/pod"
 	"sigs.k8s.io/kueue/pkg/workload"
 )
@@ -125,12 +125,18 @@ func FindNotFinishedWorkloads(ctx context.Context, clnt client.Client, jobObject
 	// as a tiebreaker. This edge case is uncommon in production but can occur in
 	// integration or e2e tests where the original and scaled-up workloads are created
 	// in rapid succession.
-	sort.Slice(list.Items, func(i, j int) bool {
-		a, b := list.Items[i], list.Items[j]
-		if a.CreationTimestamp.Equal(&b.CreationTimestamp) {
-			return b.Annotations[WorkloadSliceReplacementFor] == string(workload.Key(&a))
-		}
-		return a.CreationTimestamp.Before(&b.CreationTimestamp)
+	slices.SortFunc(list.Items, func(a, b kueue.Workload) int {
+		return cmputil.LazyOr(
+			func() int {
+				return a.CreationTimestamp.Compare(b.CreationTimestamp.Time)
+			},
+			func() int {
+				if b.Annotations[WorkloadSliceReplacementFor] == string(workload.Key(&a)) {
+					return -1
+				}
+				return 1
+			},
+		)
 	})
 
 	// Filter out workloads with activated "Finished" condition.



Test patch:
diff --git a/pkg/scheduler/preemption/preemption_test.go b/pkg/scheduler/preemption/preemption_test.go
index 13c3b8368..28074c01a 100644
--- a/pkg/scheduler/preemption/preemption_test.go
+++ b/pkg/scheduler/preemption/preemption_test.go
@@ -19,7 +19,7 @@ package preemption
 import (
 	"context"
 	"fmt"
-	"sort"
+	"slices"
 	"sync"
 	"testing"
 	"time"
@@ -46,7 +46,7 @@ import (
 	"sigs.k8s.io/kueue/pkg/resources"
 	"sigs.k8s.io/kueue/pkg/scheduler/flavorassigner"
 	preemptioncommon "sigs.k8s.io/kueue/pkg/scheduler/preemption/common"
-	"sigs.k8s.io/kueue/pkg/util/slices"
+	utilslices "sigs.k8s.io/kueue/pkg/util/slices"
 	utiltesting "sigs.k8s.io/kueue/pkg/util/testing"
 	"sigs.k8s.io/kueue/pkg/workload"
 )
@@ -2773,7 +2773,7 @@ func TestFairPreemptions(t *testing.T) {
 					},
 				},
 			), snapshotWorkingCopy)
-			gotTargets := sets.New(slices.Map(targets, func(t **Target) string {
+			gotTargets := sets.New(utilslices.Map(targets, func(t **Target) string {
 				return targetKeyReason(workload.Key((*t).WorkloadInfo.Obj), (*t).Reason)
 			})...)
 			if diff := cmp.Diff(tc.wantPreempted, gotTargets, cmpopts.EquateEmpty()); diff != "" {
@@ -2897,10 +2897,10 @@ func TestCandidatesOrdering(t *testing.T) {
 	_, log := utiltesting.ContextWithLog(t)
 	for _, tc := range cases {
 		features.SetFeatureGateDuringTest(t, features.AdmissionFairSharing, tc.admissionFairSharingEnabled)
-		sort.Slice(tc.candidates, func(i int, j int) bool {
-			return preemptioncommon.CandidatesOrdering(log, tc.admissionFairSharingEnabled, &tc.candidates[i], &tc.candidates[j], kueue.ClusterQueueReference(preemptorCq), now)
+		slices.SortFunc(tc.candidates, func(a, b workload.Info) int {
+			return preemptioncommon.CandidatesOrdering(log, tc.admissionFairSharingEnabled, &a, &b, kueue.ClusterQueueReference(preemptorCq), now)
 		})
-		got := slices.Map(tc.candidates, func(c *workload.Info) workload.Reference {
+		got := utilslices.Map(tc.candidates, func(c *workload.Info) workload.Reference {
 			return workload.Reference(c.Obj.Name)
 		})
 		if diff := cmp.Diff(tc.wantCandidates, got); diff != "" {