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

grpc / grpc-java / #20349

07 Jul 2026 10:24AM UTC coverage: 89.111% (-0.01%) from 89.122%
#20349

push

github

web-flow
enable child channel plugins (#12578)

Implements https://github.com/grpc/proposal/pull/529

38030 of 42677 relevant lines covered (89.11%)

0.89 hits per line

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

84.36
/../netty/src/main/java/io/grpc/netty/Utils.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.netty;
18

19
import static com.google.common.base.Preconditions.checkState;
20
import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_KEY;
21
import static io.grpc.internal.TransportFrameUtil.toHttp2Headers;
22
import static io.grpc.internal.TransportFrameUtil.toRawSerializedHeaders;
23
import static io.netty.channel.ChannelOption.SO_LINGER;
24
import static io.netty.channel.ChannelOption.SO_TIMEOUT;
25
import static io.netty.util.CharsetUtil.UTF_8;
26
import static java.nio.charset.StandardCharsets.US_ASCII;
27

28
import com.google.common.annotations.VisibleForTesting;
29
import com.google.common.base.Preconditions;
30
import com.google.errorprone.annotations.CheckReturnValue;
31
import io.grpc.InternalChannelz;
32
import io.grpc.InternalMetadata;
33
import io.grpc.Metadata;
34
import io.grpc.Status;
35
import io.grpc.internal.GrpcUtil;
36
import io.grpc.internal.SharedResourceHolder.Resource;
37
import io.grpc.internal.TransportTracer;
38
import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2InboundHeaders;
39
import io.grpc.netty.NettySocketSupport.NativeSocketOptions;
40
import io.netty.buffer.ByteBufAllocator;
41
import io.netty.buffer.PooledByteBufAllocator;
42
import io.netty.buffer.UnpooledByteBufAllocator;
43
import io.netty.channel.Channel;
44
import io.netty.channel.ChannelConfig;
45
import io.netty.channel.ChannelFactory;
46
import io.netty.channel.ChannelOption;
47
import io.netty.channel.EventLoopGroup;
48
import io.netty.channel.ReflectiveChannelFactory;
49
import io.netty.channel.ServerChannel;
50
import io.netty.channel.socket.nio.NioServerSocketChannel;
51
import io.netty.channel.socket.nio.NioSocketChannel;
52
import io.netty.handler.codec.DecoderException;
53
import io.netty.handler.codec.http2.Http2Connection;
54
import io.netty.handler.codec.http2.Http2Exception;
55
import io.netty.handler.codec.http2.Http2FlowController;
56
import io.netty.handler.codec.http2.Http2Headers;
57
import io.netty.handler.codec.http2.Http2Stream;
58
import io.netty.util.AsciiString;
59
import io.netty.util.NettyRuntime;
60
import io.netty.util.concurrent.DefaultThreadFactory;
61
import java.io.IOException;
62
import java.lang.reflect.Constructor;
63
import java.nio.channels.ClosedChannelException;
64
import java.nio.channels.UnresolvedAddressException;
65
import java.util.Locale;
66
import java.util.Map;
67
import java.util.concurrent.ThreadFactory;
68
import java.util.concurrent.TimeUnit;
69
import java.util.logging.Level;
70
import java.util.logging.Logger;
71
import javax.annotation.Nullable;
72
import javax.net.ssl.SSLException;
73

74
/**
75
 * Common utility methods.
76
 */
77
class Utils {
78
  private static final Logger logger = Logger.getLogger(Utils.class.getName());
1✔
79

80
  public static final AsciiString STATUS_OK = AsciiString.of("200");
1✔
81
  public static final AsciiString HTTP_METHOD = AsciiString.of(GrpcUtil.HTTP_METHOD);
1✔
82
  public static final AsciiString HTTP_GET_METHOD = AsciiString.of("GET");
1✔
83
  public static final AsciiString HTTPS = AsciiString.of("https");
1✔
84
  public static final AsciiString HTTP = AsciiString.of("http");
1✔
85
  public static final AsciiString CONTENT_TYPE_HEADER = AsciiString.of(CONTENT_TYPE_KEY.name());
1✔
86
  public static final AsciiString CONTENT_TYPE_GRPC = AsciiString.of(GrpcUtil.CONTENT_TYPE_GRPC);
1✔
87
  public static final AsciiString TE_HEADER = AsciiString.of(GrpcUtil.TE_HEADER.name());
1✔
88
  public static final AsciiString TE_TRAILERS = AsciiString.of(GrpcUtil.TE_TRAILERS);
1✔
89
  public static final AsciiString USER_AGENT = AsciiString.of(GrpcUtil.USER_AGENT_KEY.name());
1✔
90
  public static final Resource<EventLoopGroup> NIO_BOSS_EVENT_LOOP_GROUP
1✔
91
      = new DefaultEventLoopGroupResource(1, "grpc-nio-boss-ELG", EventLoopGroupType.NIO);
92
  public static final Resource<EventLoopGroup> NIO_WORKER_EVENT_LOOP_GROUP
1✔
93
      = new DefaultEventLoopGroupResource(0, "grpc-nio-worker-ELG", EventLoopGroupType.NIO);
94
  private static final int HEADER_ENTRY_OVERHEAD = 32;
95
  private static final byte[] binaryHeaderSuffixBytes =
1✔
96
      Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII);
1✔
97
  public static final Resource<EventLoopGroup> DEFAULT_BOSS_EVENT_LOOP_GROUP;
98
  public static final Resource<EventLoopGroup> DEFAULT_WORKER_EVENT_LOOP_GROUP;
99

100
  // This class is initialized on first use, thus provides delayed allocator creation.
101
  private static final class ByteBufAllocatorPreferDirectHolder {
102
    private static final ByteBufAllocator allocator = createByteBufAllocator(true);
1✔
103
  }
104

105
  // This class is initialized on first use, thus provides delayed allocator creation.
106
  private static final class ByteBufAllocatorPreferHeapHolder {
107
    private static final ByteBufAllocator allocator = createByteBufAllocator(false);
1✔
108
  }
109

110
  public static final ChannelFactory<? extends ServerChannel> DEFAULT_SERVER_CHANNEL_FACTORY;
111
  public static final Class<? extends Channel> DEFAULT_CLIENT_CHANNEL_TYPE;
112
  public static final Class<? extends Channel> EPOLL_DOMAIN_CLIENT_CHANNEL_TYPE;
113

114
  @Nullable
115
  private static final Constructor<? extends EventLoopGroup> EPOLL_EVENT_LOOP_GROUP_CONSTRUCTOR;
116

117
  static {
118
    // Decide default channel types and EventLoopGroup based on Epoll availability
119
    if (isEpollAvailable()) {
1✔
120
      DEFAULT_CLIENT_CHANNEL_TYPE = epollChannelType();
1✔
121
      EPOLL_DOMAIN_CLIENT_CHANNEL_TYPE = epollDomainSocketChannelType();
1✔
122
      DEFAULT_SERVER_CHANNEL_FACTORY = new ReflectiveChannelFactory<>(epollServerChannelType());
1✔
123
      EPOLL_EVENT_LOOP_GROUP_CONSTRUCTOR = epollEventLoopGroupConstructor();
1✔
124
      DEFAULT_BOSS_EVENT_LOOP_GROUP = new DefaultEventLoopGroupResource(
1✔
125
          1, "grpc-default-boss-ELG", EventLoopGroupType.EPOLL);
126
      DEFAULT_WORKER_EVENT_LOOP_GROUP = new DefaultEventLoopGroupResource(
1✔
127
          0, "grpc-default-worker-ELG", EventLoopGroupType.EPOLL);
128
    } else {
129
      logger.log(Level.FINE, "Epoll is not available, using Nio.", getEpollUnavailabilityCause());
1✔
130
      DEFAULT_SERVER_CHANNEL_FACTORY = nioServerChannelFactory();
1✔
131
      DEFAULT_CLIENT_CHANNEL_TYPE = NioSocketChannel.class;
1✔
132
      EPOLL_DOMAIN_CLIENT_CHANNEL_TYPE = null;
1✔
133
      DEFAULT_BOSS_EVENT_LOOP_GROUP = NIO_BOSS_EVENT_LOOP_GROUP;
1✔
134
      DEFAULT_WORKER_EVENT_LOOP_GROUP = NIO_WORKER_EVENT_LOOP_GROUP;
1✔
135
      EPOLL_EVENT_LOOP_GROUP_CONSTRUCTOR = null;
1✔
136
    }
137
  }
1✔
138

139
  public static ByteBufAllocator getByteBufAllocator(boolean forceHeapBuffer) {
140
    if (Boolean.parseBoolean(
1✔
141
            System.getProperty("io.grpc.netty.useCustomAllocator", "true"))) {
1✔
142

143
      String allocType = System.getProperty("io.netty.allocator.type", "pooled");
1✔
144
      if (allocType.toLowerCase(Locale.ROOT).equals("unpooled")) {
1✔
145
        logger.log(Level.FINE, "Using unpooled allocator");
1✔
146
        return UnpooledByteBufAllocator.DEFAULT;
1✔
147
      }
148

149
      boolean defaultPreferDirect = PooledByteBufAllocator.defaultPreferDirect();
1✔
150
      logger.log(
1✔
151
          Level.FINE,
152
          "Using custom allocator: forceHeapBuffer={0}, defaultPreferDirect={1}",
153
          new Object[] { forceHeapBuffer, defaultPreferDirect });
1✔
154
      if (forceHeapBuffer || !defaultPreferDirect) {
1✔
155
        return ByteBufAllocatorPreferHeapHolder.allocator;
1✔
156
      } else {
157
        return ByteBufAllocatorPreferDirectHolder.allocator;
1✔
158
      }
159
    } else {
160
      logger.log(Level.FINE, "Using default allocator");
×
161
      return ByteBufAllocator.DEFAULT;
×
162
    }
163
  }
164

165
  private static ByteBufAllocator createByteBufAllocator(boolean preferDirect) {
166
    int maxOrder;
167
    logger.log(Level.FINE, "Creating allocator, preferDirect=" + preferDirect);
1✔
168
    if (System.getProperty("io.netty.allocator.maxOrder") == null) {
1✔
169
      // See the implementation of PooledByteBufAllocator.  DEFAULT_MAX_ORDER in there is
170
      // 11, which makes chunk size to be 8192 << 11 = 16 MiB.  We want the chunk size to be
171
      // 2MiB, thus reducing the maxOrder to 8.
172
      maxOrder = 8;
1✔
173
      logger.log(Level.FINE, "Forcing maxOrder=" + maxOrder);
1✔
174
    } else {
175
      maxOrder = PooledByteBufAllocator.defaultMaxOrder();
×
176
      logger.log(Level.FINE, "Using default maxOrder=" + maxOrder);
×
177
    }
178
    return new PooledByteBufAllocator(
1✔
179
        preferDirect,
180
        PooledByteBufAllocator.defaultNumHeapArena(),
1✔
181
        // Assuming neither gRPC nor netty are using allocator.directBuffer() to request
182
        // specifically for direct buffers, which is true as I just checked, setting arenas to 0
183
        // will make sure no direct buffer is ever created.
184
        preferDirect ? PooledByteBufAllocator.defaultNumDirectArena() : 0,
1✔
185
        PooledByteBufAllocator.defaultPageSize(),
1✔
186
        maxOrder,
187
        PooledByteBufAllocator.defaultSmallCacheSize(),
1✔
188
        PooledByteBufAllocator.defaultNormalCacheSize(),
1✔
189
        PooledByteBufAllocator.defaultUseCacheForAllThreads());
1✔
190
  }
191

192
  public static Metadata convertHeaders(Http2Headers http2Headers) {
193
    if (http2Headers instanceof GrpcHttp2InboundHeaders) {
1✔
194
      GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
1✔
195
      return InternalMetadata.newMetadata(h.numHeaders(), h.namesAndValues());
1✔
196
    }
197
    return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
1✔
198
  }
199

200
  public static int getH2HeadersSize(Http2Headers http2Headers) {
201
    if (http2Headers instanceof GrpcHttp2InboundHeaders) {
1✔
202
      GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
1✔
203
      int size = 0;
1✔
204
      for (int i = 0; i < h.numHeaders(); i++) {
1✔
205
        size += h.namesAndValues()[2 * i].length;
1✔
206
        size +=
1✔
207
            maybeAddBinaryHeaderOverhead(h.namesAndValues()[2 * i], h.namesAndValues()[2 * i + 1]);
1✔
208
        size += HEADER_ENTRY_OVERHEAD;
1✔
209
      }
210
      return size;
1✔
211
    }
212

213
    // the binary header is not decoded yet, no need to add overhead.
214
    int size = 0;
×
215
    for (Map.Entry<CharSequence, CharSequence> entry : http2Headers) {
×
216
      size += entry.getKey().length();
×
217
      size += entry.getValue().length();
×
218
      size += HEADER_ENTRY_OVERHEAD;
×
219
    }
×
220
    return size;
×
221
  }
222

223
  private static int maybeAddBinaryHeaderOverhead(byte[] name, byte[] value) {
224
    if (endsWith(name, binaryHeaderSuffixBytes)) {
1✔
225
      return value.length * 4 / 3;
1✔
226
    }
227
    return value.length;
1✔
228
  }
229

230
  private static boolean endsWith(byte[] bytes, byte[] suffix) {
231
    if (bytes == null || suffix == null || bytes.length < suffix.length) {
1✔
232
      return false;
1✔
233
    }
234

235
    for (int i = 0; i < suffix.length; i++) {
1✔
236
      if (bytes[bytes.length - suffix.length + i] != suffix[i]) {
1✔
237
        return false;
1✔
238
      }
239
    }
240

241
    return true;
1✔
242
  }
243

244
  public static boolean shouldRejectOnMetadataSizeSoftLimitExceeded(
245
      int h2HeadersSize, int softLimitHeaderListSize, int maxHeaderListSize) {
246
    if (h2HeadersSize < softLimitHeaderListSize) {
1✔
247
      return false;
1✔
248
    }
249
    double failProbability =
1✔
250
        (double) (h2HeadersSize - softLimitHeaderListSize) / (double) (maxHeaderListSize
251
            - softLimitHeaderListSize);
252
    return Math.random() < failProbability;
1✔
253
  }
254

255
  @CheckReturnValue
256
  private static byte[][] convertHeadersToArray(Http2Headers http2Headers) {
257
    // The Netty AsciiString class is really just a wrapper around a byte[] and supports
258
    // arbitrary binary data, not just ASCII.
259
    byte[][] headerValues = new byte[http2Headers.size() * 2][];
1✔
260
    int i = 0;
1✔
261
    for (Map.Entry<CharSequence, CharSequence> entry : http2Headers) {
1✔
262
      headerValues[i++] = bytes(entry.getKey());
1✔
263
      headerValues[i++] = bytes(entry.getValue());
1✔
264
    }
1✔
265
    return toRawSerializedHeaders(headerValues);
1✔
266
  }
267

268
  private static byte[] bytes(CharSequence seq) {
269
    if (seq instanceof AsciiString) {
1✔
270
      // Fast path - sometimes copy.
271
      AsciiString str = (AsciiString) seq;
1✔
272
      return str.isEntireArrayUsed() ? str.array() : str.toByteArray();
1✔
273
    }
274
    // Slow path - copy.
275
    return seq.toString().getBytes(UTF_8);
1✔
276
  }
277

278
  public static Http2Headers convertClientHeaders(Metadata headers,
279
      AsciiString scheme,
280
      AsciiString defaultPath,
281
      AsciiString authority,
282
      AsciiString method,
283
      AsciiString userAgent) {
284
    Preconditions.checkNotNull(defaultPath, "defaultPath");
1✔
285
    Preconditions.checkNotNull(authority, "authority");
1✔
286
    Preconditions.checkNotNull(method, "method");
1✔
287

288
    // Discard any application supplied duplicates of the reserved headers
289
    headers.discardAll(CONTENT_TYPE_KEY);
1✔
290
    headers.discardAll(GrpcUtil.TE_HEADER);
1✔
291
    headers.discardAll(GrpcUtil.USER_AGENT_KEY);
1✔
292

293
    return GrpcHttp2OutboundHeaders.clientRequestHeaders(
1✔
294
        toHttp2Headers(headers),
1✔
295
        authority,
296
        defaultPath,
297
        method,
298
        scheme,
299
        userAgent);
300
  }
301

302
  public static Http2Headers convertServerHeaders(Metadata headers) {
303
    // Discard any application supplied duplicates of the reserved headers
304
    headers.discardAll(CONTENT_TYPE_KEY);
1✔
305
    headers.discardAll(GrpcUtil.TE_HEADER);
1✔
306
    headers.discardAll(GrpcUtil.USER_AGENT_KEY);
1✔
307

308
    return GrpcHttp2OutboundHeaders.serverResponseHeaders(toHttp2Headers(headers));
1✔
309
  }
310

311
  public static Metadata convertTrailers(Http2Headers http2Headers) {
312
    if (http2Headers instanceof GrpcHttp2InboundHeaders) {
1✔
313
      GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
1✔
314
      return InternalMetadata.newMetadata(h.numHeaders(), h.namesAndValues());
1✔
315
    }
316
    return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
1✔
317
  }
318

319
  public static Http2Headers convertTrailers(Metadata trailers, boolean headersSent) {
320
    if (!headersSent) {
1✔
321
      return convertServerHeaders(trailers);
1✔
322
    }
323
    return GrpcHttp2OutboundHeaders.serverResponseTrailers(toHttp2Headers(trailers));
1✔
324
  }
325

326
  public static Status statusFromThrowable(Throwable t) {
327
    Status s = Status.fromThrowable(t);
1✔
328
    if (s.getCode() != Status.Code.UNKNOWN) {
1✔
329
      return s;
1✔
330
    }
331
    if (t instanceof ClosedChannelException) {
1✔
332
      if (t.getCause() != null) {
1✔
333
        // If the remote closes the connection while the event loop is processing, then a write or
334
        // flush can be the first operation to notice the closure. Those exceptions are a
335
        // ClosedChannelException, with a cause that provides more information, which is exactly
336
        // what we'd hope for.
337
        return Status.UNAVAILABLE.withDescription("channel closed").withCause(t);
×
338
      }
339
      // ClosedChannelException is used for all operations after the Netty channel is closed. But it
340
      // doesn't have the original closure information. Proper error processing requires remembering
341
      // the error that occurred before this one and using it instead.
342
      //
343
      // Netty uses an exception that has no stack trace, while we would never hope to show this to
344
      // users, if it happens having the extra information may provide a small hint of where to
345
      // look.
346
      ClosedChannelException extraT = new ClosedChannelException();
1✔
347
      extraT.initCause(t);
1✔
348
      return Status.UNKNOWN.withDescription("channel closed").withCause(extraT);
1✔
349
    }
350
    if (t instanceof DecoderException && t.getCause() instanceof SSLException) {
1✔
351
      return Status.UNAVAILABLE.withDescription("ssl exception").withCause(t);
1✔
352
    }
353
    if (t instanceof IOException) {
1✔
354
      return Status.UNAVAILABLE.withDescription("io exception").withCause(t);
1✔
355
    }
356
    if (t instanceof UnresolvedAddressException) {
1✔
357
      return Status.UNAVAILABLE.withDescription("unresolved address").withCause(t);
1✔
358
    }
359
    if (t instanceof Http2Exception) {
1✔
360
      return Status.INTERNAL.withDescription("http2 exception").withCause(t);
1✔
361
    }
362
    return s;
1✔
363
  }
364

365
  @VisibleForTesting
366
  static boolean isEpollAvailable() {
367
    try {
368
      return (boolean) (Boolean)
1✔
369
          Class
370
              .forName("io.netty.channel.epoll.Epoll")
1✔
371
              .getDeclaredMethod("isAvailable")
1✔
372
              .invoke(null);
1✔
373
    } catch (ClassNotFoundException e) {
1✔
374
      // this is normal if netty-epoll runtime dependency doesn't exist.
375
      return false;
1✔
376
    } catch (Exception e) {
×
377
      throw new RuntimeException("Exception while checking Epoll availability", e);
×
378
    }
379
  }
380

381
  private static Throwable getEpollUnavailabilityCause() {
382
    try {
383
      return (Throwable)
1✔
384
          Class
385
              .forName("io.netty.channel.epoll.Epoll")
×
386
              .getDeclaredMethod("unavailabilityCause")
×
387
              .invoke(null);
×
388
    } catch (Exception e) {
1✔
389
      return e;
1✔
390
    }
391
  }
392

393
  // Must call when epoll is available
394
  private static Class<? extends Channel> epollChannelType() {
395
    try {
396
      Class<? extends Channel> channelType = Class
1✔
397
          .forName("io.netty.channel.epoll.EpollSocketChannel").asSubclass(Channel.class);
1✔
398
      return channelType;
1✔
399
    } catch (ClassNotFoundException e) {
×
400
      throw new RuntimeException("Cannot load EpollSocketChannel", e);
×
401
    }
402
  }
403

404
  // Must call when epoll is available
405
  private static Class<? extends Channel> epollDomainSocketChannelType() {
406
    try {
407
      Class<? extends Channel> channelType = Class
1✔
408
          .forName("io.netty.channel.epoll.EpollDomainSocketChannel").asSubclass(Channel.class);
1✔
409
      return channelType;
1✔
410
    } catch (ClassNotFoundException e) {
×
411
      throw new RuntimeException("Cannot load EpollDomainSocketChannel", e);
×
412
    }
413
  }
414

415
  // Must call when epoll is available
416
  private static Constructor<? extends EventLoopGroup> epollEventLoopGroupConstructor() {
417
    try {
418
      return Class
1✔
419
          .forName("io.netty.channel.epoll.EpollEventLoopGroup").asSubclass(EventLoopGroup.class)
1✔
420
          .getConstructor(Integer.TYPE, ThreadFactory.class);
1✔
421
    } catch (ClassNotFoundException e) {
×
422
      throw new RuntimeException("Cannot load EpollEventLoopGroup", e);
×
423
    } catch (NoSuchMethodException e) {
×
424
      throw new RuntimeException("EpollEventLoopGroup constructor not found", e);
×
425
    }
426
  }
427

428
  // Must call when epoll is available
429
  private static Class<? extends ServerChannel> epollServerChannelType() {
430
    try {
431
      Class<? extends ServerChannel> serverSocketChannel =
1✔
432
          Class
433
              .forName("io.netty.channel.epoll.EpollServerSocketChannel")
1✔
434
              .asSubclass(ServerChannel.class);
1✔
435
      return serverSocketChannel;
1✔
436
    } catch (ClassNotFoundException e) {
×
437
      throw new RuntimeException("Cannot load EpollServerSocketChannel", e);
×
438
    }
439
  }
440

441
  private static EventLoopGroup createEpollEventLoopGroup(
442
      int parallelism,
443
      ThreadFactory threadFactory) {
444
    checkState(EPOLL_EVENT_LOOP_GROUP_CONSTRUCTOR != null, "Epoll is not available");
1✔
445

446
    try {
447
      return EPOLL_EVENT_LOOP_GROUP_CONSTRUCTOR
1✔
448
          .newInstance(parallelism, threadFactory);
1✔
449
    } catch (Exception e) {
×
450
      throw new RuntimeException("Cannot create Epoll EventLoopGroup", e);
×
451
    }
452
  }
453

454
  private static ChannelFactory<ServerChannel> nioServerChannelFactory() {
455
    return new ChannelFactory<ServerChannel>() {
1✔
456
      @Override
457
      public ServerChannel newChannel() {
458
        return new NioServerSocketChannel();
1✔
459
      }
460
    };
461
  }
462

463
  /**
464
   * Returns TCP_USER_TIMEOUT channel option for Epoll channel if Epoll is available, otherwise
465
   * null.
466
   */
467
  @Nullable
468
  static ChannelOption<Integer> maybeGetTcpUserTimeoutOption() {
469
    return getEpollChannelOption("TCP_USER_TIMEOUT");
1✔
470
  }
471

472
  @Nullable
473
  @SuppressWarnings("unchecked")
474
  private static <T> ChannelOption<T> getEpollChannelOption(String optionName) {
475
    if (isEpollAvailable()) {
1✔
476
      try {
477
        return
1✔
478
            (ChannelOption<T>) Class.forName("io.netty.channel.epoll.EpollChannelOption")
1✔
479
                .getField(optionName)
1✔
480
                .get(null);
1✔
481
      } catch (Exception e) {
×
482
        throw new RuntimeException("ChannelOption(" + optionName + ") is not available", e);
×
483
      }
484
    }
485
    return null;
×
486
  }
487

488
  private static final class DefaultEventLoopGroupResource implements Resource<EventLoopGroup> {
489
    private final String name;
490
    private final int numEventLoops;
491
    private final EventLoopGroupType eventLoopGroupType;
492

493
    DefaultEventLoopGroupResource(
494
        int numEventLoops, String name, EventLoopGroupType eventLoopGroupType) {
1✔
495
      this.name = name;
1✔
496
      // See the implementation of MultithreadEventLoopGroup.  DEFAULT_EVENT_LOOP_THREADS there
497
      // defaults to NettyRuntime.availableProcessors() * 2.  We don't think we need that many
498
      // threads.  The overhead of a thread includes file descriptors and at least one chunk
499
      // allocation from PooledByteBufAllocator.  Here we reduce the default number of threads by
500
      // half.
501
      if (numEventLoops == 0 && System.getProperty("io.netty.eventLoopThreads") == null) {
1✔
502
        this.numEventLoops = NettyRuntime.availableProcessors();
1✔
503
      } else {
504
        this.numEventLoops = numEventLoops;
1✔
505
      }
506
      this.eventLoopGroupType = eventLoopGroupType;
1✔
507
    }
1✔
508

509
    @Override
510
    public EventLoopGroup create() {
511
      // Use Netty's DefaultThreadFactory in order to get the benefit of FastThreadLocal.
512
      ThreadFactory threadFactory = new DefaultThreadFactory(name, /* daemon= */ true);
1✔
513
      switch (eventLoopGroupType) {
1✔
514
        case NIO:
515
          @SuppressWarnings("deprecation") // Wait a bit before migrating to the Netty 4.2 API
516
          EventLoopGroup group =
1✔
517
              new io.netty.channel.nio.NioEventLoopGroup(numEventLoops, threadFactory);
518
          return group;
1✔
519
        case EPOLL:
520
          return createEpollEventLoopGroup(numEventLoops, threadFactory);
1✔
521
        default:
522
          throw new AssertionError("Unknown/Unsupported EventLoopGroupType: " + eventLoopGroupType);
×
523
      }
524
    }
525

526
    @Override
527
    public void close(EventLoopGroup instance) {
528
      instance.shutdownGracefully(0, 0, TimeUnit.SECONDS);
1✔
529
    }
1✔
530

531
    @Override
532
    public String toString() {
533
      return name;
×
534
    }
535
  }
536

537
  static final class FlowControlReader implements TransportTracer.FlowControlReader {
538
    private final Http2Stream connectionStream;
539
    private final Http2FlowController local;
540
    private final Http2FlowController remote;
541

542
    FlowControlReader(Http2Connection connection) {
1✔
543
      // 'local' in Netty is the _controller_ that controls inbound data. 'local' in Channelz is
544
      // the _present window_ provided by the remote that allows data to be sent. They are
545
      // opposites.
546
      local = connection.remote().flowController();
1✔
547
      remote = connection.local().flowController();
1✔
548
      connectionStream = connection.connectionStream();
1✔
549
    }
1✔
550

551
    @Override
552
    public TransportTracer.FlowControlWindows read() {
553
      return new TransportTracer.FlowControlWindows(
1✔
554
          local.windowSize(connectionStream),
1✔
555
          remote.windowSize(connectionStream));
1✔
556
    }
557
  }
558

559
  static InternalChannelz.SocketOptions getSocketOptions(Channel channel) {
560
    ChannelConfig config = channel.config();
1✔
561
    InternalChannelz.SocketOptions.Builder b = new InternalChannelz.SocketOptions.Builder();
1✔
562

563
    // The API allows returning null but not sure if it can happen in practice.
564
    // Let's be paranoid and do null checking just in case.
565
    Integer lingerSeconds = config.getOption(SO_LINGER);
1✔
566
    if (lingerSeconds != null) {
1✔
567
      b.setSocketOptionLingerSeconds(lingerSeconds);
1✔
568
    }
569

570
    Integer timeoutMillis = config.getOption(SO_TIMEOUT);
1✔
571
    if (timeoutMillis != null) {
1✔
572
      // in java, SO_TIMEOUT only applies to receiving
573
      b.setSocketOptionTimeoutMillis(timeoutMillis);
1✔
574
    }
575

576
    for (Map.Entry<ChannelOption<?>, Object> opt : config.getOptions().entrySet()) {
1✔
577
      ChannelOption<?> key = opt.getKey();
1✔
578
      // Constants are pooled, so there should only be one instance of each constant
579
      if (key.equals(SO_LINGER) || key.equals(SO_TIMEOUT)) {
1✔
580
        continue;
1✔
581
      }
582
      Object value = opt.getValue();
1✔
583
      // zpencer: Can a netty option be null?
584
      b.addOption(key.name(), String.valueOf(value));
1✔
585
    }
1✔
586

587
    NativeSocketOptions nativeOptions
1✔
588
        = NettySocketSupport.getNativeSocketOptions(channel);
1✔
589
    if (nativeOptions != null) {
1✔
590
      b.setTcpInfo(nativeOptions.tcpInfo); // may be null
×
591
      for (Map.Entry<String, String> entry : nativeOptions.otherInfo.entrySet()) {
×
592
        b.addOption(entry.getKey(), entry.getValue());
×
593
      }
×
594
    }
595
    return b.build();
1✔
596
  }
597

598
  private enum EventLoopGroupType {
1✔
599
    NIO,
1✔
600
    EPOLL
1✔
601
  }
602

603
  private Utils() {
604
    // Prevents instantiation
605
  }
606
}
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