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

grpc / grpc-java / #19898

03 Jul 2025 06:14AM UTC coverage: 88.537% (-0.004%) from 88.541%
#19898

push

github

web-flow
core: grpc-timeout should always be positive (#12201)

PROTOCOL-HTTP2.md specifies "TimeoutValue → {positive integer as ASCII
string of at most 8 digits}". Zero is not positive, so it should be
avoided. So make sure timeouts are at least 1 nanosecond instead of 0
nanoseconds.

grpc-go recently began disallowing zero timeouts in
https://github.com/grpc/grpc-go/pull/8290 which caused a regression as
grpc-java can generate such timeouts. Apparently no gRPC implementation
had previously been checking for zero timeouts.

Instead of changing the max(0) to max(1) everywhere, just move the max
handling into TimeoutMarshaller, since every caller of TIMEOUT_KEY was
doing the same max() handling.

Before fd8fd517d (in 2016!), grpc-java actually behaved correctly, as it
failed RPCs with timeouts "<= 0". The commit changed the handling to the
max(0) handling we see now.

b/427338711

34650 of 39136 relevant lines covered (88.54%)

0.89 hits per line

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

92.91
/../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.75.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
    List<ClientStreamTracer.Factory> factories = callOptions.getStreamTracerFactories();
1✔
764
    ClientStreamTracer[] tracers = new ClientStreamTracer[factories.size() + 1];
1✔
765
    StreamInfo streamInfo = StreamInfo.newBuilder()
1✔
766
        .setCallOptions(callOptions)
1✔
767
        .setPreviousAttempts(previousAttempts)
1✔
768
        .setIsTransparentRetry(isTransparentRetry)
1✔
769
        .build();
1✔
770
    for (int i = 0; i < factories.size(); i++) {
1✔
771
      tracers[i] = factories.get(i).newClientStreamTracer(streamInfo, headers);
1✔
772
    }
773
    // Reserved to be set later by the lb as per the API contract of ClientTransport.newStream().
774
    // See also GrpcUtil.getTransportFromPickResult()
775
    tracers[tracers.length - 1] = NOOP_TRACER;
1✔
776
    return tracers;
1✔
777
  }
778

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

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

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

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

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

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

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

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

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

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

948

949

950
  private GrpcUtil() {}
951
}
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