AsperforMias opened a new issue, #3617:
URL: https://github.com/apache/dubbo-go/issues/3617

   ## βœ… Verification Checklist
   
   - [x] πŸ” I have searched the [existing 
issues](https://github.com/apache/dubbo-go/issues) and confirmed this is not a 
duplicate
   
   ---
   
   ## πŸ”§ Environment Information
   
   ### πŸš€ Go Version
   
   go1.26.1
   
   ### πŸ“¦ Dubbo-go Version
   
   v3.3.2 (confirmed by reproduction). The same code is present on `develop` (@ 
`115af01d`), so the bug is still unfixed.
   
   ### πŸ–₯️ Server Configuration
   
   dubbo-go v3.3.2 provider, application-level registry 
(`registry.type=service`)
   
   ### πŸ’» Client Configuration
   
   dubbo-go v3.3.2 consumer, application-level service discovery
   
   ### 🌐 Protocol Configuration
   
   Triple
   
   ### πŸ“‹ Registry Configuration
   
   Nacos v2.5.1, application-level service discovery 
(`service-discovery-registry://...`)
   
   ### πŸ’Ύ Operating System
   
   🐧 Linux (Docker), reproduced on macOS Docker Desktop
   
   ---
   
   ## πŸ› Issue Details
   
   ### πŸ“ Bug Description
   
   With **application-level service discovery**, a consumer reference that sets 
`provided_by` (e.g. `client.WithProvidedBy("demo-provider")`, or `provided_by` 
in yaml) **never subscribes to any instance**. The directory stays empty 
forever and every call fails with `No provider available`, even though the 
provider is healthy and registered in Nacos. Removing `provided_by` (and 
letting the interface→app mapping come from the metadata-report / `mapping` 
config group) makes the exact same setup work immediately.
   
   Root cause (confirmed by source reading + reproduction):
   
   1. `serviceDiscoveryRegistry.Subscribe` seeds the mapping listener with the 
`provided_by` value as its *baseline* (`oldServiceNames`):
      
https://github.com/apache/dubbo-go/blob/v3.3.2/registry/servicediscovery/service_discovery_registry.go#L510
      ```go
      mappingListener := NewMappingListener(s.url, url, 
parseServices(url.GetParam(constant.ProvidedBy, "")), notify)
      services := s.getServices(url, mappingListener)
      ...
      err := 
mappingListener.OnEvent(registry.NewServiceMappingChangedEvent(url.ServiceKey(),
 services))
      ```
   2. `getServices` short-circuits on `provided_by` and returns the *same* set 
(`demo-provider`), without calling `findMappedServices` β€” so no mapping 
listener is registered for future updates either:
      
https://github.com/apache/dubbo-go/blob/v3.3.2/registry/servicediscovery/service_discovery_registry.go#L621-L631
   3. The first (and only) `OnEvent` therefore has `newServiceNames == 
oldServiceNames` and hits the early return in 
`ServiceMappingChangedListenerImpl.OnEvent`, **without ever calling 
`SubscribeURL`**:
      
https://github.com/apache/dubbo-go/blob/v3.3.2/registry/servicediscovery/service_mapping_change_listener_impl.go#L83-L85
      ```go
      if newServiceNames.Empty() || oldServiceNames.String() == 
newServiceNames.String() {
          return nil
      }
      ```
   
   As a result, with `provided_by` set, `SubscribeURL` is never invoked: no 
initial `GetInstances`, no `ServiceInstancesChangedListener`, no `AddListener`. 
Since `getServices` never registered a mapping listener, no later mapping event 
can arrive to recover either. The consumer is permanently empty from the first 
call.
   
   Impact: any consumer using `provided_by` + application-level discovery is 
100% broken (not a race β€” it fails deterministically on every call from 
startup).
   
   ### πŸ”„ Steps to Reproduce
   
   1. Start a Nacos server and a dubbo-go v3.3.2 provider using 
application-level registry:
   
      ```go
      srv, _ := server.NewServer(
          server.WithServerProtocol(protocol.WithTriple(), 
protocol.WithPort(20001)),
          server.WithServerRegistry(
              registry.WithNacos(),
              registry.WithAddress("nacos:8848"),
              registry.WithRegisterService(), // application-level
          ),
          server.SetServerApplication(&global.ApplicationConfig{Name: 
"demo-provider"}),
      )
      // register any triple service, e.g. com.example.GreetService, then 
srv.Serve()
      ```
   
   2. Start a consumer whose reference sets `provided_by`:
   
      ```go
      cli, _ := client.NewClient(
          client.WithClientRegistry(
              registry.WithNacos(),
              registry.WithAddress("nacos:8848"),
              registry.WithRegisterService(),
          ),
          client.SetClientApplication(&global.ApplicationConfig{Name: 
"demo-consumer"}),
      )
      conn, _ := cli.DialWithInfo("com.example.GreetService",
          &client.ClientInfo{InterfaceName: "com.example.GreetService", 
MethodNames: []string{"SayHello"}},
          client.WithProvidedBy("demo-provider"), // <-- the trigger
      )
      conn.CallUnary(ctx, []any{&emptypb.Empty{}}, resp, "SayHello")
      ```
   
   3. Every call fails with `No provider available ... from registry 
service-discovery-registry://nacos:...`.
   
   4. Control: delete `client.WithProvidedBy("demo-provider")` and publish the 
standard mapping config (dataId = interface name, group = `mapping`, content = 
`demo-provider`; the provider publishes it automatically) β€” the same consumer 
works on the first call.
   
   Consumer log with `provided_by` set β€” the mapping is found, then **nothing** 
(no `synchronized instance notification`, no `received instance notification 
event`, no `AddListener`):
   
   ```
   INFO servicediscovery/service_discovery_registry.go:516 
[Registry][ServiceDiscovery] find initial mapping applications 
"HashSet\ndemo-provider" for service com.example.GreetService
   # ... silence afterwards; every invocation:
   # Failed to invoke the method SayHello. No provider available for the service
   # tri://...?interface=com.example.GreetService... from registry 
service-discovery-registry://nacos:8848...
   ```
   
   A full docker-compose reproduction (Nacos + provider + consumer, one script) 
is available and can be attached/shared as a repo if useful.
   
   ### βœ… Expected Behavior
   
   `provided_by` should make the consumer subscribe to the named application(s) 
and fetch their instances β€” i.e. `SubscribeURL` must be invoked for the initial 
`provided_by` set, invokers built, and calls succeed. This is also how the 
equivalent Java Dubbo reference config behaves.
   
   ### ❌ Actual Behavior
   
   The consumer deterministically fails every call from startup:
   
   ```
   Failed to invoke the method SayHello. No provider available for the service
   tri://:@172.30.0.12:/?interface=com.example.GreetService&group=&version=
   from registry 
service-discovery-registry://nacos:8848?...&registry.type=service&...
   on the consumer 172.30.0.12 using the dubbo version 3.3.2.
   Please check if the providers have been started and registered.
   ```
   
   The provider is registered and healthy in Nacos (verified via 
`/nacos/v1/ns/instance/list?serviceName=demo-provider`).
   
   ### πŸ’‘ Possible Solution
   
   The seeding of `oldServiceNames` from `provided_by` makes the *initial* 
subscribe look like a no-op mapping diff. Minimal-invasion options (either one 
fixes it; the first is smaller):
   
   1. In `serviceDiscoveryRegistry.Subscribe`, perform the initial subscribe 
directly instead of routing it through the diff-based listener:
      ```go
      if !services.Empty() {
          s.SubscribeURL(url, notify, services) // initial subscribe is not a 
"mapping change"
      }
      ```
      and keep `mappingListener` (with its `provided_by` seed) only for 
subsequent real mapping updates. `SubscribeURL` already handles listener 
reuse/dedup via `serviceListeners`, so this does not change behavior for the 
metadata-report mapping path.
   
   2. Alternatively, seed `oldServiceNames` with an empty set so the first 
`OnEvent` takes the existing `oldServiceNames.Empty()` branch and calls 
`SubscribeURL`. The `provided_by`-seeded dedup for later identical mapping 
events is then lost, but `SubscribeURL`'s listener cache already makes a 
repeated subscribe cheap.
   
   Regression test suggestion: `Subscribe` a reference with `provided-by` set 
against a mocked `ServiceDiscovery`, and assert `GetInstances` is called / a 
`ServiceInstancesChangedListener` is installed β€” currently neither happens.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to