This is an automated email from the ASF dual-hosted git repository.

Alanxtl 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 422a66ada fix(polaris): resolve nil pointer panic on startup and route 
miss (#3303) (#3697)
422a66ada is described below

commit 422a66ada0b99b6e3adca22f8d86c8ea6a92983c
Author: hysu <[email protected]>
AuthorDate: Mon Aug 24 00:03:20 2026 -0700

    fix(polaris): resolve nil pointer panic on startup and route miss (#3303) 
(#3697)
    
    * fix(polaris): resolve nil pointer panic on startup and route miss (#3303)
    
    - Disable route filtering during directory initialization.
    - Fall back to all instances in polarisRouter.Route on route miss.
    - Add nil guards to failfast/failsafe/failback cluster
    
    Signed-off-by: hysu <[email protected]>
    
    * refactor: replace perrors with fmt.Errorf per code review
    
    Signed-off-by: hysu <[email protected]>
    
    * refactor: replace fmt.Errorf with errors.New
    
    Signed-off-by: hysu <[email protected]>
    
    ---------
    
    Signed-off-by: hysu <[email protected]>
---
 cluster/cluster/failback/cluster_invoker.go |  3 +++
 cluster/cluster/failfast/cluster_invoker.go |  4 ++++
 cluster/cluster/failfast/cluster_test.go    | 24 ++++++++++++++++++++++++
 cluster/cluster/failsafe/cluster_invoker.go |  4 ++++
 cluster/router/polaris/router.go            |  6 ++++++
 registry/polaris/registry.go                |  4 ++++
 6 files changed, 45 insertions(+)

diff --git a/cluster/cluster/failback/cluster_invoker.go 
b/cluster/cluster/failback/cluster_invoker.go
index 6cb18eac9..09871dc42 100644
--- a/cluster/cluster/failback/cluster_invoker.go
+++ b/cluster/cluster/failback/cluster_invoker.go
@@ -190,6 +190,9 @@ func (invoker *failbackClusterInvoker) Invoke(ctx 
context.Context, invocation pr
        invoked := make([]protocolbase.Invoker, 0, len(invokers))
        ivk := invoker.DoSelect(loadBalance, invocation, invokers, invoked)
        // DO INVOKE
+       if ivk == nil {
+               return &result.RPCResult{Err: errors.New("invoker is nil")}
+       }
        res := ivk.Invoke(ctx, invocation)
        if res.Error() != nil {
                timerTask := newRetryTimerTask(loadBalance, invocation, 
invokers, ivk, invoker)
diff --git a/cluster/cluster/failfast/cluster_invoker.go 
b/cluster/cluster/failfast/cluster_invoker.go
index 4d04695ec..e475174ad 100644
--- a/cluster/cluster/failfast/cluster_invoker.go
+++ b/cluster/cluster/failfast/cluster_invoker.go
@@ -19,6 +19,7 @@ package failfast
 
 import (
        "context"
+       "errors"
 )
 
 import (
@@ -54,5 +55,8 @@ func (invoker *failfastClusterInvoker) Invoke(ctx 
context.Context, invocation pr
        }
 
        ivk := invoker.DoSelect(loadbalance, invocation, invokers, nil)
+       if ivk == nil {
+               return &result.RPCResult{Err: errors.New("invoker is nil")}
+       }
        return ivk.Invoke(ctx, invocation)
 }
diff --git a/cluster/cluster/failfast/cluster_test.go 
b/cluster/cluster/failfast/cluster_test.go
index 6e1fc7976..ff83d1a56 100644
--- a/cluster/cluster/failfast/cluster_test.go
+++ b/cluster/cluster/failfast/cluster_test.go
@@ -105,3 +105,27 @@ func TestFailfastInvokeFail(t *testing.T) {
        assert.Equal(t, "error", result.Error().Error())
        assert.Nil(t, result.Result())
 }
+
+// TestFailfastInvokeWithNoAvailableProvider verifies that invoking with
+// no available providers returns an error instead of panicking.
+func TestFailfastInvokeWithNoAvailableProvider(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       defer ctrl.Finish()
+
+       extension.SetLoadbalance("random", random.NewRandomLoadBalance)
+
+       // Simulate unavailable invoker causing DoSelect to return nil.
+       invoker := mock.NewMockInvoker(ctrl)
+       invoker.EXPECT().IsAvailable().Return(false).AnyTimes()
+       invoker.EXPECT().GetURL().Return(failfastUrl).AnyTimes()
+
+       staticDir := static.NewDirectory([]base.Invoker{invoker})
+       clusterInvoker := newFailfastCluster().Join(staticDir)
+
+       require.NotPanics(t, func() {
+               res := clusterInvoker.Invoke(context.Background(), 
&invocation.RPCInvocation{})
+               // Must return an error, not panic
+               require.NotNil(t, res)
+               require.Error(t, res.Error())
+       })
+}
diff --git a/cluster/cluster/failsafe/cluster_invoker.go 
b/cluster/cluster/failsafe/cluster_invoker.go
index 537690726..912f46212 100644
--- a/cluster/cluster/failsafe/cluster_invoker.go
+++ b/cluster/cluster/failsafe/cluster_invoker.go
@@ -19,6 +19,7 @@ package failsafe
 
 import (
        "context"
+       "errors"
 )
 
 import (
@@ -74,6 +75,9 @@ func (invoker *failsafeClusterInvoker) Invoke(ctx 
context.Context, invocation pr
 
        ivk := invoker.DoSelect(loadbalance, invocation, invokers, invoked)
        // DO INVOKE
+       if ivk == nil {
+               return &result.RPCResult{Err: errors.New("invoker is nil")}
+       }
        res = ivk.Invoke(ctx, invocation)
        if res.Error() != nil {
                // ignore
diff --git a/cluster/router/polaris/router.go b/cluster/router/polaris/router.go
index aa3bc32f9..21bb74773 100644
--- a/cluster/router/polaris/router.go
+++ b/cluster/router/polaris/router.go
@@ -178,6 +178,7 @@ func (p *polarisRouter) Route(invokers []base.Invoker, url 
*common.URL,
 
        resp, err := p.routerAPI.ProcessRouters(&req)
        if err != nil {
+               logger.Warnf("[Router][Polaris] route miss, fallback: %+v", err)
                return invokers
        }
 
@@ -188,6 +189,11 @@ func (p *polarisRouter) Route(invokers []base.Invoker, url 
*common.URL,
                }
        }
 
+       if len(ret) == 0 {
+               logger.Warn("[Router][Polaris] route rule yielded no invokers, 
fallback")
+               return invokers
+       }
+
        return ret
 }
 
diff --git a/registry/polaris/registry.go b/registry/polaris/registry.go
index b23bf67ce..a903980d2 100644
--- a/registry/polaris/registry.go
+++ b/registry/polaris/registry.go
@@ -270,6 +270,10 @@ func (pr *polarisRegistry) LoadSubscribeInstances(url 
*common.URL, notify regist
                GetInstancesRequest: model.GetInstancesRequest{
                        Service:   serviceName,
                        Namespace: pr.namespace,
+                       // Polaris evaluates routing rules during GetInstances 
by default. At subscribe
+                       // time there is no uid label, so the filter rejects 
all instances. Skip it here;
+                       // polarisRouter.Route handles per-call routing instead.
+                       SkipRouteFilter: true,
                },
        })
        if err != nil {

Reply via email to