Repository navigation
Conversation
There was a problem hiding this comment.
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
Open (5)
Callingassert.NotNil(t, ...)from multiple goroutines is not safe because it writes to the… · New These tests only assert thatSelect(...)returns non-nil, which would still pass even ifSelect… · New There are blank lines containing trailing whitespace (tabs/spaces). Please remove trailing… · New There are blank lines containing trailing whitespace (tabs/spaces). Please remove trailing… · New There are blank lines containing trailing whitespace (tabs/spaces). Please remove trailing… · New
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
Selectswitch 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.
| 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) | ||
| } |
| 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 Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
|
plz fix ci |
Ethan-Xingyue
left a comment
There was a problem hiding this comment.
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:
- Fix formatting so
golang ci lintpasses (sh goimports.sh). - Make the new tests fail when the two new
caselines are removed. - Fix the constant load-balance key (pre-existing), here or in a PR that lands first; otherwise
ConsistentHashLoadBalancepins every request to one TC. - Read the
rpc.Statuscounters atomically (pre-existing data race, now reachable viaLeastActiveLoadBalance). - Add
changes/dev.mdandchanges/dev_zh.mdentries and a release note, since configuredConsistentHashLoadBalance/LeastActiveLoadBalanceno longer fall back to random.
| func TestSelect_ConcurrentDispatch(t *testing.T) { | ||
| ctrl := gomock.NewController(t) | ||
| sessions := setupSessions(ctrl, 10, false) | ||
|
|
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
fd1322b to
0f7b446
Compare
0f7b446 to
9abe001
Compare
|
Plz resolve the conflicts. |



What this PR does:
Updates
loadbalance.Selectto explicitly handle and dispatchConsistentHashLoadBalanceandLeastActiveLoadBalancestrategies. Previously, these values fell through to the default branchand 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. goto ensure correctness across all load balancing configurations.Which issue(s) this PR fixes:
Fixes #1073
Special notes for your reviewer:
The implementations for
ConsistentHashLoadBalanceandLeastActiveLoadBalancewere alreadypresent in the codebase but lacked the necessary routing logic in the central
Selectfunction.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?: