• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20050

04 Nov 2025 05:16PM UTC coverage: 88.521% (-0.009%) from 88.53%
#20050

push

github

ejona86
xds: Detect negative ref count for xds client

If the refcount goes negative, then the next getObject() will return
null. This was noticed during code inspection when investigating a
NullPointerException in b/454396128, although it is unclear if this is
actually happening.

34964 of 39498 relevant lines covered (88.52%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.18
/../xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java
1
/*
2
 * Copyright 2020 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.collect.ImmutableList;
23
import com.google.errorprone.annotations.concurrent.GuardedBy;
24
import io.grpc.CallCredentials;
25
import io.grpc.MetricRecorder;
26
import io.grpc.internal.ExponentialBackoffPolicy;
27
import io.grpc.internal.GrpcUtil;
28
import io.grpc.internal.ObjectPool;
29
import io.grpc.internal.SharedResourceHolder;
30
import io.grpc.internal.TimeProvider;
31
import io.grpc.xds.client.Bootstrapper;
32
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
33
import io.grpc.xds.client.XdsClient;
34
import io.grpc.xds.client.XdsClientImpl;
35
import io.grpc.xds.client.XdsInitializationException;
36
import io.grpc.xds.internal.security.TlsContextManagerImpl;
37
import java.util.Map;
38
import java.util.concurrent.ConcurrentHashMap;
39
import java.util.concurrent.ScheduledExecutorService;
40
import java.util.logging.Level;
41
import java.util.logging.Logger;
42
import javax.annotation.Nullable;
43
import javax.annotation.concurrent.ThreadSafe;
44

45
/**
46
 * The global factory for creating a singleton {@link XdsClient} instance to be used by all gRPC
47
 * clients in the process.
48
 */
49
@ThreadSafe
50
final class SharedXdsClientPoolProvider implements XdsClientPoolFactory {
51
  private static final boolean LOG_XDS_NODE_ID = Boolean.parseBoolean(
1✔
52
      System.getenv("GRPC_LOG_XDS_NODE_ID"));
1✔
53
  private static final Logger log = Logger.getLogger(XdsClientImpl.class.getName());
1✔
54
  private static final ExponentialBackoffPolicy.Provider BACKOFF_POLICY_PROVIDER =
1✔
55
      new ExponentialBackoffPolicy.Provider();
56

57
  @Nullable
58
  private final Bootstrapper bootstrapper;
59
  private final Object lock = new Object();
1✔
60
  private final Map<String, ObjectPool<XdsClient>> targetToXdsClientMap = new ConcurrentHashMap<>();
1✔
61

62
  SharedXdsClientPoolProvider() {
63
    this(null);
1✔
64
  }
1✔
65

66
  @VisibleForTesting
67
  SharedXdsClientPoolProvider(@Nullable Bootstrapper bootstrapper) {
1✔
68
    this.bootstrapper = bootstrapper;
1✔
69
  }
1✔
70

71
  static SharedXdsClientPoolProvider getDefaultProvider() {
72
    return SharedXdsClientPoolProviderHolder.instance;
1✔
73
  }
74

75
  @Override
76
  @Nullable
77
  public ObjectPool<XdsClient> get(String target) {
78
    return targetToXdsClientMap.get(target);
1✔
79
  }
80

81
  @Deprecated
82
  public ObjectPool<XdsClient> getOrCreate(
83
      String target, MetricRecorder metricRecorder, CallCredentials transportCallCredentials)
84
      throws XdsInitializationException {
85
    BootstrapInfo bootstrapInfo;
86
    if (bootstrapper != null) {
1✔
87
      bootstrapInfo = bootstrapper.bootstrap();
1✔
88
    } else {
89
      bootstrapInfo = GrpcBootstrapperImpl.defaultBootstrap();
×
90
    }
91
    return getOrCreate(target, bootstrapInfo, metricRecorder, transportCallCredentials);
1✔
92
  }
93

94
  @Override
95
  public ObjectPool<XdsClient> getOrCreate(
96
      String target, BootstrapInfo bootstrapInfo, MetricRecorder metricRecorder) {
97
    return getOrCreate(target, bootstrapInfo, metricRecorder, null);
1✔
98
  }
99

100
  public ObjectPool<XdsClient> getOrCreate(
101
      String target,
102
      BootstrapInfo bootstrapInfo,
103
      MetricRecorder metricRecorder,
104
      CallCredentials transportCallCredentials) {
105
    ObjectPool<XdsClient> ref = targetToXdsClientMap.get(target);
1✔
106
    if (ref == null) {
1✔
107
      synchronized (lock) {
1✔
108
        ref = targetToXdsClientMap.get(target);
1✔
109
        if (ref == null) {
1✔
110
          ref =
1✔
111
              new RefCountedXdsClientObjectPool(
112
                  bootstrapInfo, target, metricRecorder, transportCallCredentials);
113
          targetToXdsClientMap.put(target, ref);
1✔
114
        }
115
      }
1✔
116
    }
117
    return ref;
1✔
118
  }
119

120
  @Override
121
  public ImmutableList<String> getTargets() {
122
    return ImmutableList.copyOf(targetToXdsClientMap.keySet());
1✔
123
  }
124

125
  private static class SharedXdsClientPoolProviderHolder {
126
    private static final SharedXdsClientPoolProvider instance = new SharedXdsClientPoolProvider();
1✔
127
  }
128

129
  @ThreadSafe
1✔
130
  @VisibleForTesting
131
  class RefCountedXdsClientObjectPool implements ObjectPool<XdsClient> {
132

133
    private final BootstrapInfo bootstrapInfo;
134
    private final String target; // The target associated with the xDS client.
135
    private final MetricRecorder metricRecorder;
136
    private final CallCredentials transportCallCredentials;
137
    private final Object lock = new Object();
1✔
138
    @GuardedBy("lock")
139
    private ScheduledExecutorService scheduler;
140
    @GuardedBy("lock")
141
    private XdsClient xdsClient;
142
    @GuardedBy("lock")
143
    private int refCount;
144
    @GuardedBy("lock")
145
    private XdsClientMetricReporterImpl metricReporter;
146

147
    @VisibleForTesting
148
    RefCountedXdsClientObjectPool(
149
        BootstrapInfo bootstrapInfo, String target, MetricRecorder metricRecorder) {
150
      this(bootstrapInfo, target, metricRecorder, null);
1✔
151
    }
1✔
152

153
    @VisibleForTesting
154
    RefCountedXdsClientObjectPool(
155
        BootstrapInfo bootstrapInfo,
156
        String target,
157
        MetricRecorder metricRecorder,
158
        CallCredentials transportCallCredentials) {
1✔
159
      this.bootstrapInfo = checkNotNull(bootstrapInfo, "bootstrapInfo");
1✔
160
      this.target = target;
1✔
161
      this.metricRecorder = checkNotNull(metricRecorder, "metricRecorder");
1✔
162
      this.transportCallCredentials = transportCallCredentials;
1✔
163
    }
1✔
164

165
    @Override
166
    public XdsClient getObject() {
167
      synchronized (lock) {
1✔
168
        if (refCount == 0) {
1✔
169
          if (LOG_XDS_NODE_ID) {
1✔
170
            log.log(Level.INFO, "xDS node ID: {0}", bootstrapInfo.node().getId());
×
171
          }
172
          scheduler = SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE);
1✔
173
          metricReporter = new XdsClientMetricReporterImpl(metricRecorder, target);
1✔
174
          GrpcXdsTransportFactory xdsTransportFactory =
1✔
175
              new GrpcXdsTransportFactory(transportCallCredentials);
176
          xdsClient =
1✔
177
              new XdsClientImpl(
178
                  xdsTransportFactory,
179
                  bootstrapInfo,
180
                  scheduler,
181
                  BACKOFF_POLICY_PROVIDER,
1✔
182
                  GrpcUtil.STOPWATCH_SUPPLIER,
183
                  TimeProvider.SYSTEM_TIME_PROVIDER,
184
                  MessagePrinter.INSTANCE,
185
                  new TlsContextManagerImpl(bootstrapInfo),
186
                  metricReporter);
187
          metricReporter.setXdsClient(xdsClient);
1✔
188
        }
189
        refCount++;
1✔
190
        return xdsClient;
1✔
191
      }
192
    }
193

194
    @Override
195
    public XdsClient returnObject(Object object) {
196
      synchronized (lock) {
1✔
197
        refCount--;
1✔
198
        if (refCount == 0) {
1✔
199
          xdsClient.shutdown();
1✔
200
          xdsClient = null;
1✔
201
          metricReporter.close();
1✔
202
          metricReporter = null;
1✔
203
          targetToXdsClientMap.remove(target);
1✔
204
          scheduler = SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, scheduler);
1✔
205
        } else if (refCount < 0) {
1✔
206
          assert false; // We want our tests to fail
×
207
          log.log(Level.SEVERE, "Negative reference count. File a bug", new Exception());
×
208
          refCount = 0;
×
209
        }
210
        return null;
1✔
211
      }
212
    }
213

214
    @VisibleForTesting
215
    @Nullable
216
    XdsClient getXdsClientForTest() {
217
      synchronized (lock) {
1✔
218
        return xdsClient;
1✔
219
      }
220
    }
221

222
    public String getTarget() {
223
      return target;
×
224
    }
225
  }
226

227
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc