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

grpc / grpc-java / #20034

29 Oct 2025 05:05PM UTC coverage: 88.514% (-0.02%) from 88.533%
#20034

push

github

ejona86
Include causal status details in higher-level statuses

When an operation fails and we want to produce a new status at a higher
level, we commonly are turning the first status into an exception to
attach to the new exception. We should instead prefer to keep as much
information in the status description itself, as cause is not as
reliable to be logged/propagated.

I do expect long-term we'll want to expose an API in grpc-api for this,
but for the moment let's keep it internal. In particular, we'd have to
figure out its name. I could also believe we might want different
formatting, which becomes a clearer discussion when we can see the
usages.

I'm pretty certain there are some other places that could benefit from
this utility, as I remember really wishing I had these functions a month
or two ago. But these are the places I could immediately find.

OutlierDetectionLoadBalancerConfig had its status code changed from
INTERNAL to UNAVAILABLE because the value comes externally, and so isn't
a gRPC bug or such. I didn't change the xds policies in the same way
because it's murkier as the configuration for those is largely generated
within xds itself.

34957 of 39493 relevant lines covered (88.51%)

0.89 hits per line

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

93.07
/../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
   * Returns a "clean" representation of a status code and description (not cause) like
826
   * "UNAVAILABLE: The description". Should be similar to Status.formatThrowableMessage().
827
   */
828
  public static String statusToPrettyString(Status status) {
829
    if (status.getDescription() == null) {
1✔
830
      return status.getCode().toString();
1✔
831
    } else {
832
      return status.getCode() + ": " + status.getDescription();
1✔
833
    }
834
  }
835

836
  /**
837
   * Create a status with contextual information, propagating details from a non-null status that
838
   * contributed to the failure. For example, if UNAVAILABLE, "Couldn't load bar", and status
839
   * "FAILED_PRECONDITION: Foo missing" were passed as arguments, then this method would produce the
840
   * status "UNAVAILABLE: Couldn't load bar: FAILED_PRECONDITION: Foo missing" with a cause if the
841
   * passed status had a cause.
842
   */
843
  public static Status statusWithDetails(Status.Code code, String description, Status causeStatus) {
844
    return code.toStatus()
1✔
845
        .withDescription(description + ": " + statusToPrettyString(causeStatus))
1✔
846
        .withCause(causeStatus.getCause());
1✔
847
  }
848

849
  /**
850
   * Checks whether the given item exists in the iterable.  This is copied from Guava Collect's
851
   * {@code Iterables.contains()} because Guava Collect is not Android-friendly thus core can't
852
   * depend on it.
853
   */
854
  static <T> boolean iterableContains(Iterable<T> iterable, T item) {
855
    if (iterable instanceof Collection) {
1✔
856
      Collection<?> collection = (Collection<?>) iterable;
×
857
      try {
858
        return collection.contains(item);
×
859
      } catch (NullPointerException e) {
×
860
        return false;
×
861
      } catch (ClassCastException e) {
×
862
        return false;
×
863
      }
864
    }
865
    for (T i : iterable) {
1✔
866
      if (Objects.equal(i, item)) {
1✔
867
        return true;
1✔
868
      }
869
    }
1✔
870
    return false;
1✔
871
  }
872

873
  /**
874
   * Percent encode the {@code authority} based on
875
   * https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.
876
   *
877
   * <p>When escaping a String, the following rules apply:
878
   *
879
   * <ul>
880
   *   <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0" through "9" remain
881
   *       the same.
882
   *   <li>The unreserved characters ".", "-", "~", and "_" remain the same.
883
   *   <li>The general delimiters for authority, "[", "]", "@" and ":" remain the same.
884
   *   <li>The subdelimiters "!", "$", "&amp;", "'", "(", ")", "*", "+", ",", ";", and "=" remain
885
   *       the same.
886
   *   <li>The space character " " is converted into %20.
887
   *   <li>All other characters are converted into one or more bytes using UTF-8 encoding and each
888
   *       byte is then represented by the 3-character string "%XY", where "XY" is the two-digit,
889
   *       uppercase, hexadecimal representation of the byte value.
890
   * </ul>
891
   * 
892
   * <p>This section does not use URLEscapers from Guava Net as its not Android-friendly thus core
893
   *    can't depend on it.
894
   */
