Skip to content
Open
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
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ require (
golang.org/x/sys v0.36.0
golang.org/x/time v0.10.0
google.golang.org/api v0.198.0
google.golang.org/grpc v1.74.2
google.golang.org/grpc v1.75.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.33.4
k8s.io/apiextensions-apiserver v0.33.4
Expand Down Expand Up @@ -153,8 +153,8 @@ require (
golang.org/x/text v0.29.0 // indirect
golang.org/x/tools v0.37.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
Expand Down
15 changes: 8 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca h1:PupagGYwj8+I4ubCxcmcBRk3VlUWtTg5huQpZR9flmE=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
google.golang.org/api v0.198.0 h1:OOH5fZatk57iN0A7tjJQzt6aPfYQ1JiWkt1yGseazks=
google.golang.org/api v0.198.0/go.mod h1:/Lblzl3/Xqqk9hw/yS97TImKTUwnf1bv89v7+OagJzc=
Expand All @@ -517,17 +518,17 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
Expand Down
106 changes: 92 additions & 14 deletions pkg/activator/net/lb_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package net
import (
"context"
"math/rand"
"sort"
"sync"
)

Expand All @@ -31,27 +32,61 @@ import (
// and pointers therein are immutable.
type lbPolicy func(ctx context.Context, targets []*podTracker) (func(), *podTracker)

type TrackerLoad struct {
tracker *podTracker
inFlight uint64
}

// randomLBPolicy is a load balancer policy that picks a random target.
// This approximates the LB policy done by K8s Service (IPTables based).
func randomLBPolicy(_ context.Context, targets []*podTracker) (func(), *podTracker) {
return noop, targets[rand.Intn(len(targets))] //nolint:gosec
if len(targets) == 0 {
return noop, nil
}

// Filter out nil trackers to ensure uniform distribution
validTargets := make([]*podTracker, 0, len(targets))
for _, t := range targets {
if t != nil {
validTargets = append(validTargets, t)
}
}

if len(validTargets) == 0 {
return noop, nil
}

return noop, validTargets[rand.Intn(len(validTargets))] //nolint:gosec
}

// randomChoice2Policy implements the Power of 2 choices LB algorithm
func randomChoice2Policy(_ context.Context, targets []*podTracker) (func(), *podTracker) {
// Avoid random if possible.
l := len(targets)
// Filter out nil trackers first to ensure uniform distribution
validTargets := make([]*podTracker, 0, len(targets))
for _, t := range targets {
if t != nil {
validTargets = append(validTargets, t)
}
}

l := len(validTargets)
if l == 0 {
return noop, nil
}

// One tracker = no choice.
if l == 1 {
pick := targets[0]
pick := validTargets[0]
pick.increaseWeight()
return pick.decreaseWeight, pick
}
r1, r2 := 0, 1

// Two trackers - we know both contestants,
// otherwise pick 2 random unequal integers.
r1, r2 := 0, 1
if l > 2 {
r1, r2 = rand.Intn(l), rand.Intn(l-1) //nolint:gosec // We don't need cryptographic randomness here.
r1 = rand.Intn(l) //nolint:gosec // We don't need cryptographic randomness for load balancing
r2 = rand.Intn(l - 1) //nolint:gosec // We don't need cryptographic randomness here.
// shift second half of second rand.Intn down so we're picking
// from range of numbers other than r1.
// i.e. rand.Intn(l-1) range is now from range [0,r1),[r1+1,l).
Expand All @@ -60,7 +95,8 @@ func randomChoice2Policy(_ context.Context, targets []*podTracker) (func(), *pod
}
}

pick, alt := targets[r1], targets[r2]
pick, alt := validTargets[r1], validTargets[r2]

// Possible race here, but this policy is for CC=0,
// so fine.
if pick.getWeight() > alt.getWeight() {
Expand All @@ -75,17 +111,22 @@ func randomChoice2Policy(_ context.Context, targets []*podTracker) (func(), *pod
return pick.decreaseWeight, pick
}

// firstAvailableLBPolicy is a load balancer policy, that picks the first target
// firstAvailableLBPolicy is a load balancer policy that picks the first target
// that has capacity to serve the request right now.
func firstAvailableLBPolicy(ctx context.Context, targets []*podTracker) (func(), *podTracker) {
for _, t := range targets {
if cb, ok := t.Reserve(ctx); ok {
return cb, t
if t != nil {
if cb, ok := t.Reserve(ctx); ok {
return cb, t
}
}
}
return noop, nil
}

// roundRobinPolicy is a load balancer policy that tries all targets in order until one responds,
// using it as the target. It then continues in order from the last target to determine
// subsequent targets
func newRoundRobinPolicy() lbPolicy {
var (
mu sync.Mutex
Expand All @@ -104,13 +145,50 @@ func newRoundRobinPolicy() lbPolicy {
// round robin fashion.
for i := range l {
p := (idx + i) % l
if cb, ok := targets[p].Reserve(ctx); ok {
// We want to start with the next index.
idx = p + 1
return cb, targets[p]
if targets[p] != nil {
if cb, ok := targets[p].Reserve(ctx); ok {
// We want to start with the next index.
idx = p + 1
return cb, targets[p]
}
}
}
// We exhausted all the options...
return noop, nil
}
}

// leastConnectionsPolicy is a load balancer policy that uses the tracker with the
// least connections to determine the next target
func leastConnectionsPolicy(ctx context.Context, targets []*podTracker) (func(), *podTracker) {
trackerLoads := make([]TrackerLoad, len(targets))
for i, t := range targets {
if t != nil {
// Use the weight field as a proxy for in-flight connections
weight := t.weight.Load()
if weight < 0 {
weight = 0
}
// Safe conversion: weight is guaranteed to be non-negative after the check above
// Since weight is int32 and non-negative, it will always fit in uint64
// Use explicit check for gosec G115
var inFlight uint64
if weight >= 0 {
inFlight = uint64(weight)
}
trackerLoads[i] = TrackerLoad{tracker: t, inFlight: inFlight}
}
}
sort.Slice(trackerLoads, func(i, j int) bool {
return trackerLoads[i].inFlight < trackerLoads[j].inFlight
})
for _, tl := range trackerLoads {
if tl.tracker == nil {
continue
}
if cb, ok := tl.tracker.Reserve(ctx); ok {
return cb, tl.tracker
}
}
return noop, nil
}
Loading
Loading