This is an automated email from the ASF dual-hosted git repository.
alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new a0b31e564 fix(protocol): fix panic caused by race condition during
invoker destruction (#3184)
a0b31e564 is described below
commit a0b31e564dc010b4e7f5de5518398ead5d8eb2ab
Author: 花国栋 <[email protected]>
AuthorDate: Sun Mar 1 20:38:47 2026 +0800
fix(protocol): fix panic caused by race condition during invoker
destruction (#3184)
* fix(protocol): fix panic caused by race condition during invoker
destruction
* fix(protocol): resolve data race in BaseInvoker using atomic pointer
* test(router): update assertions to support atomic invoker
* trigger ci: rerun checks
* fix: update internal constant
---
cluster/router/condition/router_test.go | 4 +++-
protocol/base/base_invoker.go | 26 ++++++++++++++++++--------
protocol/base/base_invoker_test.go | 5 ++++-
3 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/cluster/router/condition/router_test.go
b/cluster/router/condition/router_test.go
index 0750a5df9..5c6da3b2d 100644
--- a/cluster/router/condition/router_test.go
+++ b/cluster/router/condition/router_test.go
@@ -414,7 +414,9 @@ func TestRouteReturn(t *testing.T) {
resVal := len(filterInvokers)
assert.Equal(t, data.wantVal, resVal)
- assert.Equal(t, wantInvokers, filterInvokers)
+ for i := range filterInvokers {
+ assert.Equal(t,
wantInvokers[i].GetURL().String(), filterInvokers[i].GetURL().String())
+ }
})
}
}
diff --git a/protocol/base/base_invoker.go b/protocol/base/base_invoker.go
index 77ea05cfd..c7071a926 100644
--- a/protocol/base/base_invoker.go
+++ b/protocol/base/base_invoker.go
@@ -35,7 +35,14 @@ import (
"dubbo.apache.org/dubbo-go/v3/protocol/result"
)
+const (
+ protocolDestroyed = "dubbo-go-internal-invoker-destroyed"
+)
+
var (
+ // emptyURL is a global placeholder to prevent nil pointer dereferences
during invoker destruction.
+ // It is returned by GetURL() when the invoker is destroyed, ensuring
that concurrent readers receive a safe, initialized object.
+ emptyURL =
common.NewURLWithOptions(common.WithProtocol(protocolDestroyed))
ErrClientClosed = perrors.New("remoting client has closed")
ErrNoReply = perrors.New("request need @response")
ErrDestroyedInvoker = perrors.New("request Destroyed invoker")
@@ -53,16 +60,15 @@ type Invoker interface {
// BaseInvoker provides default invoker implements Invoker
type BaseInvoker struct {
- url *common.URL
+ url uatomic.Pointer[common.URL]
available uatomic.Bool
destroyed uatomic.Bool
}
// NewBaseInvoker creates a new BaseInvoker
func NewBaseInvoker(url *common.URL) *BaseInvoker {
- ivk := &BaseInvoker{
- url: url,
- }
+ ivk := &BaseInvoker{}
+ ivk.url.Store(url)
ivk.available.Store(true)
ivk.destroyed.Store(false)
@@ -71,7 +77,11 @@ func NewBaseInvoker(url *common.URL) *BaseInvoker {
// GetURL gets base invoker URL
func (bi *BaseInvoker) GetURL() *common.URL {
- return bi.url
+ u := bi.url.Load()
+ if u == nil {
+ return emptyURL
+ }
+ return u
}
// IsAvailable gets available flag
@@ -94,13 +104,13 @@ func (bi *BaseInvoker) Destroy() {
logger.Infof("Destroy invoker: %s", bi.GetURL())
bi.destroyed.Store(true)
bi.available.Store(false)
- bi.url = nil
+ bi.url.Store(nil)
}
func (bi *BaseInvoker) String() string {
- if bi.url != nil {
+ if u := bi.url.Load(); u != nil {
return fmt.Sprintf("invoker{protocol: %s, host: %s:%s, path:
%s}",
- bi.url.Protocol, bi.url.Ip, bi.url.Port, bi.url.Path)
+ u.Protocol, u.Ip, u.Port, u.Path)
}
return fmt.Sprintf("%#v", bi)
}
diff --git a/protocol/base/base_invoker_test.go
b/protocol/base/base_invoker_test.go
index e1fddefe6..6dc5478bd 100644
--- a/protocol/base/base_invoker_test.go
+++ b/protocol/base/base_invoker_test.go
@@ -72,7 +72,10 @@ func TestBaseInvokerWithFullURL(t *testing.T) {
ivk.Destroy()
assert.False(t, ivk.IsAvailable())
assert.True(t, ivk.IsDestroyed())
- assert.Nil(t, ivk.GetURL())
+
+ safeURL := ivk.GetURL()
+ assert.NotNil(t, safeURL)
+ assert.Equal(t, "dubbo-go-internal-invoker-destroyed", safeURL.Protocol)
// Test String method after destroy (url is nil)
str = ivk.String()