895
  public static class AuthorityEscaper {
×
896
    // Escapers should output upper case hex digits.
897
    private static final char[] UPPER_HEX_DIGITS = "0123456789ABCDEF".toCharArray();
1✔
898
    private static final Set<Character> UNRESERVED_CHARACTERS = Collections
1✔
899
        .unmodifiableSet(new HashSet<>(Arrays.asList('-', '_', '.', '~')));
1✔
900
    private static final Set<Character> SUB_DELIMS = Collections
1✔
901
        .unmodifiableSet(new HashSet<>(
1✔
902
            Arrays.asList('!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=')));
1✔
903
    private static final Set<Character> AUTHORITY_DELIMS = Collections
1✔
904
        .unmodifiableSet(new HashSet<>(Arrays.asList(':', '[', ']', '@')));
1✔
905

906
    private static boolean shouldEscape(char c) {
907
      // Only encode ASCII.
908
      if (c > 127) {
1✔
909
        return false;
1✔
910
      }
911
      // Letters don't need an escape.
912
      if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))) {
1✔
913
        return false;
1✔
914
      }
915
      // Numbers don't need to be escaped.
916
      if ((c >= '0' && c <= '9'))  {
1✔
917
        return false;
1✔
918
      }
919
      // Don't escape allowed characters.
920
      if (UNRESERVED_CHARACTERS.contains(c)
1✔
921
          || SUB_DELIMS.contains(c)
1✔
922
          || AUTHORITY_DELIMS.contains(c)) {
1✔
923
        return false;
1✔
924
      }
925
      return true;
1✔
926
    }
927

928
    public static String encodeAuthority(String authority) {
929
      Preconditions.checkNotNull(authority, "authority");
1✔
930
      int authorityLength = authority.length();
1✔
931
      int hexCount = 0;
1✔
932
      // Calculate how many characters actually need escaping.
933
      for (int index = 0; index < authorityLength; index++) {
1✔
934
        char c = authority.charAt(index);
1✔
935
        if (shouldEscape(c)) {
1✔
936
          hexCount++;
1✔
937
        }
938
      }
939
      // If no char need escaping, just return the original string back.
940
      if (hexCount == 0) {
1✔
941
        return authority;
1✔
942
      }
943

944
      // Allocate enough space as encoded characters need 2 extra chars.
945
      StringBuilder encoded_authority = new StringBuilder((2 * hexCount) + authorityLength);
1✔
946
      for (int index = 0; index < authorityLength; index++) {
1✔
947
        char c = authority.charAt(index);
1✔
948
        if (shouldEscape(c)) {
1✔
949
          encoded_authority.append('%');
1✔
950
          encoded_authority.append(UPPER_HEX_DIGITS[c >>> 4]);
1✔
951
          encoded_authority.append(UPPER_HEX_DIGITS[c & 0xF]);
1✔
952
        } else {
953
          encoded_authority.append(c);
1✔
954
        }
955
      }
956
      return encoded_authority.toString();
1✔
957
    }
958
  }
959

960
  public static boolean getFlag(String envVarName, boolean enableByDefault) {
961
    String envVar = System.getenv(envVarName);
1✔
962
    if (envVar == null) {
1✔
963
      envVar = System.getProperty(envVarName);
1✔
964
    }
965
    if (envVar != null) {
1✔
966
      envVar = envVar.trim();
1✔
967
    }
968
    if (enableByDefault) {
1✔
969
      return Strings.isNullOrEmpty(envVar) || Boolean.parseBoolean(envVar);
1✔
970
    } else {
971
      return !Strings.isNullOrEmpty(envVar) && Boolean.parseBoolean(envVar);
1✔
972
    }
973
  }
974

975

976

977
  private GrpcUtil() {}
978
}
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