Skip to content

Commit 2b13bb7

Browse files
Fix startup NPE when a weakly dependent registry is unavailable with check=false
When a registry cannot be created and check=false (e.g. a weakly dependent ZooKeeper is down at startup), AbstractRegistryFactory returns a null registry which RegistryFactoryWrapper still wraps in a ListenerRegistryWrapper. The wrapper already guards register/unregister/subscribe against a null delegate, but getUrl/isAvailable/destroy/unsubscribe/isServiceDiscovery/lookup did not, and RegistryDirectory#subscribe dereferences registry.getUrl() unconditionally to build a metrics label (added in #12582), causing an NPE since 3.2.5. Complete the null-safety in ListenerRegistryWrapper and guard the metrics label computation in RegistryDirectory#subscribe so a service can still start when a non-critical registry is unavailable. Fixes #16178
1 parent c587ff6 commit 2b13bb7

3 files changed

Lines changed: 46 additions & 10 deletions

File tree

dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/ListenerRegistryWrapper.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.dubbo.common.utils.CollectionUtils;
2323
import org.apache.dubbo.common.utils.UrlUtils;
2424

25+
import java.util.Collections;
2526
import java.util.List;
2627
import java.util.function.Consumer;
2728

@@ -41,17 +42,19 @@ public ListenerRegistryWrapper(Registry registry, List<RegistryServiceListener>
4142

4243
@Override
4344
public URL getUrl() {
44-
return registry.getUrl();
45+
return registry == null ? null : registry.getUrl();
4546
}
4647

4748
@Override
4849
public boolean isAvailable() {
49-
return registry.isAvailable();
50+
return registry != null && registry.isAvailable();
5051
}
5152

5253
@Override
5354
public void destroy() {
54-
registry.destroy();
55+
if (registry != null) {
56+
registry.destroy();
57+
}
5558
}
5659

5760
@Override
@@ -94,20 +97,22 @@ public void subscribe(URL url, NotifyListener listener) {
9497
@Override
9598
public void unsubscribe(URL url, NotifyListener listener) {
9699
try {
97-
registry.unsubscribe(url, listener);
100+
if (registry != null) {
101+
registry.unsubscribe(url, listener);
102+
}
98103
} finally {
99104
listenerEvent(serviceListener -> serviceListener.onUnsubscribe(url, registry));
100105
}
101106
}
102107

103108
@Override
104109
public boolean isServiceDiscovery() {
105-
return registry.isServiceDiscovery();
110+
return registry != null && registry.isServiceDiscovery();
106111
}
107112

108113
@Override
109114
public List<URL> lookup(URL url) {
110-
return registry.lookup(url);
115+
return registry == null ? Collections.emptyList() : registry.lookup(url);
111116
}
112117

113118
public Registry getRegistry() {

dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,13 @@ public void subscribe(URL url) {
152152
consumerConfigurationListener.addNotifyListener(this);
153153
referenceConfigurationListener = new ReferenceConfigurationListener(moduleModel, this, url);
154154
}
155-
String registryClusterName = registry.getUrl()
156-
.getParameter(
157-
RegistryConstants.REGISTRY_CLUSTER_KEY,
158-
registry.getUrl().getParameter(PROTOCOL_KEY));
155+
// registry.getUrl() may be null when the registry is unavailable and check=false (e.g. a weakly
156+
// dependent registry is down at startup), in which case there is no cluster name to report.
157+
URL registryUrl = registry.getUrl();
158+
String registryClusterName = registryUrl == null
159+
? null
160+
: registryUrl.getParameter(
161+
RegistryConstants.REGISTRY_CLUSTER_KEY, registryUrl.getParameter(PROTOCOL_KEY));
159162
MetricsEventBus.post(RegistryEvent.toSubscribeEvent(applicationModel, registryClusterName), () -> {
160163
super.subscribe(url);
161164
return null;

dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/ListenerRegistryWrapperTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.apache.dubbo.common.url.component.ServiceConfigURL;
2121
import org.apache.dubbo.registry.integration.DemoService;
2222

23+
import java.util.Collections;
2324
import java.util.HashMap;
2425
import java.util.Map;
2526

@@ -73,4 +74,31 @@ void testSubscribe() {
7374
registryWrapper.subscribe(subscribeUrl, notifyListener);
7475
verify(listener, times(1)).onSubscribe(subscribeUrl, registry);
7576
}
77+
78+
@Test
79+
void testNullRegistryIsTolerated() {
80+
// When a registry is unavailable and check=false (e.g. a weakly dependent registry is down at
81+
// startup), AbstractRegistryFactory returns a null registry which RegistryFactoryWrapper still
82+
// wraps. The wrapper must tolerate the null delegate instead of throwing NullPointerException.
83+
// See https://github.com/apache/dubbo/issues/16178.
84+
ListenerRegistryWrapper wrapper = new ListenerRegistryWrapper(null, Collections.emptyList());
85+
86+
URL url = new ServiceConfigURL("dubbo", "127.0.0.1", 20881, DemoService.class.getName(), new HashMap<>());
87+
NotifyListener notifyListener = mock(NotifyListener.class);
88+
89+
Assertions.assertNull(wrapper.getRegistry());
90+
Assertions.assertNull(wrapper.getUrl());
91+
Assertions.assertFalse(wrapper.isAvailable());
92+
Assertions.assertFalse(wrapper.isServiceDiscovery());
93+
Assertions.assertTrue(wrapper.lookup(url).isEmpty());
94+
95+
// register / subscribe / lifecycle methods must be no-ops rather than throwing
96+
Assertions.assertDoesNotThrow(() -> {
97+
wrapper.register(url);
98+
wrapper.unregister(url);
99+
wrapper.subscribe(url, notifyListener);
100+
wrapper.unsubscribe(url, notifyListener);
101+
wrapper.destroy();
102+
});
103+
}
76104
}

0 commit comments

Comments
 (0)