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

grpc / grpc-java / #20314

11 Jun 2026 05:34PM UTC coverage: 88.883% (+0.002%) from 88.881%
#20314

push

github

web-flow
Upgrade to Netty 4.2.15

Netty 4.2 has the thread classes interact in a new way, so they've
deprecated the Netty 4.1 threading APIs. While we want to use Netty 4.2,
not all build systems compiling gRPC may have upgraded to 4.2 yet, so
let's not begin using Netty 4.2 APIs yet.

36508 of 41074 relevant lines covered (88.88%)

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