Skip to content

bugfix: dispatch missing load balance strategies in Select (#1073) - #1196

Closed
paultanay wants to merge 6 commits into
apache:masterfrom
paultanay:fix-issue-1073
Closed

paultanay wants to merge 6 commits into
apache:masterfrom
paultanay:fix-issue-1073

Conversation

@paultanay

Copy link
Copy Markdown

What this PR does:

Updates loadbalance.Select to explicitly handle and dispatch ConsistentHashLoadBalance and
LeastActiveLoadBalance strategies. Previously, these values fell through to the default branch
and incorrectly routed to the random load balancing strategy. This PR also introduces a
comprehensive dispatcher-level regression test suite in pkg/remoting/loadbalance/loadbalance_test. go to ensure correctness across all load balancing configurations.

Which issue(s) this PR fixes:

Fixes #1073

Special notes for your reviewer:

The implementations for ConsistentHashLoadBalance and LeastActiveLoadBalance were already
present in the codebase but lacked the necessary routing logic in the central Select function.
The 7 newly added tests verify proper routing for all 5 strategies, confirm the fallback behavior,
validate empty/closed session handling, and cover concurrent dispatch execution. All 27 tests in
the package pass locally.

Does this PR introduce a user-facing change?:

NONE

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 1 High severity · 1 Medium severity · 3 Low severity

Open (5)
What changed in this PR

Fixes loadbalance.Select dispatch so ConsistentHashLoadBalance and LeastActiveLoadBalance no longer fall back to random, and adds regression tests for strategy selection behavior.

Changes:

  • Add missing Select switch cases for consistent-hash and least-active load balancing.
  • Introduce a new dispatcher-focused test suite covering strategy selection, fallback, empty/closed sessions, and concurrent dispatch.
File Description
pkg/​remoting/​loadbalance/​loadbalance.go Adds explicit dispatch branches for consistent-hash and least-active strategies.
pkg/​remoting/​loadbalance/​loadbalance_test.go Adds new tests intended to validate dispatcher behavior across strategies and concurrency.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +105 to +113
for i := 0; i < 100; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
strategy := strategies[idx%len(strategies)]
conn := Select(strategy, sessions, "test_xid")
assert.NotNil(t, conn)
}(i)
}
Comment on lines +61 to +66
func TestSelect_ConsistentHashLoadBalance(t *testing.T) {
ctrl := gomock.NewController(t)
sessions := setupSessions(ctrl, 3, false)
conn := Select(consistentHashLoadBalance, sessions, "test_xid")
assert.NotNil(t, conn)
}
func TestSelect_ConcurrentDispatch(t *testing.T) {
ctrl := gomock.NewController(t)
sessions := setupSessions(ctrl, 10, false)

consistentHashLoadBalance,
leastActiveLoadBalance,
}

assert.NotNil(t, conn)
}(i)
}

@codecov-commenter

codecov-commenter commented Sep 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.60%. Comparing base (7c162b6) to head (9abe001).
⚠️ Report is 19 commits behind head on master.

Files with missing lines Patch % Lines
...emoting/loadbalance/consistent_hash_loadbalance.go 40.00% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1196      +/-   ##
==========================================
+ Coverage   61.52%   61.60%   +0.07%     
==========================================
  Files         309      309              
  Lines       22625    22633       +8     
==========================================
+ Hits        13920    13943      +23     
+ Misses       7420     7408      -12     
+ Partials     1285     1282       -3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Ethan-Xingyue

Copy link
Copy Markdown
Contributor

plz fix ci

@Ethan-Xingyue Ethan-Xingyue left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dispatch change itself is correct, but this isn't ready to merge: CI is red, the new tests pass without the fix, and both newly reachable balancers carry pre-existing bugs that should be fixed first. Before merge:

  1. Fix formatting so golang ci lint passes (sh goimports.sh).
  2. Make the new tests fail when the two new case lines are removed.
  3. Fix the constant load-balance key (pre-existing), here or in a PR that lands first; otherwise ConsistentHashLoadBalance pins every request to one TC.
  4. Read the rpc.Status counters atomically (pre-existing data race, now reachable via LeastActiveLoadBalance).
  5. Add changes/dev.md and changes/dev_zh.md entries and a release note, since configured ConsistentHashLoadBalance/LeastActiveLoadBalance no longer fall back to random.

