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()

Reply via email to