This is an automated email from the ASF dual-hosted git repository.
Similarityoung pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git
The following commit(s) were added to refs/heads/develop by this push:
new 106afed39 refactor(loadbalancer): gate snapshot fast paths behind an
internal opt-in token (#971)
106afed39 is described below
commit 106afed39e3447d64b5f63f6c537e6dbbd4cabc6
Author: 承潜 <[email protected]>
AuthorDate: Sat Aug 15 14:27:01 2026 +0800
refactor(loadbalancer): gate snapshot fast paths behind an internal opt-in
token (#971)
* refactor(loadbalancer): gate snapshot fast paths behind an internal
opt-in token
The zero-copy and healthy-only snapshot fast paths were opt-in via two
exported marker interfaces, ZeroCopySnapshotLoadBalancer and
HealthyOnlySnapshotLoadBalancer. Because Go interface satisfaction is
structural, any external load-balancer plugin could implement those method
names and silently opt into contracts that require never mutating or
retaining snapshot-owned endpoints — risking request-path mutation, stale
pointer retention, or data races on the shared snapshot.
Replace the two markers with a single internal opt-in: balancers expose
SnapshotOptIn() returning snapshotopt.Token, a struct in a new
internal/snapshotopt package. Only balancers rooted at
pkg/cluster/loadbalancer can import that package and name the return type,
so only trusted in-tree balancers can opt in. External plugins cannot
construct a Token, cannot satisfy the opt-in interface, and therefore
always fall through to the safe default: full snapshot, defensively copied.
The public extension surface (SnapshotLoadBalancer, PickContext) is
unchanged; bundled balancers (RoundRobin, Rand, WeightRandom, Maglev,
RingHash) keep their fast path via the new token. Behavior is otherwise
identical.
A new test (TestExternalLikeBalancerCannotOptIntoFastPaths) reproduces an
out-of-tree plugin carrying the old method names and asserts it is treated
as untrusted: full snapshot, defensive copy, mutation does not escape.
Closes #941
* fix(loadbalancer): keep deprecated snapshot markers compatible
* fix(loadbalancer): close embedding bypass and drop unreleased markers
The internal-token opt-in stops out-of-tree code from declaring
SnapshotOptIn, but an external plugin could still embed an in-tree
balancer and gain the method through promotion, silently re-entering the
zero-copy / healthy-only fast paths. snapshotOptIn now also verifies the
balancer's concrete type lives under pkg/cluster/loadbalancer, so a
promoted method from an embedded balancer no longer grants trust.
Remove the HealthyOnlySnapshotLoadBalancer and ZeroCopySnapshotLoadBalancer
marker interfaces along with the Use* methods and var _ assertions on the
bundled balancers. The markers were introduced in #932 and never shipped
in a release, so there is no external caller to stay compatible with;
keeping them contradicted the PR goal and coupled the balancers to
deprecated, runtime-ignored APIs.
Add an external-package regression (load_balancer_external_test.go) that
embeds an in-tree balancer and asserts it still receives the full
snapshot and a defensive copy.
---
pkg/cluster/cluster.go | 8 +-
.../internal/snapshotopt/snapshotopt.go | 38 ++++++++++
pkg/cluster/loadbalancer/load_balancer.go | 62 +++++++++++----
.../loadbalancer/load_balancer_external_test.go | 87 ++++++++++++++++++++++
pkg/cluster/loadbalancer/load_balancer_test.go | 84 ++++++++++++++++++++-
pkg/cluster/loadbalancer/maglev/maglev_hash.go | 9 +--
.../loadbalancer/rand/load_balancer_rand.go | 9 +--
pkg/cluster/loadbalancer/ringhash/ring_hash.go | 9 +--
pkg/cluster/loadbalancer/roundrobin/round_robin.go | 9 +--
.../loadbalancer/weightrandom/weight_random.go | 9 +--
10 files changed, 273 insertions(+), 51 deletions(-)
diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
index 6608e546b..a0adefbbe 100644
--- a/pkg/cluster/cluster.go
+++ b/pkg/cluster/cluster.go
@@ -222,10 +222,10 @@ type EndpointSnapshot struct {
// only because *Endpoint and the underlying maps are treated as
// read-only after publication.
// Any code path that mutates them in place will leak state across
-// all snapshots alive at the time of the mutation. The
-// ZeroCopySnapshotLoadBalancer marker on load balancers exists
-// precisely to opt into this contract; do not introduce new
-// in-place mutation on snapshot-owned objects.
+// all snapshots alive at the time of the mutation. The zero-copy
+// opt-in on load balancers (snapshotopt.Token.ZeroCopy, set via
+// SnapshotOptIn) exists precisely to opt into this contract; do not
+// introduce new in-place mutation on snapshot-owned objects.
func newEndpointSnapshot(config *model.ClusterConfig, previous
*EndpointSnapshot, inheritRuntimeHealth bool) *EndpointSnapshot {
var endpoints []*model.Endpoint
clusterName := ""
diff --git a/pkg/cluster/loadbalancer/internal/snapshotopt/snapshotopt.go
b/pkg/cluster/loadbalancer/internal/snapshotopt/snapshotopt.go
new file mode 100644
index 000000000..0d4a4ba1f
--- /dev/null
+++ b/pkg/cluster/loadbalancer/internal/snapshotopt/snapshotopt.go
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 snapshotopt carries the opt-in token for snapshot load-balancer fast
+// paths. It lives under internal/ on purpose: only in-tree balancers (packages
+// rooted at pkg/cluster/loadbalancer) can import it and therefore name Token,
+// so only trusted balancers can declare the SnapshotOptIn method that hands
+// these flags to the runtime. External plugins cannot construct a Token, so
+// they cannot opt into zero-copy or healthy-only access and always receive
+// defensively copied snapshot endpoints.
+package snapshotopt
+
+// Token tells the snapshot pick path which fast paths a trusted balancer opts
+// into. The zero value (both false) is the safe default: full snapshot,
+// defensively copied.
+type Token struct {
+ // ZeroCopy is true when the balancer never mutates or retains snapshot
+ // endpoints, so the runtime may hand it the snapshot-owned slices
directly
+ // instead of defensive copies.
+ ZeroCopy bool
+ // HealthyOnly is true when the balancer never reads
PickContext.AllEndpoints,
+ // so the runtime can skip populating the full (including unhealthy)
set.
+ HealthyOnly bool
+}
diff --git a/pkg/cluster/loadbalancer/load_balancer.go
b/pkg/cluster/loadbalancer/load_balancer.go
index 295de13c7..4b577eab8 100644
--- a/pkg/cluster/loadbalancer/load_balancer.go
+++ b/pkg/cluster/loadbalancer/load_balancer.go
@@ -18,11 +18,14 @@
package loadbalancer
import (
+ "reflect"
+ "strings"
"sync"
"sync/atomic"
)
import (
+
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/internal/snapshotopt"
"github.com/apache/dubbo-go-pixiu/pkg/model"
)
@@ -77,18 +80,48 @@ type SnapshotLoadBalancer interface {
HandlerWithSnapshot(c PickContext, policy model.LbPolicy)
*model.Endpoint
}
-// HealthyOnlySnapshotLoadBalancer marks snapshot-aware balancers that do not
-// need PickContext.AllEndpoints. Unmarked snapshot balancers keep receiving
-// the full snapshot for compatibility with custom implementations.
-type HealthyOnlySnapshotLoadBalancer interface {
- UseHealthyEndpointsOnly() bool
+// snapshotOptInBalancer is the internal opt-in surface for trusted, in-tree
+// snapshot balancers. The method returns snapshotopt.Token, whose type lives
in
+// an internal package, so only balancers under pkg/cluster/loadbalancer can
+// implement this interface. External plugins cannot name the return type and
+// therefore always fall through to the safe default (full snapshot,
defensively
+// copied). This is the trust boundary described in issue #941.
+type snapshotOptInBalancer interface {
+ SnapshotOptIn() snapshotopt.Token
}
-// ZeroCopySnapshotLoadBalancer marks trusted balancers that never mutate or
-// retain snapshot endpoints. Other snapshot balancers receive defensive
-// copies.
-type ZeroCopySnapshotLoadBalancer interface {
- UseZeroCopySnapshot() bool
+// inTreeLoadBalancerPkg is the package prefix that scopes trusted balancers.
+const inTreeLoadBalancerPkg =
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer"
+
+// snapshotOptIn resolves a balancer's opt-in flags. Balancers that do not opt
+// in (including every external plugin, which cannot construct a Token) get the
+// zero value: no zero-copy, full snapshot.
+//
+// The internal return type already keeps out-of-tree code from declaring
+// SnapshotOptIn directly. The trust check additionally rejects external types
+// that gain the method via embedding an in-tree balancer (method promotion
+// would otherwise let them satisfy snapshotOptInBalancer).
+func snapshotOptIn(balancer LoadBalancer) snapshotopt.Token {
+ optIn, ok := balancer.(snapshotOptInBalancer)
+ if !ok || !isInTreeBalancer(balancer) {
+ return snapshotopt.Token{}
+ }
+ return optIn.SnapshotOptIn()
+}
+
+// isInTreeBalancer reports whether the balancer's concrete type is defined
+// under the in-tree load-balancer package tree. An external plugin that embeds
+// an in-tree balancer keeps its own package path here, so it is rejected.
+func isInTreeBalancer(balancer LoadBalancer) bool {
+ t := reflect.TypeOf(balancer)
+ for t != nil && t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ if t == nil {
+ return false
+ }
+ pkg := t.PkgPath()
+ return pkg == inTreeLoadBalancerPkg || strings.HasPrefix(pkg,
inTreeLoadBalancerPkg+"/")
}
// LoadBalancerStrategy load balancer strategy mode
@@ -114,10 +147,10 @@ func PickEndpoint(balancer LoadBalancer, context
PickContext, policy model.LbPol
}
// NeedsAllEndpoints reports whether a snapshot-aware balancer should receive
-// PickContext.AllEndpoints on the request path.
+// PickContext.AllEndpoints on the request path. Only trusted in-tree balancers
+// can opt out (HealthyOnly); external plugins always receive the full
snapshot.
func NeedsAllEndpoints(balancer LoadBalancer) bool {
- healthyOnly, ok := balancer.(HealthyOnlySnapshotLoadBalancer)
- return !ok || !healthyOnly.UseHealthyEndpointsOnly()
+ return !snapshotOptIn(balancer).HealthyOnly
}
// ConsistentHashForHealthyEndpoints returns a consistent hash view that only
@@ -143,8 +176,7 @@ func pickEndpoint(balancer LoadBalancer, context
PickContext, policy model.LbPol
}
if snapshotBalancer, ok := balancer.(SnapshotLoadBalancer); ok {
snapshotContext := context
- zeroCopy, ok := balancer.(ZeroCopySnapshotLoadBalancer)
- if !ok || !zeroCopy.UseZeroCopySnapshot() {
+ if !snapshotOptIn(balancer).ZeroCopy {
snapshotContext = defensiveSnapshotPickContext(context)
}
endpoint :=
snapshotBalancer.HandlerWithSnapshot(snapshotContext, policy)
diff --git a/pkg/cluster/loadbalancer/load_balancer_external_test.go
b/pkg/cluster/loadbalancer/load_balancer_external_test.go
new file mode 100644
index 000000000..a66caac35
--- /dev/null
+++ b/pkg/cluster/loadbalancer/load_balancer_external_test.go
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 loadbalancer_test
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer"
+ "github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/roundrobin"
+ "github.com/apache/dubbo-go-pixiu/pkg/model"
+)
+
+// embeddingPlugin is an out-of-tree balancer that embeds an in-tree balancer.
+// Method promotion gives it SnapshotOptIn, so structural interface
satisfaction
+// alone would wrongly grant it the snapshot fast paths. The runtime trust
check
+// keys on the concrete type's package, which is this external test package, so
+// the plugin must still be treated as untrusted.
+type embeddingPlugin struct {
+ roundrobin.RoundRobin
+ seenHealthyEndpoints []*model.Endpoint
+}
+
+func (p *embeddingPlugin) HandlerWithSnapshot(c loadbalancer.PickContext, _
model.LbPolicy) *model.Endpoint {
+ p.seenHealthyEndpoints = c.HealthyEndpoints
+ if len(c.HealthyEndpoints) == 0 {
+ return nil
+ }
+ c.HealthyEndpoints[0].Metadata["weight"] = "mutated"
+ return c.HealthyEndpoints[0]
+}
+
+// TestEmbeddingInTreeBalancerCannotOptIntoFastPaths is the trust-boundary
+// regression for the embedding bypass: an external type that promotes
+// SnapshotOptIn from an embedded in-tree balancer must not receive the
+// healthy-only or zero-copy fast paths.
+func TestEmbeddingInTreeBalancerCannotOptIntoFastPaths(t *testing.T) {
+ plugin := &embeddingPlugin{}
+
+ // HealthyOnly fast path must be denied: external embedder still gets
the
+ // full snapshot.
+ assert.True(t, loadbalancer.NeedsAllEndpoints(plugin),
+ "embedding an in-tree balancer must not promote the
healthy-only fast path")
+
+ healthy := &model.Endpoint{ID: "healthy", Metadata:
map[string]string{"weight": "1"}}
+ cluster := &model.ClusterConfig{
+ Name: "embedding-trust-boundary",
+ Endpoints: []*model.Endpoint{healthy},
+ }
+
+ got := loadbalancer.PickEndpoint(plugin, loadbalancer.PickContext{
+ AllEndpoints: []*model.Endpoint{healthy},
+ Config: cluster,
+ HealthyEndpoints: []*model.Endpoint{healthy},
+ }, nil)
+
+ // ZeroCopy fast path must be denied: the plugin mutates what it
receives,
+ // and that mutation must not escape to the snapshot-owned endpoint.
+ if assert.NotNil(t, got) {
+ assert.NotSame(t, healthy, got, "embedder must receive a
defensive copy")
+ }
+ if assert.Len(t, plugin.seenHealthyEndpoints, 1) {
+ assert.NotSame(t, healthy, plugin.seenHealthyEndpoints[0])
+ }
+ assert.Equal(t, map[string]string{"weight": "1"}, healthy.Metadata,
+ "embedder mutation must not escape to the snapshot-owned
endpoint")
+}
diff --git a/pkg/cluster/loadbalancer/load_balancer_test.go
b/pkg/cluster/loadbalancer/load_balancer_test.go
index 88b0eb4f9..a1fa9ed1b 100644
--- a/pkg/cluster/loadbalancer/load_balancer_test.go
+++ b/pkg/cluster/loadbalancer/load_balancer_test.go
@@ -30,6 +30,7 @@ import (
)
import (
+
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/internal/snapshotopt"
"github.com/apache/dubbo-go-pixiu/pkg/model"
)
@@ -44,6 +45,37 @@ type mutatingSnapshotLoadBalancer struct{}
type unhealthySnapshotLoadBalancer struct{}
type healthyOnlySnapshotLoadBalancer struct{}
+// externalLikeSnapshotLoadBalancer mimics an out-of-tree plugin that copied
the
+// pre-#941 marker method names (UseZeroCopySnapshot /
UseHealthyEndpointsOnly).
+// Because the opt-in now flows through SnapshotOptIn returning an
internal-only
+// token, these methods grant no fast path: the balancer must be treated as
+// untrusted (defensive copy, full snapshot). It also mutates the endpoints it
+// receives so the test can prove it got a copy.
+type externalLikeSnapshotLoadBalancer struct {
+ seenAllEndpoints []*model.Endpoint
+ seenHealthyEndpoints []*model.Endpoint
+}
+
+func (*externalLikeSnapshotLoadBalancer) UseZeroCopySnapshot() bool {
return true }
+func (*externalLikeSnapshotLoadBalancer) UseHealthyEndpointsOnly() bool {
return true }
+
+func (*externalLikeSnapshotLoadBalancer) Handler(_ *model.ClusterConfig, _
model.LbPolicy) *model.Endpoint {
+ return nil
+}
+
+func (b *externalLikeSnapshotLoadBalancer) HandlerWithSnapshot(c PickContext,
_ model.LbPolicy) *model.Endpoint {
+ b.seenAllEndpoints = c.AllEndpoints
+ b.seenHealthyEndpoints = c.HealthyEndpoints
+ if len(c.HealthyEndpoints) == 0 {
+ return nil
+ }
+ c.HealthyEndpoints[0].Metadata["weight"] = "mutated"
+ if len(c.AllEndpoints) > 1 {
+ c.AllEndpoints[1].Metadata["weight"] = "mutated-unhealthy"
+ }
+ return c.HealthyEndpoints[0]
+}
+
var _ LoadBalancer = (*legacyLoadBalancer)(nil)
// healthyByIDIndex is a test double for the snapshot's O(1) healthy-by-ID
@@ -153,8 +185,8 @@ func (healthyOnlySnapshotLoadBalancer)
HandlerWithSnapshot(_ PickContext, _ mode
return nil
}
-func (healthyOnlySnapshotLoadBalancer) UseHealthyEndpointsOnly() bool {
- return true
+func (healthyOnlySnapshotLoadBalancer) SnapshotOptIn() snapshotopt.Token {
+ return snapshotopt.Token{HealthyOnly: true}
}
func (b *blockingLegacyLoadBalancer) Handler(c *model.ClusterConfig, _
model.LbPolicy) *model.Endpoint {
@@ -357,6 +389,54 @@ func
TestNeedsAllEndpointsKeepsCompatibilityForUnmarkedSnapshotLoadBalancer(t *t
assert.False(t, NeedsAllEndpoints(healthyOnlySnapshotLoadBalancer{}))
}
+// TestExternalLikeBalancerCannotOptIntoFastPaths is the trust-boundary
+// regression for issue #941. A balancer that reproduces the old marker method
+// names but cannot produce the internal opt-in token must be treated as
+// untrusted: it receives the full snapshot (NeedsAllEndpoints true) and a
+// defensive copy (its mutation must not escape to the caller's endpoints).
+func TestExternalLikeBalancerCannotOptIntoFastPaths(t *testing.T) {
+ balancer := &externalLikeSnapshotLoadBalancer{}
+ assert.True(t, NeedsAllEndpoints(balancer),
+ "external-like balancer must not opt out of AllEndpoints via
the old method name")
+
+ healthy := &model.Endpoint{
+ ID: "healthy",
+ Metadata: map[string]string{"weight": "1"},
+ }
+ unhealthy := &model.Endpoint{
+ ID: "unhealthy",
+ Metadata: map[string]string{"weight": "2"},
+ UnHealthy: true,
+ }
+ cluster := &model.ClusterConfig{
+ Name: "external-like-trust-boundary",
+ Endpoints: []*model.Endpoint{healthy, unhealthy},
+ }
+
+ got := PickEndpoint(balancer, PickContext{
+ AllEndpoints: []*model.Endpoint{healthy, unhealthy},
+ Config: cluster,
+ HealthyEndpoints: []*model.Endpoint{healthy},
+ }, nil)
+
+ if assert.NotNil(t, got) {
+ assert.NotSame(t, healthy, got, "untrusted balancer must
receive a defensive copy")
+ }
+ if assert.Len(t, balancer.seenAllEndpoints, 2, "untrusted balancer must
receive the full snapshot") {
+ assert.NotSame(t, healthy, balancer.seenAllEndpoints[0])
+ assert.NotSame(t, unhealthy, balancer.seenAllEndpoints[1])
+ assert.Equal(t, "healthy", balancer.seenAllEndpoints[0].ID)
+ assert.Equal(t, "unhealthy", balancer.seenAllEndpoints[1].ID)
+ }
+ if assert.Len(t, balancer.seenHealthyEndpoints, 1) {
+ assert.NotSame(t, healthy, balancer.seenHealthyEndpoints[0])
+ }
+ assert.Equal(t, map[string]string{"weight": "1"}, healthy.Metadata,
+ "untrusted balancer mutation must not escape to the
snapshot-owned endpoint")
+ assert.Equal(t, map[string]string{"weight": "2"}, unhealthy.Metadata,
+ "untrusted balancer mutation must not escape to the full
snapshot endpoint")
+}
+
func TestDefensiveSnapshotPickContextClearsHealthyByID(t *testing.T) {
healthy := []*model.Endpoint{
{ID: "ep-1", Address: model.SocketAddress{Address: "127.0.0.1",
Port: 8080}},
diff --git a/pkg/cluster/loadbalancer/maglev/maglev_hash.go
b/pkg/cluster/loadbalancer/maglev/maglev_hash.go
index 6eca2d903..6cf7383f6 100644
--- a/pkg/cluster/loadbalancer/maglev/maglev_hash.go
+++ b/pkg/cluster/loadbalancer/maglev/maglev_hash.go
@@ -19,6 +19,7 @@ package maglev
import (
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer"
+
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/internal/snapshotopt"
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/ringhash"
"github.com/apache/dubbo-go-pixiu/pkg/logger"
"github.com/apache/dubbo-go-pixiu/pkg/model"
@@ -50,12 +51,8 @@ func NewMaglevHash(config model.ConsistentHash, endpoints
[]*model.Endpoint) mod
type MaglevHash struct{}
-func (MaglevHash) UseHealthyEndpointsOnly() bool {
- return true
-}
-
-func (MaglevHash) UseZeroCopySnapshot() bool {
- return true
+func (MaglevHash) SnapshotOptIn() snapshotopt.Token {
+ return snapshotopt.Token{ZeroCopy: true, HealthyOnly: true}
}
func (m MaglevHash) Handler(c *model.ClusterConfig, policy model.LbPolicy)
*model.Endpoint {
diff --git a/pkg/cluster/loadbalancer/rand/load_balancer_rand.go
b/pkg/cluster/loadbalancer/rand/load_balancer_rand.go
index 6335828ce..98d48f56d 100644
--- a/pkg/cluster/loadbalancer/rand/load_balancer_rand.go
+++ b/pkg/cluster/loadbalancer/rand/load_balancer_rand.go
@@ -23,6 +23,7 @@ import (
import (
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer"
+
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/internal/snapshotopt"
"github.com/apache/dubbo-go-pixiu/pkg/model"
)
@@ -32,12 +33,8 @@ func init() {
type Rand struct{}
-func (Rand) UseHealthyEndpointsOnly() bool {
- return true
-}
-
-func (Rand) UseZeroCopySnapshot() bool {
- return true
+func (Rand) SnapshotOptIn() snapshotopt.Token {
+ return snapshotopt.Token{ZeroCopy: true, HealthyOnly: true}
}
// randIntn lets tests replace randomness with deterministic choices.
diff --git a/pkg/cluster/loadbalancer/ringhash/ring_hash.go
b/pkg/cluster/loadbalancer/ringhash/ring_hash.go
index fcd62d019..d9bdbe839 100644
--- a/pkg/cluster/loadbalancer/ringhash/ring_hash.go
+++ b/pkg/cluster/loadbalancer/ringhash/ring_hash.go
@@ -27,6 +27,7 @@ import (
import (
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer"
+
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/internal/snapshotopt"
"github.com/apache/dubbo-go-pixiu/pkg/logger"
"github.com/apache/dubbo-go-pixiu/pkg/model"
)
@@ -58,12 +59,8 @@ func NewRingHash(config model.ConsistentHash, endpoints
[]*model.Endpoint) model
type RingHashing struct{}
-func (RingHashing) UseHealthyEndpointsOnly() bool {
- return true
-}
-
-func (RingHashing) UseZeroCopySnapshot() bool {
- return true
+func (RingHashing) SnapshotOptIn() snapshotopt.Token {
+ return snapshotopt.Token{ZeroCopy: true, HealthyOnly: true}
}
func (r RingHashing) Handler(c *model.ClusterConfig, policy model.LbPolicy)
*model.Endpoint {
diff --git a/pkg/cluster/loadbalancer/roundrobin/round_robin.go
b/pkg/cluster/loadbalancer/roundrobin/round_robin.go
index 6680a2412..ecd787ddb 100644
--- a/pkg/cluster/loadbalancer/roundrobin/round_robin.go
+++ b/pkg/cluster/loadbalancer/roundrobin/round_robin.go
@@ -23,6 +23,7 @@ import (
import (
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer"
+
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/internal/snapshotopt"
"github.com/apache/dubbo-go-pixiu/pkg/model"
)
@@ -32,12 +33,8 @@ func init() {
type RoundRobin struct{}
-func (RoundRobin) UseHealthyEndpointsOnly() bool {
- return true
-}
-
-func (RoundRobin) UseZeroCopySnapshot() bool {
- return true
+func (RoundRobin) SnapshotOptIn() snapshotopt.Token {
+ return snapshotopt.Token{ZeroCopy: true, HealthyOnly: true}
}
func (r RoundRobin) Handler(c *model.ClusterConfig, policy model.LbPolicy)
*model.Endpoint {
diff --git a/pkg/cluster/loadbalancer/weightrandom/weight_random.go
b/pkg/cluster/loadbalancer/weightrandom/weight_random.go
index e81785bdc..2e8d2b949 100644
--- a/pkg/cluster/loadbalancer/weightrandom/weight_random.go
+++ b/pkg/cluster/loadbalancer/weightrandom/weight_random.go
@@ -24,6 +24,7 @@ import (
import (
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer"
+
"github.com/apache/dubbo-go-pixiu/pkg/cluster/loadbalancer/internal/snapshotopt"
"github.com/apache/dubbo-go-pixiu/pkg/model"
)
@@ -40,12 +41,8 @@ type weightedEndpoint struct {
// It assigns weights to endpoints and uses these weights to influence the
probability of selection.
type WeightRandom struct{}
-func (WeightRandom) UseHealthyEndpointsOnly() bool {
- return true
-}
-
-func (WeightRandom) UseZeroCopySnapshot() bool {
- return true
+func (WeightRandom) SnapshotOptIn() snapshotopt.Token {
+ return snapshotopt.Token{ZeroCopy: true, HealthyOnly: true}
}
func (w WeightRandom) Handler(c *model.ClusterConfig, policy model.LbPolicy)
*model.Endpoint {