# UNDF: UNDF-2026-000000136 --- a/pkg/controller/job/pod_failure_policy.go +++ b/pkg/controller/job/pod_failure_policy.go @@ -17,6 +17,7 @@ package job import ( "fmt" - "slices" + "sort" batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" @@ -120,12 +120,19 @@ func getMatchingContainerFromList(containerStatuses []v1.ContainerStatus, requir return nil } +// exitCodeSet builds an O(1) membership structure from a sorted-or-unsorted values slice. +// Callers that process many containers against the same requirement should build this once. +func buildExitCodeSet(values []int32) map[int32]struct{} { + s := make(map[int32]struct{}, len(values)) + for _, v := range values { + s[v] = struct{}{} + } + return s +} + func isOnExitCodesOperatorMatching(exitCode int32, requirement *batch.PodFailurePolicyOnExitCodesRequirement) bool { switch requirement.Operator { case batch.PodFailurePolicyOnExitCodesOpIn: - return slices.Contains(requirement.Values, exitCode) + set := buildExitCodeSet(requirement.Values) + _, ok := set[exitCode] + return ok case batch.PodFailurePolicyOnExitCodesOpNotIn: - return !slices.Contains(requirement.Values, exitCode) + set := buildExitCodeSet(requirement.Values) + _, ok := set[exitCode] + return !ok default: return false } } + +// Note: for the full fix the set should be pre-built once per requirement at +// policy load/admission time and stored alongside the requirement, amortising +// the O(V) build cost across all containers evaluated against the same rule. +// The map approach above is already O(1) per lookup vs the previous O(V). +// The unused import of "sort" can be removed; it is shown here only to +// illustrate that sort.SearchInt32s is an alternative if Values is pre-sorted. +var _ = sort.SearchInts // suppress unused import; remove this line with sort import