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

grpc / grpc-java / #20535

16 Oct 2026 01:02PM UTC coverage: 89.464% (+0.04%) from 89.428%
#20535

push

github

web-flow
netty: drain before closing a gracefully shut down server connection (#13086)

Fixes #9566.

After the second GOAWAY of a graceful shutdown (`maxConnectionAge`,
`maxConnectionIdle`, `Server.shutdown()`), `Http2ConnectionHandler`
closes the socket as soon as the last stream's final frame has been
written to the kernel. If the client hasn't read the response yet, the
response is still in the server's send queue. The client's next frame (a
BDP PING or WINDOW_UPDATE) then reaches a socket the server has closed,
so the server's kernel answers with RST and drops the unsent bytes,
trailers included. The client fails an RPC the server completed, with
`UNAVAILABLE: Connection closed after GOAWAY. HTTP/2 error code:
NO_ERROR, debug data: max_age`. The response headers had already
arrived, so the call is committed and isn't retried. The full analysis
and a standalone reproducer are in
https://github.com/grpc/grpc-java/issues/9566#issuecomment-5930328142.

### Change

Once the second GOAWAY has been sent and no streams are active,
`NettyServerHandler` keeps the connection open and reading for up to 1
second before closing. The client normally closes first, as soon as it
has read the GOAWAY and its streams have completed, so the drain usually
ends early. This is the approach Go's HTTP/2 server took
(golang/net@cd69bc3).

- It overrides `isGracefulShutdownComplete()`, the hook
`NettyClientHandler` already uses, so the close still goes through
`Http2ConnectionHandler` and works the same with TLS.
- It never extends the grace time: if `maxConnectionAgeGrace` (or the
shutdown grace) runs out first, the connection closes then.
- The drain is cancelled when the channel becomes inactive.

### Tests

- `NettyServerHandlerTest`: 9 tests asserted that the channel closes
immediately after the second GOAWAY. They now advance the fake clock by
the drain time first. Two new tests:
`maxConnectionAge_drainStartsWhenLastStreamCloses` and
`maxConnectionAgeGrace_shorterThanDrain_clo... (continued)

39401 of 44041 relevant lines covered (89.46%)

0.89 hits per line

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

91.26
/../netty/src/main/java/io/grpc/netty/NettyServerHandler.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.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static io.grpc.internal.GrpcUtil.SERVER_KEEPALIVE_TIME_NANOS_DISABLED;
22
import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
23
import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_NANOS_DISABLED;
24
import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED;
25
import static io.grpc.netty.Utils.CONTENT_TYPE_HEADER;
26
import static io.grpc.netty.Utils.HTTP_METHOD;
27
import static io.grpc.netty.Utils.TE_HEADER;
28
import static io.grpc.netty.Utils.TE_TRAILERS;
29
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
30
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
31
import static io.netty.handler.codec.http2.DefaultHttp2LocalFlowController.DEFAULT_WINDOW_UPDATE_RATIO;
32
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.AUTHORITY;
33

34
import com.google.common.annotations.VisibleForTesting;
35
import com.google.common.base.Preconditions;
36
import com.google.common.base.Strings;
37
import com.google.common.base.Ticker;
38
import io.grpc.Attributes;
39
import io.grpc.ChannelLogger;
40
import io.grpc.ChannelLogger.ChannelLogLevel;
41
import io.grpc.InternalChannelz;
42
import io.grpc.InternalMetadata;
43
import io.grpc.InternalStatus;
44
import io.grpc.Metadata;
45
import io.grpc.MetricRecorder;
46
import io.grpc.ServerStreamTracer;
47
import io.grpc.Status;
48
import io.grpc.internal.GrpcUtil;
49
import io.grpc.internal.KeepAliveEnforcer;
50
import io.grpc.internal.KeepAliveManager;
51
import io.grpc.internal.LogExceptionRunnable;
52
import io.grpc.internal.MaxConnectionIdleManager;
53
import io.grpc.internal.ServerTransportListener;
54
import io.grpc.internal.StatsTraceContext;
55
import io.grpc.internal.TransportTracer;
56
import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ServerHeadersDecoder;
57
import io.netty.buffer.ByteBuf;
58
import io.netty.buffer.ByteBufUtil;
59
import io.netty.buffer.Unpooled;
60
import io.netty.channel.ChannelFuture;
61
import io.netty.channel.ChannelFutureListener;
62
import io.netty.channel.ChannelHandlerContext;
63
import io.netty.channel.ChannelPromise;
64
import io.netty.handler.codec.http.HttpHeaderNames;
65
import io.netty.handler.codec.http2.DecoratingHttp2ConnectionEncoder;
66
import io.netty.handler.codec.http2.DecoratingHttp2FrameWriter;
67
import io.netty.handler.codec.http2.DefaultHttp2Connection;
68
import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
69
import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
70
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
71
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
72
import io.netty.handler.codec.http2.DefaultHttp2Headers;
73
import io.netty.handler.codec.http2.DefaultHttp2HeadersEncoder;
74
import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController;
75
import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController;
76
import io.netty.handler.codec.http2.EmptyHttp2Headers;
77
import io.netty.handler.codec.http2.Http2Connection;
78
import io.netty.handler.codec.http2.Http2ConnectionAdapter;
79
import io.netty.handler.codec.http2.Http2ConnectionDecoder;
80
import io.netty.handler.codec.http2.Http2ConnectionEncoder;
81
import io.netty.handler.codec.http2.Http2Error;
82
import io.netty.handler.codec.http2.Http2Exception;
83
import io.netty.handler.codec.http2.Http2Exception.StreamException;
84
import io.netty.handler.codec.http2.Http2FrameAdapter;
85
import io.netty.handler.codec.http2.Http2FrameLogger;
86
import io.netty.handler.codec.http2.Http2FrameReader;
87
import io.netty.handler.codec.http2.Http2FrameWriter;
88
import io.netty.handler.codec.http2.Http2Headers;
89
import io.netty.handler.codec.http2.Http2HeadersDecoder;
90
import io.netty.handler.codec.http2.Http2HeadersEncoder;
91
import io.netty.handler.codec.http2.Http2InboundFrameLogger;
92
import io.netty.handler.codec.http2.Http2LifecycleManager;
93
import io.netty.handler.codec.http2.Http2OutboundFrameLogger;
94
import io.netty.handler.codec.http2.Http2Settings;
95
import io.netty.handler.codec.http2.Http2Stream;
96
import io.netty.handler.codec.http2.Http2StreamVisitor;
97
import io.netty.handler.codec.http2.UniformStreamByteDistributor;
98
import io.netty.handler.logging.LogLevel;
99
import io.netty.util.AsciiString;
100
import io.netty.util.ReferenceCountUtil;
101
import io.perfmark.PerfMark;
102
import io.perfmark.Tag;
103
import io.perfmark.TaskCloseable;
104
import java.text.MessageFormat;
105
import java.util.List;
106
import java.util.Set;
107
import java.util.concurrent.Future;
108
import java.util.concurrent.ScheduledFuture;
109
import java.util.concurrent.TimeUnit;
110
import java.util.logging.Level;
111
import java.util.logging.Logger;
112
import javax.annotation.CheckForNull;
113
import javax.annotation.Nullable;
114

115
/**
116
 * Server-side Netty handler for GRPC processing. All event handlers are executed entirely within
117
 * the context of the Netty Channel thread.
118
 */
119
class NettyServerHandler extends AbstractNettyHandler {
120
  private static final Logger logger = Logger.getLogger(NettyServerHandler.class.getName());
1 ✔
121
  private static final long KEEPALIVE_PING = 0xDEADL;
122
  @VisibleForTesting
123
  static final long GRACEFUL_SHUTDOWN_PING = 0x97ACEF001L;
124
  private static final long GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(10);
1 ✔
125
  /**
126
   * How long to keep the connection open and reading after the second GOAWAY, once no streams are
127
   * active. Closing a socket while the peer is still sending (WINDOW_UPDATE, PING) makes the kernel
128
   * answer with RST, which discards response bytes that have not been transmitted yet (#9566).
129
   * The peer normally closes first, as soon as it has read the GOAWAY and its streams completed.
130
   */
131
  @VisibleForTesting
132
  static final long GRACEFUL_SHUTDOWN_DRAIN_NANOS = TimeUnit.SECONDS.toNanos(1);
1 ✔
133
  /** Temporary workaround for #8674. Fine to delete after v1.45 release, and maybe earlier. */
134
  private static final boolean DISABLE_CONNECTION_HEADER_CHECK = Boolean.parseBoolean(
1 ✔
135
      System.getProperty("io.grpc.netty.disableConnectionHeaderCheck", "false"));
1 ✔
136

137
  private final Http2Connection.PropertyKey streamKey;
138
  private final ServerTransportListener transportListener;
139
  private final int maxMessageSize;
140
  private final TcpMetrics tcpMetrics;
141
  private final long keepAliveTimeInNanos;
142
  private final long keepAliveTimeoutInNanos;
143
  private final long maxConnectionAgeInNanos;
144
  private final long maxConnectionAgeGraceInNanos;
145
  private final RstStreamCounter rstStreamCounter;
146
  private final List<? extends ServerStreamTracer.Factory> streamTracerFactories;
147
  private final TransportTracer transportTracer;
148
  private final KeepAliveEnforcer keepAliveEnforcer;
149
  private final Attributes eagAttributes;
150
  /** Incomplete attributes produced by negotiator. */
151
  private Attributes negotiationAttributes;
152
  private InternalChannelz.Security securityInfo;
153
  /** Completed attributes produced by transportReady. */
154
  private Attributes attributes;
155
  private Throwable connectionError;
156
  private boolean teWarningLogged;
157
  private WriteQueue serverWriteQueue;
158
  private AsciiString lastKnownAuthority;
159
  @CheckForNull
160
  private KeepAliveManager keepAliveManager;
161
  @CheckForNull
162
  private MaxConnectionIdleManager maxConnectionIdleManager;
163
  @CheckForNull
164
  private ScheduledFuture<?> maxConnectionAgeMonitor;
165
  @CheckForNull
166
  private GracefulShutdown gracefulShutdown;
167

168
  static NettyServerHandler newHandler(
169
      ServerTransportListener transportListener,
170
      ChannelPromise channelUnused,
171
      List<? extends ServerStreamTracer.Factory> streamTracerFactories,
172
      TransportTracer transportTracer,
173
      int maxStreams,
174
      boolean autoFlowControl,
175
      int flowControlWindow,
176
      Set<AsciiString> neverIndexedMetadataKeys,
177
      int maxHeaderListSize,
178
      int softLimitHeaderListSize,
179
      int maxMessageSize,
180
      long keepAliveTimeInNanos,
181
      long keepAliveTimeoutInNanos,
182
      long maxConnectionIdleInNanos,
183
      long maxConnectionAgeInNanos,
184
      long maxConnectionAgeGraceInNanos,
185
      boolean permitKeepAliveWithoutCalls,
186
      long permitKeepAliveTimeInNanos,
187
      int maxRstCount,
188
      long maxRstPeriodNanos,
189
      Attributes eagAttributes,
190
      MetricRecorder metricRecorder) {
191
    Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive: %s",
1 ✔
192
        maxHeaderListSize);
193
    Http2FrameLogger frameLogger = new Http2FrameLogger(LogLevel.DEBUG, NettyServerHandler.class);
1 ✔
194
    Http2HeadersDecoder headersDecoder = new GrpcHttp2ServerHeadersDecoder(maxHeaderListSize);
1 ✔
195
    Http2FrameReader frameReader = new Http2InboundFrameLogger(
1 ✔
196
        new DefaultHttp2FrameReader(headersDecoder), frameLogger);
197
    Http2HeadersEncoder encoder = new DefaultHttp2HeadersEncoder(
1 ✔
198
        NettyClientHandler.sensitivityDetector(neverIndexedMetadataKeys),
1 ✔
199
        false,
200
        64,
201
        Integer.MAX_VALUE);
202
    Http2FrameWriter frameWriter =
1 ✔
203
        new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(encoder), frameLogger);
204
    return newHandler(
1 ✔
205
        channelUnused,
206
        frameReader,
207
        frameWriter,
208
        transportListener,
209
        streamTracerFactories,
210
        transportTracer,
211
        maxStreams,
212
        autoFlowControl,
213
        flowControlWindow,
214
        maxHeaderListSize,
215
        softLimitHeaderListSize,
216
        maxMessageSize,
217
        keepAliveTimeInNanos,
218
        keepAliveTimeoutInNanos,
219
        maxConnectionIdleInNanos,
220
        maxConnectionAgeInNanos,
221
        maxConnectionAgeGraceInNanos,
222
        permitKeepAliveWithoutCalls,
223
        permitKeepAliveTimeInNanos,
224
        maxRstCount,
225
        maxRstPeriodNanos,
226
        eagAttributes,
227
        Ticker.systemTicker(),
1 ✔
228
        metricRecorder);
229
  }
230

231
  static NettyServerHandler newHandler(
232
      ChannelPromise channelUnused,
233
      Http2FrameReader frameReader,
234
      Http2FrameWriter frameWriter,
235
      ServerTransportListener transportListener,
236
      List<? extends ServerStreamTracer.Factory> streamTracerFactories,
237
      TransportTracer transportTracer,
238
      int maxStreams,
239
      boolean autoFlowControl,
240
      int flowControlWindow,
241
      int maxHeaderListSize,
242
      int softLimitHeaderListSize,
243
      int maxMessageSize,
244
      long keepAliveTimeInNanos,
245
      long keepAliveTimeoutInNanos,
246
      long maxConnectionIdleInNanos,
247
      long maxConnectionAgeInNanos,
248
      long maxConnectionAgeGraceInNanos,
249
      boolean permitKeepAliveWithoutCalls,
250
      long permitKeepAliveTimeInNanos,
251
      int maxRstCount,
252
      long maxRstPeriodNanos,
253
      Attributes eagAttributes,
254
      Ticker ticker,
255
      MetricRecorder metricRecorder) {
256
    Preconditions.checkArgument(maxStreams > 0, "maxStreams must be positive: %s", maxStreams);
1 ✔
257
    Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive: %s",
1 ✔
258
        flowControlWindow);
259
    Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive: %s",
1 ✔
260
        maxHeaderListSize);
261
    Preconditions.checkArgument(
1 ✔
262
        softLimitHeaderListSize > 0, "softLimitHeaderListSize must be positive: %s",
263
        softLimitHeaderListSize);
264
    Preconditions.checkArgument(maxMessageSize > 0, "maxMessageSize must be positive: %s",
1 ✔
265
        maxMessageSize);
266

267
    final Http2Connection connection = new DefaultHttp2Connection(true);
1 ✔
268
    connection.remote().maxActiveStreams(maxStreams);
1 ✔
269
    UniformStreamByteDistributor dist = new UniformStreamByteDistributor(connection);
1 ✔
270
    dist.minAllocationChunk(MIN_ALLOCATED_CHUNK); // Increased for benchmarks performance.
1 ✔
271
    DefaultHttp2RemoteFlowController controller =
1 ✔
272
        new DefaultHttp2RemoteFlowController(connection, dist);
273
    connection.remote().flowController(controller);
1 ✔
274
    final KeepAliveEnforcer keepAliveEnforcer = new KeepAliveEnforcer(
1 ✔
275
        permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos, TimeUnit.NANOSECONDS);
276

277
    if (ticker == null) {
1 ✔
278
      ticker = Ticker.systemTicker();
×
279
    }
280

281
    RstStreamCounter rstStreamCounter
1 ✔
282
        = new RstStreamCounter(maxRstCount, maxRstPeriodNanos, ticker);
283
    // Create the local flow controller configured to auto-refill the connection window.
284
    connection.local().flowController(
1 ✔
285
        new DefaultHttp2LocalFlowController(connection, DEFAULT_WINDOW_UPDATE_RATIO, true));
286
    frameWriter = new WriteMonitoringFrameWriter(frameWriter, keepAliveEnforcer);
1 ✔
287
    Http2ConnectionEncoder encoder =
1 ✔
288
        new DefaultHttp2ConnectionEncoder(connection, frameWriter);
289
    encoder = new Http2ControlFrameLimitEncoder(encoder, 10000);
1 ✔
290
    encoder = new Http2RstCounterEncoder(encoder, rstStreamCounter);
1 ✔
291
    Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder,
1 ✔
292
        frameReader);
293

294
    Http2Settings settings = new Http2Settings();
1 ✔
295
    settings.initialWindowSize(flowControlWindow);
1 ✔
296
    settings.maxConcurrentStreams(maxStreams);
1 ✔
297
    settings.maxHeaderListSize(maxHeaderListSize);
1 ✔
298

299
    return new NettyServerHandler(
1 ✔
300
        channelUnused,
301
        connection,
302
        transportListener,
303
        streamTracerFactories,
304
        transportTracer,
305
        decoder, encoder, settings,
306
        maxMessageSize,
307
        maxHeaderListSize,
308
        softLimitHeaderListSize,
309
        keepAliveTimeInNanos,
310
        keepAliveTimeoutInNanos,
311
        maxConnectionIdleInNanos,
312
        maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
313
        keepAliveEnforcer,
314
        autoFlowControl,
315
        rstStreamCounter,
316
        eagAttributes, ticker,
317
        metricRecorder);
318
  }
319

320
  private NettyServerHandler(
321
      ChannelPromise channelUnused,
322
      final Http2Connection connection,
323
      ServerTransportListener transportListener,
324
      List<? extends ServerStreamTracer.Factory> streamTracerFactories,
325
      TransportTracer transportTracer,
326
      Http2ConnectionDecoder decoder,
327
      Http2ConnectionEncoder encoder,
328
      Http2Settings settings,
329
      int maxMessageSize,
330
      int maxHeaderListSize,
331
      int softLimitHeaderListSize,
332
      long keepAliveTimeInNanos,
333
      long keepAliveTimeoutInNanos,
334
      long maxConnectionIdleInNanos,
335
      long maxConnectionAgeInNanos,
336
      long maxConnectionAgeGraceInNanos,
337
      final KeepAliveEnforcer keepAliveEnforcer,
338
      boolean autoFlowControl,
339
      RstStreamCounter rstStreamCounter,
340
      Attributes eagAttributes,
341
      Ticker ticker,
342
      MetricRecorder metricRecorder) {
343
    super(
1 ✔
344
        channelUnused,
345
        decoder,
346
        encoder,
347
        settings,
348
        new ServerChannelLogger(),
349
        autoFlowControl,
350
        null,
351
        ticker,
352
        maxHeaderListSize,
353
        softLimitHeaderListSize);
354

355
    final MaxConnectionIdleManager maxConnectionIdleManager;
356
    if (maxConnectionIdleInNanos == MAX_CONNECTION_IDLE_NANOS_DISABLED) {
1 ✔
357
      maxConnectionIdleManager = null;
1 ✔
358
    } else {
359
      maxConnectionIdleManager = new MaxConnectionIdleManager(maxConnectionIdleInNanos);
1 ✔
360
    }
361

362
    connection.addListener(new Http2ConnectionAdapter() {
1 ✔
363
      @Override
364
      public void onStreamActive(Http2Stream stream) {
365
        if (connection.numActiveStreams() == 1) {
1 ✔
366
          keepAliveEnforcer.onTransportActive();
1 ✔
367
          if (maxConnectionIdleManager != null) {
1 ✔
368
            maxConnectionIdleManager.onTransportActive();
1 ✔
369
          }
370
        }
371
      }
1 ✔
372

373
      @Override
374
      public void onStreamClosed(Http2Stream stream) {
375
        if (connection.numActiveStreams() == 0) {
1 ✔
376
          keepAliveEnforcer.onTransportIdle();
1 ✔
377
          if (maxConnectionIdleManager != null) {
1 ✔
378
            maxConnectionIdleManager.onTransportIdle();
1 ✔
379
          }
380
          if (gracefulShutdown != null) {
1 ✔
381
            gracefulShutdown.drainIfIdle();
1 ✔
382
          }
383
        }
384
      }
1 ✔
385
    });
386

387
    checkArgument(maxMessageSize >= 0, "maxMessageSize must be non-negative: %s", maxMessageSize);
1 ✔
388
    this.maxMessageSize = maxMessageSize;
1 ✔
389
    this.tcpMetrics = new TcpMetrics(metricRecorder);
1 ✔
390
    this.keepAliveTimeInNanos = keepAliveTimeInNanos;
1 ✔
391
    this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
1 ✔
392
    this.maxConnectionIdleManager = maxConnectionIdleManager;
1 ✔
393
    this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
1 ✔
394
    this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
1 ✔
395
    this.keepAliveEnforcer = checkNotNull(keepAliveEnforcer, "keepAliveEnforcer");
1 ✔
396
    this.rstStreamCounter = rstStreamCounter;
1 ✔
397
    this.eagAttributes = checkNotNull(eagAttributes, "eagAttributes");
1 ✔
398

399
    streamKey = encoder.connection().newKey();
1 ✔
400
    this.transportListener = checkNotNull(transportListener, "transportListener");
1 ✔
401
    this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
1 ✔
402
    this.transportTracer = checkNotNull(transportTracer, "transportTracer");
1 ✔
403
    // Set the frame listener on the decoder.
404
    decoder().frameListener(new FrameListener());
1 ✔
405
  }
1 ✔
406

407
  @Nullable
408
  Throwable connectionError() {
409
    return connectionError;
1 ✔
410
  }
411

412
  @Override
413
  public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
414
    serverWriteQueue = new WriteQueue(ctx.channel());
1 ✔
415

416
    // init max connection age monitor
417
    if (maxConnectionAgeInNanos != MAX_CONNECTION_AGE_NANOS_DISABLED) {
1 ✔
418
      maxConnectionAgeMonitor = ctx.executor().schedule(
1 ✔
419
          new LogExceptionRunnable(new Runnable() {
1 ✔
420
            @Override
421
            public void run() {
422
              if (gracefulShutdown == null) {
1 ✔
423
                gracefulShutdown = new GracefulShutdown("max_age", maxConnectionAgeGraceInNanos);
1 ✔
424
                gracefulShutdown.start(ctx);
1 ✔
425
                ctx.flush();
1 ✔
426
              }
427
            }
1 ✔
428
          }),
429
          maxConnectionAgeInNanos,
430
          TimeUnit.NANOSECONDS);
431
    }
432

433
    if (maxConnectionIdleManager != null) {
1 ✔
434
      maxConnectionIdleManager.start(new Runnable() {
1 ✔
435
        @Override
436
        public void run() {
437
          if (gracefulShutdown == null) {
1 ✔
438
            gracefulShutdown = new GracefulShutdown("max_idle", null);
1 ✔
439
            gracefulShutdown.start(ctx);
1 ✔
440
            ctx.flush();
1 ✔
441
          }
442
        }
1 ✔
443
      }, ctx.executor());
1 ✔
444
    }
445

446
    if (keepAliveTimeInNanos != SERVER_KEEPALIVE_TIME_NANOS_DISABLED) {
1 ✔
447
      keepAliveManager = new KeepAliveManager(new KeepAlivePinger(ctx), ctx.executor(),
1 ✔
448
          keepAliveTimeInNanos, keepAliveTimeoutInNanos, true /* keepAliveDuringTransportIdle */);
449
      keepAliveManager.onTransportStarted();
1 ✔
450
    }
451

452
    assert encoder().connection().equals(decoder().connection());
1 ✔
453
    transportTracer.setFlowControlWindowReader(new Utils.FlowControlReader(encoder().connection()));
1 ✔
454

455
    super.handlerAdded(ctx);
1 ✔
456
  }
1 ✔
457

458
  private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers)
459
      throws Http2Exception {
460
    try {
461
      // Connection-specific header fields makes a request malformed. Ideally this would be handled
462
      // by Netty. RFC 7540 section 8.1.2.2
463
      if (!DISABLE_CONNECTION_HEADER_CHECK && headers.contains(CONNECTION)) {
1 ✔
464
        resetStream(ctx, streamId, Http2Error.PROTOCOL_ERROR.code(), ctx.newPromise());
×
465
        return;
×
466
      }
467

468
      if (headers.authority() == null) {
1 ✔
469
        List<CharSequence> hosts = headers.getAll(HOST);
1 ✔
470
        if (hosts.size() > 1) {
1 ✔
471
          // RFC 7230 section 5.4
472
          respondWithHttpError(ctx, streamId, 400, Status.Code.INTERNAL,
1 ✔
473
              "Multiple host headers");
474
          return;
1 ✔
475
        }
476
        if (!hosts.isEmpty()) {
1 ✔
477
          headers.add(AUTHORITY.value(), hosts.get(0));
1 ✔
478
        }
479
      }
480
      headers.remove(HOST);
1 ✔
481

482
      // Remove the leading slash of the path and get the fully qualified method name
483
      CharSequence path = headers.path();
1 ✔
484

485
      if (path == null) {
1 ✔
486
        respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED,
1 ✔
487
            "Expected path but is missing");
488
        return;
1 ✔
489
      }
490

491
      if (path.charAt(0) != '/') {
1 ✔
492
        respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED,
1 ✔
493
            String.format("Expected path to start with /: %s", path));
1 ✔
494
        return;
1 ✔
495
      }
496

497
      String method = path.subSequence(1, path.length()).toString();
1 ✔
498

499
      // Verify that the Content-Type is correct in the request.
500
      CharSequence contentType = headers.get(CONTENT_TYPE_HEADER);
1 ✔
501
      if (contentType == null) {
1 ✔
502
        respondWithHttpError(
1 ✔
503
            ctx, streamId, 415, Status.Code.INTERNAL, "Content-Type is missing from the request");
504
        return;
1 ✔
505
      }
506
      String contentTypeString = contentType.toString();
1 ✔
507
      if (!GrpcUtil.isGrpcContentType(contentTypeString)) {
1 ✔
508
        respondWithHttpError(ctx, streamId, 415, Status.Code.INTERNAL,
1 ✔
509
            String.format("Content-Type '%s' is not supported", contentTypeString));
1 ✔
510
        return;
1 ✔
511
      }
512

513
      if (!HTTP_METHOD.contentEquals(headers.method())) {
1 ✔
514
        Http2Headers extraHeaders = new DefaultHttp2Headers();
1 ✔
515
        extraHeaders.add(HttpHeaderNames.ALLOW, HTTP_METHOD);
1 ✔
516
        respondWithHttpError(ctx, streamId, 405, Status.Code.INTERNAL,
1 ✔
517
            String.format("Method '%s' is not supported", headers.method()), extraHeaders);
1 ✔
518
        return;
1 ✔
519
      }
520

521
      int h2HeadersSize = Utils.getH2HeadersSize(headers);
1 ✔
522
      if (Utils.shouldRejectOnMetadataSizeSoftLimitExceeded(
1 ✔
523
              h2HeadersSize, softLimitHeaderListSize, maxHeaderListSize)) {
524
        respondWithHttpError(ctx, streamId, 431, Status.Code.RESOURCE_EXHAUSTED, String.format(
×
525
                "Client Headers of size %d exceeded Metadata size soft limit: %d",
526
                h2HeadersSize,
×
527
                softLimitHeaderListSize));
×
528
        return;
×
529
      }
530

531
      if (!teWarningLogged && !TE_TRAILERS.contentEquals(headers.get(TE_HEADER))) {
1 ✔
532
        logger.warning(String.format("Expected header TE: %s, but %s is received. This means "
1 ✔
533
                + "some intermediate proxy may not support trailers",
534
            TE_TRAILERS, headers.get(TE_HEADER)));
1 ✔
535
        teWarningLogged = true;
1 ✔
536
      }
537

538
      // The Http2Stream object was put by AbstractHttp2ConnectionHandler before calling this
539
      // method.
540
      Http2Stream http2Stream = requireHttp2Stream(streamId);
1 ✔
541

542
      Metadata metadata = Utils.convertHeaders(headers);
1 ✔
543
      StatsTraceContext statsTraceCtx =
1 ✔
544
          StatsTraceContext.newServerContext(streamTracerFactories, method, metadata);
1 ✔
545

546
      NettyServerStream.TransportState state = new NettyServerStream.TransportState(
1 ✔
547
          this,
548
          ctx.channel().eventLoop(),
1 ✔
549
          http2Stream,
550
          maxMessageSize,
551
          statsTraceCtx,
552
          transportTracer,
553
          method);
554

555
      try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onHeadersRead")) {
1 ✔
556
        PerfMark.attachTag(state.tag());
1 ✔
557
        String authority = getOrUpdateAuthority((AsciiString) headers.authority());
1 ✔
558
        NettyServerStream stream = new NettyServerStream(
1 ✔
559
            ctx.channel(),
1 ✔
560
            state,
561
            attributes,
562
            authority,
563
            statsTraceCtx);
564
        transportListener.streamCreated(stream, method, metadata);
1 ✔
565
        state.onStreamAllocated();
1 ✔
566
        http2Stream.setProperty(streamKey, state);
1 ✔
567
      }
568
    } catch (Exception e) {
×
569
      logger.log(Level.WARNING, "Exception in onHeadersRead()", e);
×
570
      // Throw an exception that will get handled by onStreamError.
571
      throw newStreamException(streamId, e);
×
572
    }
1 ✔
573
  }
1 ✔
574

575
  private String getOrUpdateAuthority(AsciiString authority) {
576
    if (authority == null) {
1 ✔
577
      return null;
1 ✔
578
    } else if (!authority.equals(lastKnownAuthority)) {
1 ✔
579
      lastKnownAuthority = authority;
1 ✔
580
    }
581

582
    // AsciiString.toString() is internally cached, so subsequent calls will not
583
    // result in recomputing the String representation of lastKnownAuthority.
584
    return lastKnownAuthority.toString();
1 ✔
585
  }
586

587
  private void onDataRead(int streamId, ByteBuf data, int padding, boolean endOfStream)
588
      throws Http2Exception {
589
    flowControlPing().onDataRead(data.readableBytes(), padding);
1 ✔
590
    try {
591
      NettyServerStream.TransportState stream = serverStream(requireHttp2Stream(streamId));
1 ✔
592
      if (stream == null) {
1 ✔
593
        return;
1 ✔
594
      }
595
      try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onDataRead")) {
1 ✔
596
        PerfMark.attachTag(stream.tag());
1 ✔
597
        stream.inboundDataReceived(data, endOfStream);
1 ✔
598
      }
599
    } catch (Throwable e) {
×
600
      logger.log(Level.WARNING, "Exception in onDataRead()", e);
×
601
      // Throw an exception that will get handled by onStreamError.
602
      throw newStreamException(streamId, e);
×
603
    }
1 ✔
604
  }
1 ✔
605

606
  private void onRstStreamRead(int streamId, long errorCode) throws Http2Exception {
607
    Http2Exception tooManyRstStream = rstStreamCounter.countRstStream();
1 ✔
608
    if (tooManyRstStream != null) {
1 ✔
609
      throw tooManyRstStream;
1 ✔
610
    }
611

612
    try {
613
      NettyServerStream.TransportState stream = serverStream(connection().stream(streamId));
1 ✔
614
      if (stream != null) {
1 ✔
615
        try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onRstStreamRead")) {
1 ✔
616
          PerfMark.attachTag(stream.tag());
1 ✔
617
          stream.transportReportStatus(
1 ✔
618
              Status.CANCELLED.withDescription("RST_STREAM received for code " + errorCode));
1 ✔
619
        }
620
      }
621
    } catch (Throwable e) {
×
622
      logger.log(Level.WARNING, "Exception in onRstStreamRead()", e);
×
623
      // Throw an exception that will get handled by onStreamError.
624
      throw newStreamException(streamId, e);
×
625
    }
1 ✔
626
  }
1 ✔
627

628
  @Override
629
  protected void onConnectionError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
630
      Http2Exception http2Ex) {
631
    logger.log(Level.FINE, "Connection Error", cause);
1 ✔
632
    connectionError = cause;
1 ✔
633
    super.onConnectionError(ctx, outbound, cause, http2Ex);
1 ✔
634
  }
1 ✔
635

636
  @Override
637
  protected void onStreamError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
638
      StreamException http2Ex) {
639
    NettyServerStream.TransportState serverStream = serverStream(
1 ✔
640
        connection().stream(Http2Exception.streamId(http2Ex)));
1 ✔
641
    Level level = Level.WARNING;
1 ✔
642
    if (serverStream == null && http2Ex.error() == Http2Error.STREAM_CLOSED) {
1 ✔
643
      level = Level.FINE;
1 ✔
644
    }
645
    logger.log(level, "Stream Error", cause);
1 ✔
646
    Tag tag = serverStream != null ? serverStream.tag() : PerfMark.createTag();
1 ✔
647
    try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onStreamError")) {
1 ✔
648
      PerfMark.attachTag(tag);
1 ✔
649
      if (serverStream != null) {
1 ✔
650
        serverStream.transportReportStatus(Utils.statusFromThrowable(cause));
1 ✔
651
      }
652
      // TODO(ejona): Abort the stream by sending headers to help the client with debugging.
653
      // Delegate to the base class to send a RST_STREAM.
654
      super.onStreamError(ctx, outbound, cause, http2Ex);
1 ✔
655
    }
656
  }
1 ✔
657

658
  @Override
659
  public void handleProtocolNegotiationCompleted(
660
      Attributes attrs, InternalChannelz.Security securityInfo) {
661
    negotiationAttributes = attrs;
1 ✔
662
    this.securityInfo = securityInfo;
1 ✔
663
    super.handleProtocolNegotiationCompleted(attrs, securityInfo);
1 ✔
664
    NettyClientHandler.writeBufferingAndRemove(ctx().channel());
1 ✔
665
  }
1 ✔
666

667
  @Override
668
  public Attributes getEagAttributes() {
669
    return eagAttributes;
1 ✔
670
  }
671

672
  InternalChannelz.Security getSecurityInfo() {
673
    return securityInfo;
1 ✔
674
  }
675

676
  @VisibleForTesting
677
  KeepAliveManager getKeepAliveManagerForTest() {
678
    return keepAliveManager;
1 ✔
679
  }
680

681
  @VisibleForTesting
682
  void setKeepAliveManagerForTest(KeepAliveManager keepAliveManager) {
683
    this.keepAliveManager = keepAliveManager;
1 ✔
684
  }
1 ✔
685

686
  /**
687
   * Handler for the Channel shutting down.
688
   */
689
  @Override
690
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
691
    tcpMetrics.channelActive(ctx.channel());
1 ✔
692
    super.channelActive(ctx);
1 ✔
693
  }
1 ✔
694

695
  @Override
696
  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
697
    try {
698
      tcpMetrics.channelInactive(ctx.channel());
1 ✔
699
      if (keepAliveManager != null) {
1 ✔
700
        keepAliveManager.onTransportTermination();
1 ✔
701
      }
702
      if (maxConnectionIdleManager != null) {
1 ✔
703
        maxConnectionIdleManager.onTransportTermination();
1 ✔
704
      }
705
      if (maxConnectionAgeMonitor != null) {
1 ✔
706
        maxConnectionAgeMonitor.cancel(false);
1 ✔
707
      }
708
      if (gracefulShutdown != null) {
1 ✔
709
        gracefulShutdown.cancelDrain();
1 ✔
710
      }
711
      final Status status =
1 ✔
712
          Status.UNAVAILABLE.withDescription("connection terminated for unknown reason");
1 ✔
713
      // Any streams that are still active must be closed
714
      connection().forEachActiveStream(new Http2StreamVisitor() {
1 ✔
715
        @Override
716
        public boolean visit(Http2Stream stream) throws Http2Exception {
717
          NettyServerStream.TransportState serverStream = serverStream(stream);
1 ✔
718
          if (serverStream != null) {
1 ✔
719
            serverStream.transportReportStatus(status);
1 ✔
720
          }
721
          return true;
1 ✔
722
        }
723
      });
724
    } finally {
725
      super.channelInactive(ctx);
1 ✔
726
    }
727
  }
1 ✔
728

729
  WriteQueue getWriteQueue() {
730
    return serverWriteQueue;
1 ✔
731
  }
732

733
  /** Handler for commands sent from the stream. */
734
  @Override
735
  public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
736
      throws Exception {
737
    if (msg instanceof SendGrpcFrameCommand) {
1 ✔
738
      sendGrpcFrame(ctx, (SendGrpcFrameCommand) msg, promise);
1 ✔
739
    } else if (msg instanceof SendResponseHeadersCommand) {
1 ✔
740
      sendResponseHeaders(ctx, (SendResponseHeadersCommand) msg, promise);
1 ✔
741
    } else if (msg instanceof CancelServerStreamCommand) {
1 ✔
742
      cancelStream(ctx, (CancelServerStreamCommand) msg, promise);
1 ✔
743
    } else if (msg instanceof GracefulServerCloseCommand) {
1 ✔
744
      gracefulClose(ctx, (GracefulServerCloseCommand) msg, promise);
1 ✔
745
    } else if (msg instanceof ForcefulCloseCommand) {
1 ✔
746
      forcefulClose(ctx, (ForcefulCloseCommand) msg, promise);
1 ✔
747
    } else {
748
      AssertionError e =
×
749
          new AssertionError("Write called for unexpected type: " + msg.getClass().getName());
×
750
      ReferenceCountUtil.release(msg);
×
751
      promise.setFailure(e);
×
752
      throw e;
×
753
    }
754
  }
1 ✔
755

756
  @Override
757
  public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
758
    gracefulClose(ctx, new GracefulServerCloseCommand("app_requested"), promise);
1 ✔
759
    ctx.flush();
1 ✔
760
  }
1 ✔
761

762
  @Override
763
  protected boolean isGracefulShutdownComplete() {
764
    return super.isGracefulShutdownComplete()
1 ✔
765
        && (gracefulShutdown == null || gracefulShutdown.drainComplete());
1 ✔
766
  }
767

768
  /**
769
   * Returns the given processed bytes back to inbound flow control.
770
   */
771
  void returnProcessedBytes(Http2Stream http2Stream, int bytes) {
772
    try {
773
      decoder().flowController().consumeBytes(http2Stream, bytes);
1 ✔
774
    } catch (Http2Exception e) {
×
775
      throw new RuntimeException(e);
×
776
    }
1 ✔
777
  }
1 ✔
778

779
  private void closeStreamWhenDone(ChannelPromise promise, Http2Stream stream) {
780
    promise.addListener(
1 ✔
781
        new ChannelFutureListener() {
1 ✔
782
          @Override
783
          public void operationComplete(ChannelFuture future) {
784
            serverStream(stream).complete();
1 ✔
785
          }
1 ✔
786
        });
787
  }
1 ✔
788

789
  private static void streamGone(int streamId, ChannelPromise promise) {
790
    promise.setFailure(
1 ✔
791
        new IllegalStateException(
792
            "attempting to write to stream " + streamId + " that no longer exists") {
1 ✔
793
          @Override
794
          public synchronized Throwable fillInStackTrace() {
795
            return this;
1 ✔
796
          }
797
        });
798
  }
1 ✔
799

800
  /** Sends the given gRPC frame to the client. */
801
  private void sendGrpcFrame(
802
      ChannelHandlerContext ctx, SendGrpcFrameCommand cmd, ChannelPromise promise)
803
      throws Http2Exception {
804
    try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.sendGrpcFrame")) {
1 ✔
805
      PerfMark.attachTag(cmd.stream().tag());
1 ✔
806
      PerfMark.linkIn(cmd.getLink());
1 ✔
807
      int streamId = cmd.stream().id();
1 ✔
808
      Http2Stream stream = connection().stream(streamId);
1 ✔
809
      if (stream == null) {
1 ✔
810
        cmd.release();
1 ✔
811
        streamGone(streamId, promise);
1 ✔
812
        return;
1 ✔
813
      }
814
      if (cmd.endStream()) {
1 ✔
815
        closeStreamWhenDone(promise, stream);
×
816
      }
817
      // Call the base class to write the HTTP/2 DATA frame.
818
      encoder().writeData(ctx, streamId, cmd.content(), 0, cmd.endStream(), promise);
1 ✔
819
    }
820
  }
1 ✔
821

822
  /**
823
   * Sends the response headers to the client.
824
   */
825
  private void sendResponseHeaders(ChannelHandlerContext ctx, SendResponseHeadersCommand cmd,
826
      ChannelPromise promise) throws Http2Exception {
827
    try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.sendResponseHeaders")) {
1 ✔
828
      PerfMark.attachTag(cmd.stream().tag());
1 ✔
829
      PerfMark.linkIn(cmd.getLink());
1 ✔
830
      int streamId = cmd.stream().id();
1 ✔
831
      Http2Stream stream = connection().stream(streamId);
1 ✔
832
      if (stream == null) {
1 ✔
833
        streamGone(streamId, promise);
1 ✔
834
        return;
1 ✔
835
      }
836
      if (cmd.endOfStream()) {
1 ✔
837
        closeStreamWhenDone(promise, stream);
1 ✔
838
      }
839
      encoder().writeHeaders(ctx, streamId, cmd.headers(), 0, cmd.endOfStream(), promise);
1 ✔
840
    }
841
  }
1 ✔
842

843
  private void cancelStream(ChannelHandlerContext ctx, CancelServerStreamCommand cmd,
844
      ChannelPromise promise) {
845
    try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.cancelStream")) {
1 ✔
846
      PerfMark.attachTag(cmd.stream().tag());
1 ✔
847
      PerfMark.linkIn(cmd.getLink());
1 ✔
848
      // Notify the listener if we haven't already.
849
      cmd.stream().transportReportStatus(cmd.reason());
1 ✔
850

851
      // Now we need to decide how we're going to notify the peer that this stream is closed.
852
      // If possible, it's nice to inform the peer _why_ this stream was cancelled by sending
853
      // a structured headers frame.
854
      if (shouldCloseStreamWithHeaders(cmd, connection())) {
1 ✔
855
        Metadata md = new Metadata();
1 ✔
856
        md.put(InternalStatus.CODE_KEY, cmd.reason());
1 ✔
857
        if (cmd.reason().getDescription() != null) {
1 ✔
858
          md.put(InternalStatus.MESSAGE_KEY, cmd.reason().getDescription());
1 ✔
859
        }
860
        Http2Headers headers = Utils.convertServerHeaders(md);
1 ✔
861
        encoder().writeHeaders(
1 ✔
862
            ctx, cmd.stream().id(), headers, /* padding = */ 0, /* endStream = */ true, promise);
1 ✔
863
      } else {
1 ✔
864
        // Terminate the stream.
865
        encoder().writeRstStream(ctx, cmd.stream().id(), Http2Error.CANCEL.code(), promise);
1 ✔
866
      }
867
    }
868
  }
1 ✔
869

870
  // Determine whether a CancelServerStreamCommand should try to close the stream with a
871
  // HEADERS or a RST_STREAM frame. The caller has some influence over this (they can
872
  // configure cmd.wantsHeaders()). The state of the stream also has an influence: we
873
  // only try to send HEADERS if the stream exists and hasn't already sent any headers.
874
  private static boolean shouldCloseStreamWithHeaders(
875
          CancelServerStreamCommand cmd, Http2Connection conn) {
876
    if (!cmd.wantsHeaders()) {
1 ✔
877
      return false;
1 ✔
878
    }
879
    Http2Stream stream = conn.stream(cmd.stream().id());
1 ✔
880
    return stream != null && !stream.isHeadersSent();
1 ✔
881
  }
882

883
  private void gracefulClose(final ChannelHandlerContext ctx, final GracefulServerCloseCommand msg,
884
      ChannelPromise promise) throws Exception {
885
    // Ideally we'd adjust a pre-existing graceful shutdown's grace period to at least what is
886
    // requested here. But that's an edge case and seems bug-prone.
887
    if (gracefulShutdown == null) {
1 ✔
888
      Long graceTimeInNanos = null;
1 ✔
889
      if (msg.getGraceTimeUnit() != null) {
1 ✔
890
        graceTimeInNanos = msg.getGraceTimeUnit().toNanos(msg.getGraceTime());
1 ✔
891
      }
892
      gracefulShutdown = new GracefulShutdown(msg.getGoAwayDebugString(), graceTimeInNanos);
1 ✔
893
      gracefulShutdown.start(ctx);
1 ✔
894
    }
895
    promise.setSuccess();
1 ✔
896
  }
1 ✔
897

898
  private void forcefulClose(final ChannelHandlerContext ctx, final ForcefulCloseCommand msg,
899
      ChannelPromise promise) throws Exception {
900
    super.close(ctx, promise);
1 ✔
901
    connection().forEachActiveStream(new Http2StreamVisitor() {
1 ✔
902
      @Override
903
      public boolean visit(Http2Stream stream) throws Http2Exception {
904
        NettyServerStream.TransportState serverStream = serverStream(stream);
1 ✔
905
        if (serverStream != null) {
1 ✔
906
          try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.forcefulClose")) {
1 ✔
907
            PerfMark.attachTag(serverStream.tag());
1 ✔
908
            PerfMark.linkIn(msg.getLink());
1 ✔
909
            serverStream.transportReportStatus(msg.getStatus());
1 ✔
910
            resetStream(ctx, stream.id(), Http2Error.CANCEL.code(), ctx.newPromise());
1 ✔
911
          }
912
        }
913
        stream.close();
1 ✔
914
        return true;
1 ✔
915
      }
916
    });
917
  }
1 ✔
918

919
  private void respondWithHttpError(
920
      ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg) {
921
    respondWithHttpError(ctx, streamId, code, statusCode, msg, EmptyHttp2Headers.INSTANCE);
1 ✔
922
  }
1 ✔
923

924
  private void respondWithHttpError(
925
      ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg,
926
      Http2Headers extraHeaders) {
927
    Metadata metadata = new Metadata();
1 ✔
928
    metadata.put(InternalStatus.CODE_KEY, statusCode.toStatus());
1 ✔
929
    metadata.put(InternalStatus.MESSAGE_KEY, msg);
1 ✔
930
    byte[][] serialized = InternalMetadata.serialize(metadata);
1 ✔
931

932
    Http2Headers headers = new DefaultHttp2Headers(true, serialized.length / 2)
1 ✔
933
        .status("" + code)
1 ✔
934
        .set(CONTENT_TYPE_HEADER, "text/plain; charset=utf-8");
1 ✔
935
    for (int i = 0; i < serialized.length; i += 2) {
1 ✔
936
      headers.add(new AsciiString(serialized[i], false), new AsciiString(serialized[i + 1], false));
1 ✔
937
    }
938
    headers.add(extraHeaders);
1 ✔
939
    encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
1 ✔
940
    ByteBuf msgBuf = ByteBufUtil.writeUtf8(ctx.alloc(), msg);
1 ✔
941
    encoder().writeData(ctx, streamId, msgBuf, 0, true, ctx.newPromise());
1 ✔
942
  }
1 ✔
943

944
  private Http2Stream requireHttp2Stream(int streamId) {
945
    Http2Stream stream = connection().stream(streamId);
1 ✔
946
    if (stream == null) {
1 ✔
947
      // This should never happen.
948
      throw new AssertionError("Stream does not exist: " + streamId);
×
949
    }
950
    return stream;
1 ✔
951
  }
952

953
  /**
954
   * Returns the server stream associated to the given HTTP/2 stream object.
955
   */
956
  private NettyServerStream.TransportState serverStream(Http2Stream stream) {
957
    return stream == null ? null : (NettyServerStream.TransportState) stream.getProperty(streamKey);
1 ✔
958
  }
959

960
  private Http2Exception newStreamException(int streamId, Throwable cause) {
961
    return Http2Exception.streamError(
×
962
        streamId, Http2Error.INTERNAL_ERROR, cause, Strings.nullToEmpty(cause.getMessage()));
×
963
  }
964

965
  private class FrameListener extends Http2FrameAdapter {
1 ✔
966
    private boolean firstSettings = true;
1 ✔
967

968
    @Override
969
    public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
970
      if (firstSettings) {
1 ✔
971
        firstSettings = false;
1 ✔
972
        // Delay transportReady until we see the client's HTTP handshake, for coverage with
973
        // handshakeTimeout
974
        attributes = transportListener.transportReady(negotiationAttributes);
1 ✔
975
      }
976
    }
1 ✔
977

978
    @Override
979
    public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
980
        boolean endOfStream) throws Http2Exception {
981
      if (keepAliveManager != null) {
1 ✔
982
        keepAliveManager.onDataReceived();
1 ✔
983
      }
984
      NettyServerHandler.this.onDataRead(streamId, data, padding, endOfStream);
1 ✔
985
      return padding;
1 ✔
986
    }
987

988
    @Override
989
    public void onHeadersRead(ChannelHandlerContext ctx,
990
        int streamId,
991
        Http2Headers headers,
992
        int streamDependency,
993
        short weight,
994
        boolean exclusive,
995
        int padding,
996
        boolean endStream) throws Http2Exception {
997
      if (keepAliveManager != null) {
1 ✔
998
        keepAliveManager.onDataReceived();
1 ✔
999
      }
1000
      NettyServerHandler.this.onHeadersRead(ctx, streamId, headers);
1 ✔
1001
      if (endStream) {
1 ✔
1002
        NettyServerHandler.this.onDataRead(streamId, Unpooled.EMPTY_BUFFER, 0, endStream);
×
1003
      }
1004
    }
1 ✔
1005

1006
    @Override
1007
    public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
1008
        throws Http2Exception {
1009
      if (keepAliveManager != null) {
1 ✔
1010
        keepAliveManager.onDataReceived();
1 ✔
1011
      }
1012
      NettyServerHandler.this.onRstStreamRead(streamId, errorCode);
1 ✔
1013
    }
1 ✔
1014

1015
    @Override
1016
    public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
1017
      if (keepAliveManager != null) {
1 ✔
1018
        keepAliveManager.onDataReceived();
1 ✔
1019
      }
1020
      if (!keepAliveEnforcer.pingAcceptable()) {
1 ✔
1021
        ByteBuf debugData = ByteBufUtil.writeAscii(ctx.alloc(), "too_many_pings");
1 ✔
1022
        goAway(ctx, connection().remote().lastStreamCreated(), Http2Error.ENHANCE_YOUR_CALM.code(),
1 ✔
1023
            debugData, ctx.newPromise());
1 ✔
1024
        Status status = Status.RESOURCE_EXHAUSTED.withDescription("Too many pings from client");
1 ✔
1025
        try {
1026
          forcefulClose(ctx, new ForcefulCloseCommand(status), ctx.newPromise());
1 ✔
1027
        } catch (Exception ex) {
×
1028
          onError(ctx, /* outbound= */ true, ex);
×
1029
        }
1 ✔
1030
      }
1031
    }
1 ✔
1032

1033
    @Override
1034
    public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
1035
      if (keepAliveManager != null) {
1 ✔
1036
        keepAliveManager.onDataReceived();
1 ✔
1037
      }
1038
      if (data == flowControlPing().payload()) {
1 ✔
1039
        flowControlPing().updateWindow();
1 ✔
1040
        logger.log(Level.FINE, "Window: {0}",
1 ✔
1041
            decoder().flowController().initialWindowSize(connection().connectionStream()));
1 ✔
1042
      } else if (data == GRACEFUL_SHUTDOWN_PING) {
1 ✔
1043
        if (gracefulShutdown == null) {
1 ✔
1044
          // this should never happen
1045
          logger.warning("Received GRACEFUL_SHUTDOWN_PING Ack but gracefulShutdown is null");
×
1046
        } else {
1047
          gracefulShutdown.secondGoAwayAndClose(ctx);
1 ✔
1048
        }
1049
      } else if (data != KEEPALIVE_PING) {
1 ✔
1050
        logger.warning("Received unexpected ping ack. No ping outstanding");
×
1051
      }
1052
    }
1 ✔
1053
  }
1054

1055
  private final class KeepAlivePinger implements KeepAliveManager.KeepAlivePinger {
1056
    final ChannelHandlerContext ctx;
1057

1058
    KeepAlivePinger(ChannelHandlerContext ctx) {
1 ✔
1059
      this.ctx = ctx;
1 ✔
1060
    }
1 ✔
1061

1062
    @Override
1063
    public void ping() {
1064
      ChannelFuture pingFuture = encoder().writePing(
1 ✔
1065
          ctx, false /* isAck */, KEEPALIVE_PING, ctx.newPromise());
1 ✔
1066
      ctx.flush();
1 ✔
1067
      pingFuture.addListener(new ChannelFutureListener() {
1 ✔
1068
        @Override
1069
        public void operationComplete(ChannelFuture future) throws Exception {
1070
          if (future.isSuccess()) {
1 ✔
1071
            transportTracer.reportKeepAliveSent();
1 ✔
1072
          }
1073
        }
1 ✔
1074
      });
1075
    }
1 ✔
1076

1077
    @Override
1078
    public void onPingTimeout() {
1079
      try {
1080
        forcefulClose(
1 ✔
1081
            ctx,
1082
            new ForcefulCloseCommand(Status.UNAVAILABLE
1083
                .withDescription("Keepalive failed. The connection is likely gone")),
1 ✔
1084
            ctx.newPromise());
1 ✔
1085
      } catch (Exception ex) {
×
1086
        try {
1087
          exceptionCaught(ctx, ex);
×
1088
        } catch (Exception ex2) {
×
1089
          logger.log(Level.WARNING, "Exception while propagating exception", ex2);
×
1090
          logger.log(Level.WARNING, "Original failure", ex);
×
1091
        }
×
1092
      }
1 ✔
1093
    }
1 ✔
1094
  }
1095

1096
  private final class GracefulShutdown {
1097
    String goAwayMessage;
1098

1099
    /**
1100
     * The grace time between starting graceful shutdown and closing the netty channel,
1101
     * {@code null} is unspecified.
1102
     */
1103
    @CheckForNull
1104
    Long graceTimeInNanos;
1105

1106
    /**
1107
     * True if ping is Acked or ping is timeout.
1108
     */
1109
    boolean pingAckedOrTimeout;
1110

1111
    Future<?> pingFuture;
1112

1113
    ChannelHandlerContext ctx;
1114

1115
    /** Scheduled once the second GOAWAY has been sent and no streams are active. */
1116
    Future<?> drainFuture;
1117

1118
    boolean drained;
1119

1120
    GracefulShutdown(String goAwayMessage,
1121
        @Nullable Long graceTimeInNanos) {
1 ✔
1122
      this.goAwayMessage = goAwayMessage;
1 ✔
1123
      this.graceTimeInNanos = graceTimeInNanos;
1 ✔
1124
    }
1 ✔
1125

1126
    /**
1127
     * Sends out first GOAWAY and ping, and schedules second GOAWAY and close.
1128
     */
1129
    void start(final ChannelHandlerContext ctx) {
1130
      this.ctx = ctx;
1 ✔
1131
      goAway(
1 ✔
1132
          ctx,
1133
          Integer.MAX_VALUE,
1134
          Http2Error.NO_ERROR.code(),
1 ✔
1135
          ByteBufUtil.writeAscii(ctx.alloc(), goAwayMessage),
1 ✔
1136
          ctx.newPromise());
1 ✔
1137

1138
      pingFuture = ctx.executor().schedule(
1 ✔
1139
          new Runnable() {
1 ✔
1140
            @Override
1141
            public void run() {
1142
              secondGoAwayAndClose(ctx);
1 ✔
1143
            }
1 ✔
1144
          },
1145
          GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS,
1 ✔
1146
          TimeUnit.NANOSECONDS);
1147

1148
      encoder().writePing(ctx, false /* isAck */, GRACEFUL_SHUTDOWN_PING, ctx.newPromise());
1 ✔
1149
    }
1 ✔
1150

1151
    void secondGoAwayAndClose(ChannelHandlerContext ctx) {
1152
      if (pingAckedOrTimeout) {
1 ✔
1153
        return;
×
1154
      }
1155
      pingAckedOrTimeout = true;
1 ✔
1156

1157
      checkNotNull(pingFuture, "pingFuture");
1 ✔
1158
      pingFuture.cancel(false);
1 ✔
1159

1160
      // send the second GOAWAY with last stream id
1161
      goAway(
1 ✔
1162
          ctx,
1163
          connection().remote().lastStreamCreated(),
1 ✔
1164
          Http2Error.NO_ERROR.code(),
1 ✔
1165
          ByteBufUtil.writeAscii(ctx.alloc(), goAwayMessage),
1 ✔
1166
          ctx.newPromise());
1 ✔
1167

1168
      // gracefully shutdown with specified grace time
1169
      long savedGracefulShutdownTimeMillis = gracefulShutdownTimeoutMillis();
1 ✔
1170
      long overriddenGraceTime = graceTimeOverrideMillis(savedGracefulShutdownTimeMillis);
1 ✔
1171
      try {
1172
        gracefulShutdownTimeoutMillis(overriddenGraceTime);
1 ✔
1173
        // Closes once isGracefulShutdownComplete(), i.e. after the drain, or when the grace time
1174
        // runs out.
1175
        NettyServerHandler.super.close(ctx, ctx.newPromise());
1 ✔
1176
      } catch (Exception e) {
×
1177
        onError(ctx, /* outbound= */ true, e);
×
1178
      } finally {
1179
        gracefulShutdownTimeoutMillis(savedGracefulShutdownTimeMillis);
1 ✔
1180
      }
1181
      drainIfIdle();
1 ✔
1182
    }
1 ✔
1183

1184
    boolean drainComplete() {
1185
      return !pingAckedOrTimeout || drained;
1 ✔
1186
    }
1187

1188
    void drainIfIdle() {
1189
      if (!pingAckedOrTimeout || drainFuture != null || connection().numActiveStreams() != 0) {
1 ✔
1190
        return;
1 ✔
1191
      }
1192
      drainFuture = ctx.executor().schedule(
1 ✔
1193
          new Runnable() {
1 ✔
1194
            @Override
1195
            public void run() {
1196
              drained = true;
1 ✔
1197
              try {
1198
                // No streams are active, so this closes as soon as pending writes are flushed.
1199
                NettyServerHandler.super.close(ctx, ctx.newPromise());
1 ✔
1200
              } catch (Exception e) {
×
1201
                onError(ctx, /* outbound= */ true, e);
×
1202
              }
1 ✔
1203
            }
1 ✔
1204
          },
1205
          GRACEFUL_SHUTDOWN_DRAIN_NANOS,
1206
          TimeUnit.NANOSECONDS);
1207
    }
1 ✔
1208

1209
    void cancelDrain() {
1210
      if (drainFuture != null) {
1 ✔
1211
        drainFuture.cancel(false);
1 ✔
1212
      }
1213
    }
1 ✔
1214

1215
    private long graceTimeOverrideMillis(long originalMillis) {
1216
      if (graceTimeInNanos == null) {
1 ✔
1217
        return originalMillis;
1 ✔
1218
      }
1219
      if (graceTimeInNanos == MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE) {
1 ✔
1220
        // netty treats -1 as "no timeout"
1221
        return -1L;
1 ✔
1222
      }
1223
      return TimeUnit.NANOSECONDS.toMillis(graceTimeInNanos);
1 ✔
1224
    }
1225
  }
1226

1227
  // Use a frame writer so that we know when frames are through flow control and actually being
1228
  // written.
1229
  private static class WriteMonitoringFrameWriter extends DecoratingHttp2FrameWriter {
1230
    private final KeepAliveEnforcer keepAliveEnforcer;
1231

1232
    public WriteMonitoringFrameWriter(Http2FrameWriter delegate,
1233
        KeepAliveEnforcer keepAliveEnforcer) {
1234
      super(delegate);
1 ✔
1235
      this.keepAliveEnforcer = keepAliveEnforcer;
1 ✔
1236
    }
1 ✔
1237

1238
    @Override
1239
    public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
1240
        int padding, boolean endStream, ChannelPromise promise) {
1241
      keepAliveEnforcer.resetCounters();
1 ✔
1242
      return super.writeData(ctx, streamId, data, padding, endStream, promise);
1 ✔
1243
    }
1244

1245
    @Override
1246
    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
1247
        int padding, boolean endStream, ChannelPromise promise) {
1248
      keepAliveEnforcer.resetCounters();
1 ✔
1249
      return super.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
1 ✔
1250
    }
1251

1252
    @Override
1253
    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
1254
        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream,
1255
        ChannelPromise promise) {
1256
      keepAliveEnforcer.resetCounters();
×
1257
      return super.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive,
×
1258
          padding, endStream, promise);
1259
    }
1260
  }
1261

1262
  private static final class Http2RstCounterEncoder extends DecoratingHttp2ConnectionEncoder {
1263
    private final RstStreamCounter rstStreamCounter;
1264
    private Http2LifecycleManager lifecycleManager;
1265

1266
    Http2RstCounterEncoder(Http2ConnectionEncoder encoder, RstStreamCounter rstStreamCounter) {
1267
      super(encoder);
1 ✔
1268
      this.rstStreamCounter = rstStreamCounter;
1 ✔
1269
    }
1 ✔
1270

1271
    @Override
1272
    public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
1273
      this.lifecycleManager = lifecycleManager;
1 ✔
1274
      super.lifecycleManager(lifecycleManager);
1 ✔
1275
    }
1 ✔
1276

1277
    @Override
1278
    public ChannelFuture writeRstStream(
1279
        ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise) {
1280
      ChannelFuture future = super.writeRstStream(ctx, streamId, errorCode, promise);
1 ✔
1281
      // We want to count "induced" RST_STREAM, where the server sent a reset because of a malformed
1282
      // frame.
1283
      boolean normalRst
1 ✔
1284
          = errorCode == Http2Error.NO_ERROR.code() || errorCode == Http2Error.CANCEL.code();
1 ✔
1285
      if (!normalRst) {
1 ✔
1286
        Http2Exception tooManyRstStream = rstStreamCounter.countRstStream();
1 ✔
1287
        if (tooManyRstStream != null) {
1 ✔
1288
          lifecycleManager.onError(ctx, true, tooManyRstStream);
1 ✔
1289
          ctx.close();
1 ✔
1290
        }
1291
      }
1292
      return future;
1 ✔
1293
    }
1294
  }
1295

1296
  private static final class RstStreamCounter {
1297
    private final int maxRstCount;
1298
    private final long maxRstPeriodNanos;
1299
    private final Ticker ticker;
1300
    private int rstCount;
1301
    private long lastRstNanoTime;
1302

1303
    RstStreamCounter(int maxRstCount, long maxRstPeriodNanos, Ticker ticker) {
1 ✔
1304
      checkArgument(maxRstCount >= 0, "maxRstCount must be non-negative: %s", maxRstCount);
1 ✔
1305
      this.maxRstCount = maxRstCount;
1 ✔
1306
      this.maxRstPeriodNanos = maxRstPeriodNanos;
1 ✔
1307
      this.ticker = checkNotNull(ticker, "ticker");
1 ✔
1308
      this.lastRstNanoTime = ticker.read();
1 ✔
1309
    }
1 ✔
1310

1311
    /** Returns non-{@code null} when the connection should be killed by the caller. */
1312
    private Http2Exception countRstStream() {
1313
      if (maxRstCount == 0) {
1 ✔
1314
        return null;
1 ✔
1315
      }
1316
      long now = ticker.read();
1 ✔
1317
      if (now - lastRstNanoTime > maxRstPeriodNanos) {
1 ✔
1318
        lastRstNanoTime = now;
1 ✔
1319
        rstCount = 1;
1 ✔
1320
      } else {
1321
        rstCount++;
1 ✔
1322
        if (rstCount > maxRstCount) {
1 ✔
1323
          return new Http2Exception(Http2Error.ENHANCE_YOUR_CALM, "too_many_rststreams") {
1 ✔
1324
            @SuppressWarnings("UnsynchronizedOverridesSynchronized") // No memory accesses
1325
            @Override
1326
            public Throwable fillInStackTrace() {
1327
              // Avoid the CPU cycles, since the resets may be a CPU consumption attack
1328
              return this;
1 ✔
1329
            }
1330
          };
1331
        }
1332
      }
1333
      return null;
1 ✔
1334
    }
1335
  }
1336

1337
  private static class ServerChannelLogger extends ChannelLogger {
1338
    private static final Logger log = Logger.getLogger(ChannelLogger.class.getName());
1 ✔
1339

1340
    @Override
1341
    public void log(ChannelLogLevel level, String message) {
1342
      log.log(toJavaLogLevel(level), message);
1 ✔
1343
    }
1 ✔
1344

1345
    @Override
1346
    public void log(ChannelLogLevel level, String messageFormat, Object... args) {
1347
      log(level, MessageFormat.format(messageFormat, args));
1 ✔
1348
    }
1 ✔
1349
  }
1350

1351
  private static Level toJavaLogLevel(ChannelLogLevel level) {
1352
    switch (level) {
1 ✔
1353
      case ERROR:
1354
        return Level.FINE;
×
1355
      case WARNING:
1356
        return Level.FINER;
×
1357
      default:
1358
        return Level.FINEST;
1 ✔
1359
    }
1360
  }
1361
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc