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

grpc / grpc-java / #19996

24 Sep 2025 12:08AM UTC coverage: 88.575% (+0.03%) from 88.543%
#19996

push

github

web-flow
Implement otel retry metrics (#12064)

implements [A96](https://github.com/grpc/proposal/pull/488/files)

34731 of 39211 relevant lines covered (88.57%)

0.89 hits per line

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

92.93
/../core/src/main/java/io/grpc/internal/GrpcUtil.java
1
/*
2
 * Copyright 2014 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.internal;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkState;
21

22
import com.google.common.annotations.VisibleForTesting;
23
import com.google.common.base.Objects;
24
import com.google.common.base.Preconditions;
25
import com.google.common.base.Splitter;
26
import com.google.common.base.Stopwatch;
27
import com.google.common.base.Strings;
28
import com.google.common.base.Supplier;
29
import com.google.common.util.concurrent.ListenableFuture;
30
import com.google.common.util.concurrent.ThreadFactoryBuilder;
31
import io.grpc.CallOptions;
32
import io.grpc.ClientStreamTracer;
33
import io.grpc.ClientStreamTracer.StreamInfo;
34
import io.grpc.InternalChannelz.SocketStats;
35
import io.grpc.InternalLogId;
36
import io.grpc.InternalMetadata;
37
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
38
import io.grpc.LoadBalancer.PickResult;
39
import io.grpc.LoadBalancer.Subchannel;
40
import io.grpc.Metadata;
41
import io.grpc.MethodDescriptor;
42
import io.grpc.ProxiedSocketAddress;
43
import io.grpc.ProxyDetector;
44
import io.grpc.Status;
45
import io.grpc.internal.ClientStreamListener.RpcProgress;
46
import io.grpc.internal.SharedResourceHolder.Resource;
47
import io.grpc.internal.StreamListener.MessageProducer;
48
import java.io.Closeable;
49
import java.io.IOException;
50
import java.io.InputStream;
51
import java.lang.reflect.Method;
52
import java.net.HttpURLConnection;
53
import java.net.SocketAddress;
54
import java.net.URI;
55
import java.net.URISyntaxException;
56
import java.nio.charset.Charset;
57
import java.util.Arrays;
58
import java.util.Collection;
59
import java.util.Collections;
60
import java.util.EnumSet;
61
import java.util.HashSet;
62
import java.util.List;
63
import java.util.Locale;
64
import java.util.Set;
65
import java.util.concurrent.Executor;
66
import java.util.concurrent.ExecutorService;
67
import java.util.concurrent.Executors;
68
import java.util.concurrent.ScheduledExecutorService;
69
import java.util.concurrent.ThreadFactory;
70
import java.util.concurrent.TimeUnit;
71
import java.util.logging.Level;
72
import java.util.logging.Logger;
73
import javax.annotation.Nullable;
74
import javax.annotation.concurrent.Immutable;
75

76
/**
77
 * Common utilities for GRPC.
78
 */
79
public final class GrpcUtil {
80

81
  private static final Logger log = Logger.getLogger(GrpcUtil.class.getName());
1✔
82

83
  private static final Set<Status.Code> INAPPROPRIATE_CONTROL_PLANE_STATUS
1✔
84
      = Collections.unmodifiableSet(EnumSet.of(
1✔
85
          Status.Code.OK,
86
          Status.Code.INVALID_ARGUMENT,
87
          Status.Code.NOT_FOUND,
88
          Status.Code.ALREADY_EXISTS,
89
          Status.Code.FAILED_PRECONDITION,
90
          Status.Code.ABORTED,
91
          Status.Code.OUT_OF_RANGE,
92
          Status.Code.DATA_LOSS));
93

94
  public static final Charset US_ASCII = Charset.forName("US-ASCII");
1✔
95

96
  /**
97
   * {@link io.grpc.Metadata.Key} for the timeout header.
98
   */
99
  public static final Metadata.Key<Long> TIMEOUT_KEY =
1✔
100
          Metadata.Key.of(GrpcUtil.TIMEOUT, new TimeoutMarshaller());
1✔
101

102
  /**
103
   * {@link io.grpc.Metadata.Key} for the message encoding header.
104
   */
105
  public static final Metadata.Key<String> MESSAGE_ENCODING_KEY =
1✔
106
          Metadata.Key.of(GrpcUtil.MESSAGE_ENCODING, Metadata.ASCII_STRING_MARSHALLER);
1✔
107

108
  /**
109
   * {@link io.grpc.Metadata.Key} for the accepted message encodings header.
110
   */
111
  public static final Metadata.Key<byte[]> MESSAGE_ACCEPT_ENCODING_KEY =
1✔
112
      InternalMetadata.keyOf(GrpcUtil.MESSAGE_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
1✔
113

114
  /**
115
   * {@link io.grpc.Metadata.Key} for the stream's content encoding header.
116
   */
117
  public static final Metadata.Key<String> CONTENT_ENCODING_KEY =
1✔
118
      Metadata.Key.of(GrpcUtil.CONTENT_ENCODING, Metadata.ASCII_STRING_MARSHALLER);
1✔
119

120
  /**
121
   * {@link io.grpc.Metadata.Key} for the stream's accepted content encoding header.
122
   */
123
  public static final Metadata.Key<byte[]> CONTENT_ACCEPT_ENCODING_KEY =
1✔
124
      InternalMetadata.keyOf(GrpcUtil.CONTENT_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
1✔
125

126
  static final Metadata.Key<String> CONTENT_LENGTH_KEY =
1✔
127
      Metadata.Key.of("content-length", Metadata.ASCII_STRING_MARSHALLER);
1✔
128

129
  private static final class AcceptEncodingMarshaller implements TrustedAsciiMarshaller<byte[]> {
130
    @Override
131
    public byte[] toAsciiString(byte[] value) {
132
      return value;
1✔
133
    }
134

135
    @Override
136
    public byte[] parseAsciiString(byte[] serialized) {
137
      return serialized;
1✔
138
    }
139
  }
140

141
  /**
142
   * {@link io.grpc.Metadata.Key} for the Content-Type request/response header.
143
   */
144
  public static final Metadata.Key<String> CONTENT_TYPE_KEY =
1✔
145
          Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER);
1✔
146

147
  /**
148
   * {@link io.grpc.Metadata.Key} for the Transfer encoding.
149
   */
150
  public static final Metadata.Key<String> TE_HEADER =
1✔
151
      Metadata.Key.of("te", Metadata.ASCII_STRING_MARSHALLER);
1✔
152

153
  /**
154
   * {@link io.grpc.Metadata.Key} for the Content-Type request/response header.
155
   */
156
  public static final Metadata.Key<String> USER_AGENT_KEY =
1✔
157
          Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER);
1✔
158

159
  /**
160
   * The default port for plain-text connections.
161
   */
162
  public static final int DEFAULT_PORT_PLAINTEXT = 80;
163

164
  /**
165
   * The default port for SSL connections.
166
   */
167
  public static final int DEFAULT_PORT_SSL = 443;
168

169
  /**
170
   * Content-Type used for GRPC-over-HTTP/2.
171
   */
172
  public static final String CONTENT_TYPE_GRPC = "application/grpc";
173

174
  /**
175
   * The HTTP method used for GRPC requests.
176
   */
177
  public static final String HTTP_METHOD = "POST";
178

179
  /**
180
   * The TE (transport encoding) header for requests over HTTP/2.
181
   */
182
  public static final String TE_TRAILERS = "trailers";
183

184
  /**
185
   * The Timeout header name.
186
   */
187
  public static final String TIMEOUT = "grpc-timeout";
188

189
  /**
190
   * The message encoding (i.e. compression) that can be used in the stream.
191
   */
192
  public static final String MESSAGE_ENCODING = "grpc-encoding";
193

194
  /**
195
   * The accepted message encodings (i.e. compression) that can be used in the stream.
196
   */
197
  public static final String MESSAGE_ACCEPT_ENCODING = "grpc-accept-encoding";
198

199
  /**
200
   * The content-encoding used to compress the full gRPC stream.
201
   */
202
  public static final String CONTENT_ENCODING = "content-encoding";
203

204
  /**
205
   * The accepted content-encodings that can be used to compress the full gRPC stream.
206
   */
207
  public static final String CONTENT_ACCEPT_ENCODING = "accept-encoding";
208

209
  /**
210
   * The default maximum uncompressed size (in bytes) for inbound messages. Defaults to 4 MiB.
211
   */
212
  public static final int DEFAULT_MAX_MESSAGE_SIZE = 4 * 1024 * 1024;
213

214
  /**
215
   * The default maximum size (in bytes) for inbound header/trailer.
216
   */
217
  // Update documentation in public-facing Builders when changing this value.
218
  public static final int DEFAULT_MAX_HEADER_LIST_SIZE = 8192;
219

220
  public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
1✔
221

222
  public static final String IMPLEMENTATION_VERSION = "1.77.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
223

224
  /**
225
   * The default timeout in nanos for a keepalive ping request.
226
   */
227
  public static final long DEFAULT_KEEPALIVE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(20L);
1✔
228

229
  /**
230
   * The magic keepalive time value that disables client keepalive.
231
   */
232
  public static final long KEEPALIVE_TIME_NANOS_DISABLED = Long.MAX_VALUE;
233

234
  /**
235
   * The default delay in nanos for server keepalive.
236
   */
237
  public static final long DEFAULT_SERVER_KEEPALIVE_TIME_NANOS = TimeUnit.HOURS.toNanos(2L);
1✔
238

239
  /**
240
   * The default timeout in nanos for a server keepalive ping request.
241
   */
242
  public static final long DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(20L);
1✔
243

244
  /**
245
   * The magic keepalive time value that disables keepalive.
246
   */
247
  public static final long SERVER_KEEPALIVE_TIME_NANOS_DISABLED = Long.MAX_VALUE;
248

249
  /**
250
   * The default proxy detector.
251
   */
252
  public static final ProxyDetector DEFAULT_PROXY_DETECTOR = new ProxyDetectorImpl();
1✔
253

254
  /**
255
   * A proxy detector that always claims no proxy is needed.
256
   */
257
  public static final ProxyDetector NOOP_PROXY_DETECTOR = new ProxyDetector() {
1✔
258
    @Nullable
259
    @Override
260
    public ProxiedSocketAddress proxyFor(SocketAddress targetServerAddress) {
261
      return null;
1✔
262
    }
263
  };
264

265
  /**
266
   * The very default load-balancing policy.
267
   */
268
  public static final String DEFAULT_LB_POLICY = "pick_first";
269

270
  /**
271
   * RPCs created on the Channel returned by {@link io.grpc.LoadBalancer.Subchannel#asChannel}
272
   * will have this option with value {@code true}.  They will be treated differently from
273
   * the ones created by application.
274
   */
275
  public static final CallOptions.Key<Boolean> CALL_OPTIONS_RPC_OWNED_BY_BALANCER =
1✔
276
      CallOptions.Key.create("io.grpc.internal.CALL_OPTIONS_RPC_OWNED_BY_BALANCER");
1✔
277

278
  private static final ClientStreamTracer NOOP_TRACER = new ClientStreamTracer() {};
1✔
279

280
  /**
281
   * Returns true if an RPC with the given properties should be counted when calculating the
282
   * in-use state of a transport.
283
   */
284
  public static boolean shouldBeCountedForInUse(CallOptions callOptions) {
285
    return !Boolean.TRUE.equals(callOptions.getOption(CALL_OPTIONS_RPC_OWNED_BY_BALANCER));
1✔
286
  }
287

288
  /**
289
   * Maps HTTP error response status codes to transport codes, as defined in <a
290
   * href="https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md">
291
   * http-grpc-status-mapping.md</a>. Never returns a status for which {@code status.isOk()} is
292
   * {@code true}.
293
   */
294
  public static Status httpStatusToGrpcStatus(int httpStatusCode) {
295
    return httpStatusToGrpcCode(httpStatusCode).toStatus()
1✔
296
        .withDescription("HTTP status code " + httpStatusCode);
1✔
297
  }
298

299
  private static Status.Code httpStatusToGrpcCode(int httpStatusCode) {
300
    if (httpStatusCode >= 100 && httpStatusCode < 200) {
1✔
301
      // 1xx. These headers should have been ignored.
302
      return Status.Code.INTERNAL;
1✔
303
    }
304
    switch (httpStatusCode) {
1✔
305
      case HttpURLConnection.HTTP_BAD_REQUEST:  // 400
306
      case 431: // Request Header Fields Too Large
307
        // TODO(carl-mastrangelo): this should be added to the http-grpc-status-mapping.md doc.
308
        return Status.Code.INTERNAL;
1✔
309
      case HttpURLConnection.HTTP_UNAUTHORIZED:  // 401
310
        return Status.Code.UNAUTHENTICATED;
1✔
311
      case HttpURLConnection.HTTP_FORBIDDEN:  // 403
312
        return Status.Code.PERMISSION_DENIED;
1✔
313
      case HttpURLConnection.HTTP_NOT_FOUND:  // 404
314
        return Status.Code.UNIMPLEMENTED;
1✔
315
      case 429:  // Too Many Requests
316
      case HttpURLConnection.HTTP_BAD_GATEWAY:  // 502
317
      case HttpURLConnection.HTTP_UNAVAILABLE:  // 503
318
      case HttpURLConnection.HTTP_GATEWAY_TIMEOUT:  // 504
319
        return Status.Code.UNAVAILABLE;
1✔
320
      default:
321
        return Status.Code.UNKNOWN;
1✔
322
    }
323
  }
324

325
  /**
326
   * All error codes identified by the HTTP/2 spec. Used in GOAWAY and RST_STREAM frames.
327
   */
328
  public enum Http2Error {
1✔
329
    /**
330
     * Servers implementing a graceful shutdown of the connection will send {@code GOAWAY} with
331
     * {@code NO_ERROR}. In this case it is important to indicate to the application that the
332
     * request should be retried (i.e. {@link Status#UNAVAILABLE}).
333
     */
334
    NO_ERROR(0x0, Status.UNAVAILABLE),
1✔
335
    PROTOCOL_ERROR(0x1, Status.INTERNAL),
1✔
336
    INTERNAL_ERROR(0x2, Status.INTERNAL),
1✔
337
    FLOW_CONTROL_ERROR(0x3, Status.INTERNAL),
1✔
338
    SETTINGS_TIMEOUT(0x4, Status.INTERNAL),
1✔
339
    STREAM_CLOSED(0x5, Status.INTERNAL),
1✔
340
    FRAME_SIZE_ERROR(0x6, Status.INTERNAL),
1✔
341
    REFUSED_STREAM(0x7, Status.UNAVAILABLE),
1✔
342
    CANCEL(0x8, Status.CANCELLED),
1✔
343
    COMPRESSION_ERROR(0x9, Status.INTERNAL),
1✔
344
    CONNECT_ERROR(0xA, Status.INTERNAL),
1✔
345
    ENHANCE_YOUR_CALM(0xB, Status.RESOURCE_EXHAUSTED.withDescription("Bandwidth exhausted")),
1✔
346
    INADEQUATE_SECURITY(0xC, Status.PERMISSION_DENIED.withDescription("Permission denied as "
1✔
347
        + "protocol is not secure enough to call")),
348
    HTTP_1_1_REQUIRED(0xD, Status.UNKNOWN);
1✔
349

350
    // Populate a mapping of code to enum value for quick look-up.
351
    private static final Http2Error[] codeMap = buildHttp2CodeMap();
1✔
352

353
    private static Http2Error[] buildHttp2CodeMap() {
354
      Http2Error[] errors = Http2Error.values();
1✔
355
      int size = (int) errors[errors.length - 1].code() + 1;
1✔
356
      Http2Error[] http2CodeMap = new Http2Error[size];
1✔
357
      for (Http2Error error : errors) {
1✔
358
        int index = (int) error.code();
1✔
359
        http2CodeMap[index] = error;
1✔
360
      }
361
      return http2CodeMap;
1✔
362
    }
363

364
    private final int code;
365
    // Status is not guaranteed to be deeply immutable. Don't care though, since that's only true
366
    // when there are exceptions in the Status, which is not true here.
367
    @SuppressWarnings("ImmutableEnumChecker")
368
    private final Status status;
369

370
    Http2Error(int code, Status status) {
1✔
371
      this.code = code;
1✔
372
      String description = "HTTP/2 error code: " + this.name();
1✔
373
      if (status.getDescription() != null) {
1✔
374
        description += " (" + status.getDescription() + ")";
1✔
375
      }
376
      this.status = status.withDescription(description);
1✔
377
    }
1✔
378

379
    /**
380
     * Gets the code for this error used on the wire.
381
     */
382
    public long code() {
383
      return code;
1✔
384
    }
385

386
    /**
387
     * Gets the {@link Status} associated with this HTTP/2 code.
388
     */
389
    public Status status() {
390
      return status;
1✔
391
    }
392

393
    /**
394
     * Looks up the HTTP/2 error code enum value for the specified code.
395
     *
396
     * @param code an HTTP/2 error code value.
397
     * @return the HTTP/2 error code enum or {@code null} if not found.
398
     */
399
    public static Http2Error forCode(long code) {
400
      if (code >= codeMap.length || code < 0) {
1✔
401
        return null;
1✔
402
      }
403
      return codeMap[(int) code];
1✔
404
    }
405

406
    /**
407
     * Looks up the {@link Status} from the given HTTP/2 error code. This is preferred over {@code
408
     * forCode(code).status()}, to more easily conform to HTTP/2:
409
     *
410
     * <blockquote>Unknown or unsupported error codes MUST NOT trigger any special behavior.
411
     * These MAY be treated by an implementation as being equivalent to INTERNAL_ERROR.</blockquote>
412
     *
413
     * @param code the HTTP/2 error code.
414
     * @return a {@link Status} representing the given error.
415
     */
416
    public static Status statusForCode(long code) {
417
      Http2Error error = forCode(code);
1✔
418
      if (error == null) {
1✔
419
        // This "forgets" the message of INTERNAL_ERROR while keeping the same status code.
420
        Status.Code statusCode = INTERNAL_ERROR.status().getCode();
1✔
421
        return Status.fromCodeValue(statusCode.value())
1✔
422
            .withDescription("Unrecognized HTTP/2 error code: " + code);
1✔
423
      }
424

425
      return error.status();
1✔
426
    }
427
  }
428

429
  /**
430
   * Indicates whether or not the given value is a valid gRPC content-type.
431
   */
432
  public static boolean isGrpcContentType(String contentType) {
433
    if (contentType == null) {
1✔
434
      return false;
1✔
435
    }
436

437
    if (CONTENT_TYPE_GRPC.length() > contentType.length()) {
1✔
438
      return false;
1✔
439
    }
440

441
    contentType = contentType.toLowerCase(Locale.US);
1✔
442
    if (!contentType.startsWith(CONTENT_TYPE_GRPC)) {
1✔
443
      // Not a gRPC content-type.
444
      return false;
1✔
445
    }
446

447
    if (contentType.length() == CONTENT_TYPE_GRPC.length()) {
1✔
448
      // The strings match exactly.
449
      return true;
1✔
450
    }
451

452
    // The contentType matches, but is longer than the expected string.
453
    // We need to support variations on the content-type (e.g. +proto, +json) as defined by the
454
    // gRPC wire spec.
455
    char nextChar = contentType.charAt(CONTENT_TYPE_GRPC.length());
1✔
456
    return nextChar == '+' || nextChar == ';';
1✔
457
  }
458

459
  /**
460
   * Gets the User-Agent string for the gRPC transport.
461
   */
462
  public static String getGrpcUserAgent(
463
      String transportName, @Nullable String applicationUserAgent) {
464
    StringBuilder builder = new StringBuilder();
1✔
465
    if (applicationUserAgent != null) {
1✔
466
      builder.append(applicationUserAgent);
1✔
467
      builder.append(' ');
1✔
468
    }
469
    builder.append("grpc-java-");
1✔
470
    builder.append(transportName);
1✔
471
    builder.append('/');
1✔
472
    builder.append(IMPLEMENTATION_VERSION);
1✔
473
    return builder.toString();
1✔
474
  }
475

476
  @Immutable
477
  public static final class GrpcBuildVersion {
478
    private final String userAgent;
479
    private final String implementationVersion;
480

481
    private GrpcBuildVersion(String userAgent, String implementationVersion) {
1✔
482
      this.userAgent = Preconditions.checkNotNull(userAgent, "userAgentName");
1✔
483
      this.implementationVersion =
1✔
484
          Preconditions.checkNotNull(implementationVersion, "implementationVersion");
1✔
485
    }
1✔
486

487
    public String getUserAgent() {
488
      return userAgent;
1✔
489
    }
490

491
    public String getImplementationVersion() {
492
      return implementationVersion;
1✔
493
    }
494

495
    @Override
496
    public String toString() {
497
      return userAgent + " " + implementationVersion;
1✔
498
    }
499
  }
500

501
  /**
502
   * Returns the build version of gRPC.
503
   */
504
  public static GrpcBuildVersion getGrpcBuildVersion() {
505
    return new GrpcBuildVersion("gRPC Java", IMPLEMENTATION_VERSION);
1✔
506
  }
507

508
  /**
509
   * Parse an authority into a URI for retrieving the host and port.
510
   */
511
  public static URI authorityToUri(String authority) {
512
    Preconditions.checkNotNull(authority, "authority");
1✔
513
    URI uri;
514
    try {
515
      uri = new URI(null, authority, null, null, null);
1✔
516
    } catch (URISyntaxException ex) {
1✔
517
      throw new IllegalArgumentException("Invalid authority: " + authority, ex);
1✔
518
    }
1✔
519
    return uri;
1✔
520
  }
521

522
  /**
523
   * Verify {@code authority} is valid for use with gRPC. The syntax must be valid and it must not
524
   * include userinfo.
525
   *
526
   * @return the {@code authority} provided
527
   */
528
  public static String checkAuthority(String authority) {
529
    URI uri = authorityToUri(authority);
1✔
530
    // Verify that the user Info is not provided.
531
    checkArgument(uri.getAuthority().indexOf('@') == -1,
1✔
532
        "Userinfo must not be present on authority: '%s'", authority);
533
    return authority;
1✔
534
  }
535

536
  /**
537
   * Combine a host and port into an authority string.
538
   */
539
  // There is a copy of this method in io.grpc.Grpc
540
  public static String authorityFromHostAndPort(String host, int port) {
541
    try {
542
      return new URI(null, null, host, port, null, null, null).getAuthority();
1✔
543
    } catch (URISyntaxException ex) {
1✔
544
      throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex);
1✔
545
    }
546
  }
547

548
  /**
549
   * Shared executor for channels.
550
   */
551
  public static final Resource<Executor> SHARED_CHANNEL_EXECUTOR =
1✔
552
      new Resource<Executor>() {
1✔
553
        private static final String NAME = "grpc-default-executor";
554
        @Override
555
        public Executor create() {
556
          return Executors.newCachedThreadPool(getThreadFactory(NAME + "-%d", true));
1✔
557
        }
558

559
        @Override
560
        public void close(Executor instance) {
561
          ((ExecutorService) instance).shutdown();
1✔
562
        }
1✔
563

564
        @Override
565
        public String toString() {
566
          return NAME;
×
567
        }
568
      };
569

570
  /**
571
   * Shared single-threaded executor for managing channel timers.
572
   */
573
  public static final Resource<ScheduledExecutorService> TIMER_SERVICE =
1✔
574
      new Resource<ScheduledExecutorService>() {
1✔
575
        @Override
576
        public ScheduledExecutorService create() {
577
          // We don't use newSingleThreadScheduledExecutor because it doesn't return a
578
          // ScheduledThreadPoolExecutor.
579
          ScheduledExecutorService service = Executors.newScheduledThreadPool(
1✔
580
              1,
581
              getThreadFactory("grpc-timer-%d", true));
1✔
582

583
          // If there are long timeouts that are cancelled, they will not actually be removed from
584
          // the executors queue.  This forces immediate removal upon cancellation to avoid a
585
          // memory leak.  Reflection is used because we cannot use methods added in Java 1.7.  If
586
          // the method does not exist, we give up.  Note that the method is not present in 1.6, but
587
          // _is_ present in the android standard library.
588
          try {
589
            Method method = service.getClass().getMethod("setRemoveOnCancelPolicy", boolean.class);
1✔
590
            method.invoke(service, true);
1✔
591
          } catch (NoSuchMethodException e) {
×
592
            // no op
593
          } catch (RuntimeException e) {
×
594
            throw e;
×
595
          } catch (Exception e) {
×
596
            throw new RuntimeException(e);
×
597
          }
1✔
598

599
          return Executors.unconfigurableScheduledExecutorService(service);
1✔
600
        }
601

602
        @Override
603
        public void close(ScheduledExecutorService instance) {
604
          instance.shutdown();
1✔
605
        }
1✔
606
      };
607

608

609
  /**
610
   * Get a {@link ThreadFactory} suitable for use in the current environment.
611
   * @param nameFormat to apply to threads created by the factory.
612
   * @param daemon {@code true} if the threads the factory creates are daemon threads, {@code false}
613
   *     otherwise.
614
   * @return a {@link ThreadFactory}.
615
   */
616
  public static ThreadFactory getThreadFactory(String nameFormat, boolean daemon) {
617
    return new ThreadFactoryBuilder()
1✔
618
        .setDaemon(daemon)
1✔
619
        .setNameFormat(nameFormat)
1✔
620
        .build();
1✔
621
  }
622

623
  /**
624
   * The factory of default Stopwatches.
625
   */
626
  public static final Supplier<Stopwatch> STOPWATCH_SUPPLIER = new Supplier<Stopwatch>() {
1✔
627
      @Override
628
      public Stopwatch get() {
629
        return Stopwatch.createUnstarted();
1✔
630
      }
631
    };
632

633
  /**
634
   * Marshals a nanoseconds representation of the timeout to and from a string representation,
635
   * consisting of an ASCII decimal representation of a number with at most 8 digits, followed by a
636
   * unit. Available units:
637
   * n = nanoseconds
638
   * u = microseconds
639
   * m = milliseconds
640
   * S = seconds
641
   * M = minutes
642
   * H = hours
643
   *
644
   * <p>The representation is greedy with respect to precision. That is, 2 seconds will be
645
   * represented as `2000000u`.</p>
646
   *
647
   * <p>See <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests">the
648
   * request header definition</a></p>
649
   */
650
  @VisibleForTesting
651
  static class TimeoutMarshaller implements Metadata.AsciiMarshaller<Long> {
1✔
652

653
    @Override
654
    public String toAsciiString(Long timeoutNanosObject) {
655
      long cutoff = 100000000;
1✔
656
      // Timeout checking is inherently racy. RPCs with timeouts in the past ideally don't even get
657
      // here, but if the timeout is expired assume that happened recently and adjust it to the
658
      // smallest allowed timeout
659
      long timeoutNanos = Math.max(1, timeoutNanosObject);
1✔
660
      TimeUnit unit = TimeUnit.NANOSECONDS;
1✔
661
      if (timeoutNanos < cutoff) {
1✔
662
        return timeoutNanos + "n";
1✔
663
      } else if (timeoutNanos < cutoff * 1000L) {
1✔
664
        return unit.toMicros(timeoutNanos) + "u";
1✔
665
      } else if (timeoutNanos < cutoff * 1000L * 1000L) {
1✔
666
        return unit.toMillis(timeoutNanos) + "m";
1✔
667
      } else if (timeoutNanos < cutoff * 1000L * 1000L * 1000L) {
1✔
668
        return unit.toSeconds(timeoutNanos) + "S";
1✔
669
      } else if (timeoutNanos < cutoff * 1000L * 1000L * 1000L * 60L) {
1✔
670
        return unit.toMinutes(timeoutNanos) + "M";
1✔
671
      } else {
672
        return unit.toHours(timeoutNanos) + "H";
1✔
673
      }
674
    }
675

676
    @Override
677
    public Long parseAsciiString(String serialized) {
678
      checkArgument(serialized.length() > 0, "empty timeout");
1✔
679
      checkArgument(serialized.length() <= 9, "bad timeout format");
1✔
680
      long value = Long.parseLong(serialized.substring(0, serialized.length() - 1));
1✔
681
      char unit = serialized.charAt(serialized.length() - 1);
1✔
682
      switch (unit) {
1✔
683
        case 'n':
684
          return value;
1✔
685
        case 'u':
686
          return TimeUnit.MICROSECONDS.toNanos(value);
1✔
687
        case 'm':
688
          return TimeUnit.MILLISECONDS.toNanos(value);
1✔
689
        case 'S':
690
          return TimeUnit.SECONDS.toNanos(value);
1✔
691
        case 'M':
692
          return TimeUnit.MINUTES.toNanos(value);
1✔
693
        case 'H':
694
          return TimeUnit.HOURS.toNanos(value);
1✔
695
        default:
696
          throw new IllegalArgumentException(String.format("Invalid timeout unit: %s", unit));
×
697
      }
698
    }
699
  }
700

701
  /**
702
   * Returns a transport out of a PickResult, or {@code null} if the result is "buffer".
703
   */
704
  @Nullable
705
  static ClientTransport getTransportFromPickResult(PickResult result, boolean isWaitForReady) {
706
    final ClientTransport transport;
707
    Subchannel subchannel = result.getSubchannel();
1✔
708
    if (subchannel != null) {
1✔
709
      transport = ((TransportProvider) subchannel.getInternalSubchannel()).obtainActiveTransport();
1✔
710
    } else {
711
      transport = null;
1✔
712
    }
713
    if (transport != null) {
1✔
714
      final ClientStreamTracer.Factory streamTracerFactory = result.getStreamTracerFactory();
1✔
715
      if (streamTracerFactory == null) {
1✔
716
        return transport;
1✔
717
      }
718
      return new ClientTransport() {
1✔
719
        @Override
720
        public ClientStream newStream(
721
            MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions,
722
            ClientStreamTracer[] tracers) {
723
          StreamInfo info = StreamInfo.newBuilder().setCallOptions(callOptions).build();
1✔
724
          ClientStreamTracer streamTracer =
1✔
725
              streamTracerFactory.newClientStreamTracer(info, headers);
1✔
726
          checkState(tracers[tracers.length - 1] == NOOP_TRACER, "lb tracer already assigned");
1✔
727
          tracers[tracers.length - 1] = streamTracer;
1✔
728
          return transport.newStream(method, headers, callOptions, tracers);
1✔
729
        }
730

731
        @Override
732
        public void ping(PingCallback callback, Executor executor) {
733
          transport.ping(callback, executor);
×
734
        }
×
735

736
        @Override
737
        public InternalLogId getLogId() {
738
          return transport.getLogId();
×
739
        }
740

741
        @Override
742
        public ListenableFuture<SocketStats> getStats() {
743
          return transport.getStats();
×
744
        }
745
      };
746
    }
747
    if (!result.getStatus().isOk()) {
1✔
748
      if (result.isDrop()) {
1✔
749
        return new FailingClientTransport(
1✔
750
            replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.DROPPED);
1✔
751
      }
752
      if (!isWaitForReady) {
1✔
753
        return new FailingClientTransport(
1✔
754
            replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.PROCESSED);
1✔
755
      }
756
    }
757
    return null;
1✔
758
  }
759

760
  /** Gets stream tracers based on CallOptions. */
761
  public static ClientStreamTracer[] getClientStreamTracers(
762
      CallOptions callOptions, Metadata headers, int previousAttempts, boolean isTransparentRetry,
763
      boolean isHedging) {
764
    List<ClientStreamTracer.Factory> factories = callOptions.getStreamTracerFactories();
1✔
765
    ClientStreamTracer[] tracers = new ClientStreamTracer[factories.size() + 1];
1✔
766
    StreamInfo streamInfo = StreamInfo.newBuilder()
1✔
767
        .setCallOptions(callOptions)
1✔
768
        .setPreviousAttempts(previousAttempts)
1✔
769
        .setIsTransparentRetry(isTransparentRetry)
1✔
770
        .setIsHedging(isHedging)
1✔
771
        .build();
1✔
772
    for (int i = 0; i < factories.size(); i++) {
1✔
773
      tracers[i] = factories.get(i).newClientStreamTracer(streamInfo, headers);
1✔
774
    }
775
    // Reserved to be set later by the lb as per the API contract of ClientTransport.newStream().
776
    // See also GrpcUtil.getTransportFromPickResult()
777
    tracers[tracers.length - 1] = NOOP_TRACER;
1✔
778
    return tracers;
1✔
779
  }
780

781
  /** Quietly closes all messages in MessageProducer. */
782
  static void closeQuietly(MessageProducer producer) {
783
    InputStream message;
784
    while ((message = producer.next()) != null) {
1✔
785
      closeQuietly(message);
1✔
786
    }
787
  }
1✔
788

789
  /**
790
   * Closes a Closeable, ignoring IOExceptions.
791
   * This method exists because Guava's {@code Closeables.closeQuietly()} is beta.
792
   */
793
  public static void closeQuietly(@Nullable Closeable message) {
794
    if (message == null) {
1✔
795
      return;
×
796
    }
797
    try {
798
      message.close();
1✔
799
    } catch (IOException ioException) {
×
800
      // do nothing except log
801
      log.log(Level.WARNING, "exception caught in closeQuietly", ioException);
×
802
    }
1✔
803
  }
1✔
804

805
  /** Reads {@code in} until end of stream. */
806
  public static void exhaust(InputStream in) throws IOException {
807
    byte[] buf = new byte[256];
1✔
808
    while (in.read(buf) != -1) {}
1✔
809
  }
1✔
810

811
  /**
812
   * Some status codes from the control plane are not appropritate to use in the data plane. If one
813
   * is given it will be replaced with INTERNAL, indicating a bug in the control plane
814
   * implementation.
815
   */
816
  public static Status replaceInappropriateControlPlaneStatus(Status status) {
817
    checkArgument(status != null);
1✔
818
    return INAPPROPRIATE_CONTROL_PLANE_STATUS.contains(status.getCode())
1✔
819
        ? Status.INTERNAL.withDescription(
1✔
820
        "Inappropriate status code from control plane: " + status.getCode() + " "
1✔
821
            + status.getDescription()).withCause(status.getCause()) : status;
1✔
822
  }
823

824
  /**
825
   * Checks whether the given item exists in the iterable.  This is copied from Guava Collect's
826
   * {@code Iterables.contains()} because Guava Collect is not Android-friendly thus core can't
827
   * depend on it.
828
   */
829
  static <T> boolean iterableContains(Iterable<T> iterable, T item) {
830
    if (iterable instanceof Collection) {
1✔
831
      Collection<?> collection = (Collection<?>) iterable;
×
832
      try {
833
        return collection.contains(item);
×
834
      } catch (NullPointerException e) {
×
835
        return false;
×
836
      } catch (ClassCastException e) {
×
837
        return false;
×
838
      }
839
    }
840
    for (T i : iterable) {
1✔
841
      if (Objects.equal(i, item)) {
1✔
842
        return true;
1✔
843
      }
844
    }
1✔
845
    return false;
1✔
846
  }
847

848
  /**
849
   * Percent encode the {@code authority} based on
850
   * https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.
851
   *
852
   * <p>When escaping a String, the following rules apply:
853
   *
854
   * <ul>
855
   *   <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0" through "9" remain
856
   *       the same.
857
   *   <li>The unreserved characters ".", "-", "~", and "_" remain the same.
858
   *   <li>The general delimiters for authority, "[", "]", "@" and ":" remain the same.
859
   *   <li>The subdelimiters "!", "$", "&amp;", "'", "(", ")", "*", "+", ",", ";", and "=" remain
860
   *       the same.
861
   *   <li>The space character " " is converted into %20.
862
   *   <li>All other characters are converted into one or more bytes using UTF-8 encoding and each
863
   *       byte is then represented by the 3-character string "%XY", where "XY" is the two-digit,
864
   *       uppercase, hexadecimal representation of the byte value.
865
   * </ul>
866
   * 
867
   * <p>This section does not use URLEscapers from Guava Net as its not Android-friendly thus core
868
   *    can't depend on it.
869
   */
870
  public static class AuthorityEscaper {
×
871
    // Escapers should output upper case hex digits.
872
    private static final char[] UPPER_HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1✔
873
    private static final Set<Character> UNRESERVED_CHARACTERS = Collections
1✔
874
        .unmodifiableSet(new HashSet<>(Arrays.asList('-', '_', '.', '~')));
1✔
875
    private static final Set<Character> SUB_DELIMS = Collections
1✔
876
        .unmodifiableSet(new HashSet<>(
1✔
877
            Arrays.asList('!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=')));
1✔
878
    private static final Set<Character> AUTHORITY_DELIMS = Collections
1✔
879
        .unmodifiableSet(new HashSet<>(Arrays.asList(':', '[', ']', '@')));
1✔
880

881
    private static boolean shouldEscape(char c) {
882
      // Only encode ASCII.
883
      if (c > 127) {
1✔
884
        return false;
1✔
885
      }
886
      // Letters don't need an escape.
887
      if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))) {
1✔
888
        return false;
1✔
889
      }
890
      // Numbers don't need to be escaped.
891
      if ((c >= '0' && c <= '9'))  {
1✔
892
        return false;
1✔
893
      }
894
      // Don't escape allowed characters.
895
      if (UNRESERVED_CHARACTERS.contains(c)
1✔
896
          || SUB_DELIMS.contains(c)
1✔
897
          || AUTHORITY_DELIMS.contains(c)) {
1✔
898
        return false;
1✔
899
      }
900
      return true;
1✔
901
    }
902

903
    public static String encodeAuthority(String authority) {
904
      Preconditions.checkNotNull(authority, "authority");
1✔
905
      int authorityLength = authority.length();
1✔
906
      int hexCount = 0;
1✔
907
      // Calculate how many characters actually need escaping.
908
      for (int index = 0; index < authorityLength; index++) {
1✔
909
        char c = authority.charAt(index);
1✔
910
        if (shouldEscape(c)) {
1✔
911
          hexCount++;
1✔
912
        }
913
      }
914
      // If no char need escaping, just return the original string back.
915
      if (hexCount == 0) {
1✔
916
        return authority;
1✔
917
      }
918

919
      // Allocate enough space as encoded characters need 2 extra chars.
920
      StringBuilder encoded_authority = new StringBuilder((2 * hexCount) + authorityLength);
1✔
921
      for (int index = 0; index < authorityLength; index++) {
1✔
922
        char c = authority.charAt(index);
1✔
923
        if (shouldEscape(c)) {
1✔
924
          encoded_authority.append('%');
1✔
925
          encoded_authority.append(UPPER_HEX_DIGITS[c >>> 4]);
1✔
926
          encoded_authority.append(UPPER_HEX_DIGITS[c & 0xF]);
1✔
927
        } else {
928
          encoded_authority.append(c);
1✔
929
        }
930
      }
931
      return encoded_authority.toString();
1✔
932
    }
933
  }
934

935
  public static boolean getFlag(String envVarName, boolean enableByDefault) {
936
    String envVar = System.getenv(envVarName);
1✔
937
    if (envVar == null) {
1✔
938
      envVar = System.getProperty(envVarName);
1✔
939
    }
940
    if (envVar != null) {
1✔
941
      envVar = envVar.trim();
1✔
942
    }
943
    if (enableByDefault) {
1✔
944
      return Strings.isNullOrEmpty(envVar) || Boolean.parseBoolean(envVar);
1✔
945
    } else {
946
      return !Strings.isNullOrEmpty(envVar) && Boolean.parseBoolean(envVar);
1✔
947
    }
948
  }
949

950

951

952
  private GrpcUtil() {}
953
}
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

© 2025 Coveralls, Inc