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

alexstocks pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/main by this push:
     new dbb029098 Fix panic when retrieving metadata from Java providers via 
RPC (#3092)
dbb029098 is described below

commit dbb029098571fe8ebba62f954dbd5fcd57e7c7e0
Author: liushiqi1001 <[email protected]>
AuthorDate: Thu Dec 18 17:41:37 2025 +0800

    Fix panic when retrieving metadata from Java providers via RPC (#3092)
    
    * fix: use interface{} to avoid Hessian2 reflection panic
    
    - Changed reply parameter from nil to &interface{} in getMetadataInfo
    - Hessian2 can deserialize to interface{} without type checking
    - Handles both *MetadataInfo and string types from Java Dubbo
    - Fixes panic when Java Dubbo 3.2.4 returns string instead of object
    - Gracefully skips instances with empty metadata during startup
    
    This fixes the panic that occurs when Java Dubbo returns a string
    type instead of MetadataInfo object. By using interface{} as the
    reply parameter, Hessian2 can accept any type and we handle the
    type assertion safely in Go code.
    
    Tested with Java Dubbo 3.2.4 and confirmed:
    - No more panics on type mismatch
    - Automatic recovery when metadata becomes ready
    - Services work correctly after retry
    
    Fixes #3091
    
    * test: fix unit test for interface{} metadata reply type
    
    Update mockInvoker.Invoke to handle both *info.MetadataInfo and
    *interface{} reply types, supporting the new implementation that
    uses interface{} for flexible type handling.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
    
    * refactor: simplify logging in metadata RPC client
    
    Remove redundant debug logs to reduce verbosity:
    - Remove pre-call and post-call debug logs
    - Remove type assertion success debug log
    - Remove JSON parse success info log
    - Remove final success debug log
    - Consolidate error logs for unexpected type
    
    Address review feedback from @No-SilverBullet
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
    
    * fix: update Makefile to use -any flag for modernize tool
    
    The modernize tool in the latest version of golang.org/x/tools/gopls
    no longer supports the -category=efaceany flag. It has been replaced
    with the -any flag to enable the any analysis.
    
    This commit also updates interface{} to any in the metadata package
    as recommended by the modernize tool.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
    
    ---------
    
    Co-authored-by: shiqi <[email protected]>
    Co-authored-by: Claude Sonnet 4.5 <[email protected]>
---
 Makefile                                           |  2 +-
 metadata/client.go                                 | 53 ++++++++++++++++++++--
 metadata/client_test.go                            | 24 +++++++---
 .../service_instances_changed_listener_impl.go     |  6 ++-
 4 files changed, 71 insertions(+), 14 deletions(-)

diff --git a/Makefile b/Makefile
index ad4e56969..2d121a776 100644
--- a/Makefile
+++ b/Makefile
@@ -42,7 +42,7 @@ test: clean
 
 fmt: install-imports-formatter
        # replace interface{} with any
-       go run 
golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest 
-category=efaceany -fix -test ./...
+       go run 
golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -any 
-fix -test ./...
        go fmt ./... && GOROOT=$(shell go env GOROOT) imports-formatter
        cd $(CLI_DIR) && go fmt ./...
 
diff --git a/metadata/client.go b/metadata/client.go
index 9009aad52..f0c407e8f 100644
--- a/metadata/client.go
+++ b/metadata/client.go
@@ -149,19 +149,62 @@ type remoteMetadataServiceV1 struct {
 
 func (m *remoteMetadataServiceV1) getMetadataInfo(ctx context.Context, 
revision string) (*info.MetadataInfo, error) {
        const methodName = "getMetadataInfo"
-       metadataInfo := &info.MetadataInfo{}
-       inv, _ := generateInvocation(m.invoker.GetURL(), methodName, revision, 
metadataInfo, constant.CallUnary)
+       // Use interface{} as reply parameter to accept any type (MetadataInfo 
or string)
+       // This avoids panic when Java returns String instead of MetadataInfo
+       var rawResult any
+       inv, _ := generateInvocation(m.invoker.GetURL(), methodName, revision, 
&rawResult, constant.CallUnary)
+
        res := m.invoker.Invoke(context.Background(), inv)
        if res.Error() != nil {
-               logger.Errorf("could not get the metadata info from remote 
provider: %v", res.Error())
+               logger.Errorf("[MetadataRPC] RPC call failed to %s: %v", 
m.invoker.GetURL().Location, res.Error())
                return nil, res.Error()
        }
-       if metadataInfo.Services == nil {
-               metadataInfo = res.Result().(*info.MetadataInfo)
+
+       // rawResult now contains the deserialized value - could be 
*MetadataInfo, string, or nil
+
+       // Handle nil response (e.g., Java service not fully initialized)
+       if rawResult == nil {
+               logger.Warnf("[MetadataRPC] Provider %s returned nil metadata 
(service may not be ready), revision: %s",
+                       m.invoker.GetURL().Location, revision)
+               return nil, perrors.Errorf("metadata is nil from %s, revision: 
%s", m.invoker.GetURL().Location, revision)
        }
+
+       var metadataInfo *info.MetadataInfo
+
+       // Try to handle different return types from Java Dubbo
+       if result, ok := rawResult.(*info.MetadataInfo); ok {
+               metadataInfo = result
+       } else if strValue, ok := rawResult.(string); ok {
+               // Old Java Dubbo version returns JSON string instead of 
MetadataInfo object
+               // Try to parse it as JSON for backward compatibility
+               logger.Warnf("[MetadataRPC] Provider %s returned string type 
(old Dubbo version), attempting JSON parse", m.invoker.GetURL().Location)
+
+               metadataInfo = &info.MetadataInfo{}
+               if err := json.Unmarshal([]byte(strValue), metadataInfo); err 
!= nil {
+                       logger.Errorf("[MetadataRPC] Failed to parse JSON 
string from provider %s: %v", m.invoker.GetURL().Location, err)
+                       logger.Errorf("[MetadataRPC]   - String content: %s", 
truncateString(strValue, 1000))
+                       return nil, perrors.Errorf("failed to parse metadata 
JSON from %s: %v", m.invoker.GetURL().Location, err)
+               }
+
+       } else {
+               // Neither MetadataInfo nor String - this is unexpected
+               logger.Errorf("[MetadataRPC] Unexpected metadata type from %s: 
got %T, expected *info.MetadataInfo or string",
+                       m.invoker.GetURL().Location, rawResult)
+               return nil, perrors.Errorf("unexpected metadata type from %s: 
got %T, expected *info.MetadataInfo or string",
+                       m.invoker.GetURL().Location, rawResult)
+       }
+
        return metadataInfo, nil
 }
 
+// truncateString truncates a string to maxLen characters
+func truncateString(s string, maxLen int) string {
+       if len(s) <= maxLen {
+               return s
+       }
+       return s[:maxLen] + "..."
+}
+
 // buildStandardMetadataServiceURL will use standard format to build the 
metadata service url.
 func buildStandardMetadataServiceURL(ins registry.ServiceInstance) *common.URL 
{
        ps := getMetadataServiceUrlParams(ins)
diff --git a/metadata/client_test.go b/metadata/client_test.go
index 99b53cb1f..b64b7f9fc 100644
--- a/metadata/client_test.go
+++ b/metadata/client_test.go
@@ -277,13 +277,23 @@ func (m *mockInvoker) Destroy() {
 
 func (m *mockInvoker) Invoke(ctx context.Context, inv base.Invocation) 
result.Result {
        args := m.Called()
-       meta := args.Get(0).(result.Result).Result().(*info.MetadataInfo)
-       reply := inv.Reply().(*info.MetadataInfo)
-       reply.App = meta.App
-       reply.Tag = meta.Tag
-       reply.Revision = meta.Revision
-       reply.Services = meta.Services
-       return args.Get(0).(result.Result)
+       res := args.Get(0).(result.Result)
+
+       // Handle both *info.MetadataInfo and *interface{} reply types
+       // This supports the new implementation that uses interface{} to handle 
different return types
+       if replyPtr, ok := inv.Reply().(*any); ok {
+               // New code path: reply is *interface{}, set it to point to the 
metadata
+               *replyPtr = res.Result().(*info.MetadataInfo)
+       } else if reply, ok := inv.Reply().(*info.MetadataInfo); ok {
+               // Old code path: reply is *info.MetadataInfo, copy fields
+               meta := res.Result().(*info.MetadataInfo)
+               reply.App = meta.App
+               reply.Tag = meta.Tag
+               reply.Revision = meta.Revision
+               reply.Services = meta.Services
+       }
+
+       return res
 }
 
 type mockExporter struct {
diff --git 
a/registry/servicediscovery/service_instances_changed_listener_impl.go 
b/registry/servicediscovery/service_instances_changed_listener_impl.go
index 24618054a..8cf11e81e 100644
--- a/registry/servicediscovery/service_instances_changed_listener_impl.go
+++ b/registry/servicediscovery/service_instances_changed_listener_impl.go
@@ -119,7 +119,11 @@ func (lstn *ServiceInstancesChangedListenerImpl) OnEvent(e 
observer.Event) error
                        if metadataInfo == nil {
                                meta, err := GetMetadataInfo(lstn.app, 
instance, revision)
                                if err != nil {
-                                       return err
+                                       // Skip this instance if metadata fetch 
fails (e.g., old Java Dubbo version)
+                                       // Try next instance with same revision
+                                       logger.Warnf("Failed to get metadata 
from instance %s (revision %s): %v, skipping this instance",
+                                               instance.GetHost(), revision, 
err)
+                                       continue
                                }
                                metadataInfo = meta
                        }

Reply via email to