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
}