func TestSelect_ConcurrentDispatch(t *testing.T) {
ctrl := gomock.NewController(t)
sessions := setupSessions(ctrl, 10, false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI golang ci lint fails here: lines 95, 104 and 114 contain only a tab, which gofmt -s rejects. Running sh goimports.sh (or goimports -local seata.apache.org/seata-go/v2 -w) fixes them and also moves the seata.apache.org/seata-go/v2/pkg/remoting/mock import on line 26 into its own group.

ctrl := gomock.NewController(t)
sessions := setupSessions(ctrl, 3, false)
conn := Select(consistentHashLoadBalance, sessions, "test_xid")
assert.NotNil(t, conn)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests still pass with the two new case lines removed: every mock returns the same RemoteAddr() (line 34), NotNil holds for any strategy, and without resetConsistentHashForTest() the ring can be empty and fall back to random. Please give each mock a distinct address and assert strategy-specific results, e.g. mark all but one address busy with rpc.BeginCount and expect that one from leastActiveLoadBalance (line 71), and after resetConsistentHashForTest() expect repeated Select(consistentHashLoadBalance, ...) calls with the same xid to return the same connection (use an xid that doesn't hash past the last virtual node; see the wrap-around note on loadbalance.go).

case roundRobinLoadBalance:
return RoundRobinLoadBalance(sessions, xid)
case consistentHashLoadBalance:
return ConsistentHashLoadBalance(sessions, xid)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing, but this case makes it user-visible: xid is a constant here in production. SendSync/SendAsync pass the whole message.RpcMessage to selectSession/selectChannel (getty_remoting.go:53,71, grpc_remoting.go:52,71), so getXid returns "<invalid Value>" (getty) or "" (gRPC) for every message, and ConsistentHashLoadBalance sends every request, branch commit/rollback responses included, to the same TC. Please pass msg.Body there and, like the Java client, fall back to a random key when no xid is found, either here or in a PR that lands first.

case consistentHashLoadBalance:
return ConsistentHashLoadBalance(sessions, xid)
case leastActiveLoadBalance:
return LeastActiveLoadBalance(sessions, xid)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing, but only reachable through this case: LeastActiveLoadBalance calls rpc.GetStatus(addr).GetActive(), which reads Status.Active without synchronization while SendSync/SendAsync update it with atomic.AddInt32, so concurrent requests race on it. Please switch GetActive/GetTotal in pkg/remoting/rpc/rpc_status.go to atomic.LoadInt32.

return XidLoadBalance(sessions, xid)
case roundRobinLoadBalance:
return RoundRobinLoadBalance(sessions, xid)
case consistentHashLoadBalance:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking, pre-existing: ConsistentHashLoadBalance builds its ring once via a package-level sync.Once and rebuilds it only when a picked connection is closed, while both managers pass a fresh snapshot on every call. So if the first call sees no sessions, the ring stays empty and every pick falls back to random; TC nodes that join or reconnect later stay out of the ring until some ringed connection closes. Suggested follow-up: rebuild the ring whenever the snapshot's membership changes, as the Java ConsistentHashSelectorWrapper does.

return XidLoadBalance(sessions, xid)
case roundRobinLoadBalance:
return RoundRobinLoadBalance(sessions, xid)
case consistentHashLoadBalance:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking, pre-existing: when the key hashes past the last virtual node, pick returns RandomLoadBalance(...) instead of wrapping to sortedHashNodes[0], so those xids may go to a different TC on each call. Suggested follow-up: wrap around like the Java selector (tailMap.isEmpty() ? firstKey() : tailMap.firstKey()).

defer wg.Done()
strategy := strategies[idx%len(strategies)]
conn := Select(strategy, sessions, "test_xid")
assert.NotNil(t, conn)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking: assert.NotNil from these goroutines is fine (assert reports through t.Errorf, which is goroutine-safe), but nothing here calls rpc.BeginCount/EndCount while LeastActiveLoadBalance runs, which is the concurrency SendSync produces. Adding such a goroutine would cover the GetActive race under -race.

@github-actions github-actions Bot added documentation Improvements or additions to documentation milestone labels Sep 24, 2026
@thunguo

thunguo commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Plz resolve the conflicts.

@paultanay

paultanay commented Nov 5, 2026 •

Copy link
Copy Markdown
Author

Since #1205 has been merged and resolves #1073, this PR is superseded. I’m closing this PR to avoid duplicating the fix.

@paultanay paultanay closed this Nov 5, 2026
@github-actions github-actions Bot removed the milestone label Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working coding documentation Improvements or additions to documentation module/at remoting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

loadbalance.Select does not dispatch ConsistentHashLoadBalance or LeastActiveLoadBalance

5 participants