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

grpc / grpc-java / #19717

06 Mar 2025 06:32PM UTC coverage: 88.525% (+0.03%) from 88.494%
#19717

push

github

web-flow
xds: Support filter state retention

This PR adds support filter state retention in Java. The mechanism
will be similar to the one described in [A83]
(https://github.com/grpc/proposal/blob/master/A83-xds-gcp-authn-filter.md#filter-call-credentials-cache)
for C-core, and will serve the same purpose. However, the
implementation details are very different due to the different nature
of xDS HTTP filter support in C-core and Java.

### Filter instance lifecycle
#### xDS gRPC clients
New filter instances are created per combination of:
1. `XdsNameResolver` instance,
2. Filter name+typeUrl as configured in 
   HttpConnectionManager (HCM) http_filters.

Existing client-side filter instances are shutdown:
- A single a filter instance is shutdown when an LDS update contains
  HCM that is missing filter configuration for name+typeUrl
  combination of this instance.
- All filter instances when watched LDS resource is missing from an
  LDS update.
- All filter instances name resolver shutdown.

#### xDS-enabled gRPC servers
New filter instances are created per combination of:
1. Server instance,
2. FilterChain name,
3. Filter name+typeUrl as configured in FilterChain's HCM.http_filters

Filter instances of Default Filter Chain is tracked separately per:
1. Server instance,
2. Filter name+typeUrl in default_filter_chain's HCM.http_filters.

Existing server-side filter instances are shutdown:
- A single a filter instance is shutdown when an LDS update contains
  FilterChain with HCM.http_filters that is missing configuration for
  filter name+typeUrl.
- All filter instances associated with the FilterChain when an LDS
  update no longer contains FilterChain's name.
- All filter instances when watched LDS resource is missing from an
  LDS update.
- All filter instances on server shutdown.

### Related
- Part 1: #11883

34577 of 39059 relevant lines covered (88.53%)

0.89 hits per line

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

95.96
/../xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java
1
/*
2
 * Copyright 2021 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.xds;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static io.grpc.xds.XdsAttributes.ATTR_DRAIN_GRACE_NANOS;
21
import static io.grpc.xds.XdsAttributes.ATTR_FILTER_CHAIN_SELECTOR_MANAGER;
22
import static io.grpc.xds.XdsServerWrapper.ATTR_SERVER_ROUTING_CONFIG;
23
import static io.grpc.xds.internal.security.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER;
24

25
import com.google.common.annotations.VisibleForTesting;
26
import com.google.common.base.MoreObjects;
27
import com.google.common.base.Strings;
28
import com.google.common.collect.Iterables;
29
import com.google.protobuf.UInt32Value;
30
import io.grpc.Attributes;
31
import io.grpc.internal.ObjectPool;
32
import io.grpc.netty.GrpcHttp2ConnectionHandler;
33
import io.grpc.netty.InternalGracefulServerCloseCommand;
34
import io.grpc.netty.InternalProtocolNegotiationEvent;
35
import io.grpc.netty.InternalProtocolNegotiator;
36
import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator;
37
import io.grpc.netty.ProtocolNegotiationEvent;
38
import io.grpc.xds.EnvoyServerProtoData.CidrRange;
39
import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType;
40
import io.grpc.xds.EnvoyServerProtoData.FilterChain;
41
import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch;
42
import io.grpc.xds.XdsServerWrapper.ServerRoutingConfig;
43
import io.grpc.xds.internal.Matchers.CidrMatcher;
44
import io.grpc.xds.internal.security.SslContextProviderSupplier;
45
import io.netty.channel.Channel;
46
import io.netty.channel.ChannelFuture;
47
import io.netty.channel.ChannelFutureListener;
48
import io.netty.channel.ChannelHandler;
49
import io.netty.channel.ChannelHandlerContext;
50
import io.netty.channel.ChannelInboundHandlerAdapter;
51
import io.netty.util.AsciiString;
52
import java.net.Inet6Address;
53
import java.net.InetAddress;
54
import java.net.InetSocketAddress;
55
import java.util.ArrayList;
56
import java.util.Collection;
57
import java.util.Collections;
58
import java.util.List;
59
import java.util.Map;
60
import java.util.concurrent.Executor;
61
import java.util.concurrent.TimeUnit;
62
import java.util.concurrent.atomic.AtomicReference;
63
import java.util.logging.Level;
64
import java.util.logging.Logger;
65
import javax.annotation.Nullable;
66

67

68
/**
69
 * Handles L4 filter chain match for the connection based on the xds configuration.
70
 * */
71
@SuppressWarnings("FutureReturnValueIgnored") // Netty doesn't follow this pattern
72
final class FilterChainMatchingProtocolNegotiators {
73
  private static final Logger log = Logger.getLogger(
1✔
74
          FilterChainMatchingProtocolNegotiators.class.getName());
1✔
75

76
  private static final AsciiString SCHEME = AsciiString.of("http");
1✔
77

78
  private FilterChainMatchingProtocolNegotiators() {
79
  }
80

81
  @VisibleForTesting
82
  static final class FilterChainMatchingHandler extends ChannelInboundHandlerAdapter {
83

84
    private final GrpcHttp2ConnectionHandler grpcHandler;
85
    private final FilterChainSelectorManager filterChainSelectorManager;
86
    private final ProtocolNegotiator delegate;
87

88
    FilterChainMatchingHandler(
89
            GrpcHttp2ConnectionHandler grpcHandler,
90
            FilterChainSelectorManager filterChainSelectorManager,
91
            ProtocolNegotiator delegate) {
1✔
92
      this.grpcHandler = checkNotNull(grpcHandler, "grpcHandler");
1✔
93
      this.filterChainSelectorManager =
1✔
94
          checkNotNull(filterChainSelectorManager, "filterChainSelectorManager");
1✔
95
      this.delegate = checkNotNull(delegate, "delegate");
1✔
96
    }
1✔
97

98
    @Override
99
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
100
      if (!(evt instanceof ProtocolNegotiationEvent)) {
1✔
101
        super.userEventTriggered(ctx, evt);
×
102
        return;
×
103
      }
104
      long drainGraceTime = 0;
1✔
105
      TimeUnit drainGraceTimeUnit = null;
1✔
106
      Long drainGraceNanosObj = grpcHandler.getEagAttributes().get(ATTR_DRAIN_GRACE_NANOS);
1✔
107
      if (drainGraceNanosObj != null) {
1✔
108
        drainGraceTime = drainGraceNanosObj;
1✔
109
        drainGraceTimeUnit = TimeUnit.NANOSECONDS;
1✔
110
      }
111
      FilterChainSelectorManager.Closer closer = new FilterChainSelectorManager.Closer(
1✔
112
          new GracefullyShutdownChannelRunnable(ctx.channel(), drainGraceTime, drainGraceTimeUnit));
1✔
113
      FilterChainSelector selector = filterChainSelectorManager.register(closer);
1✔
114
      ctx.channel().closeFuture().addListener(
1✔
115
          new FilterChainSelectorManagerDeregister(filterChainSelectorManager, closer));
116
      checkNotNull(selector, "selector");
1✔
117
      SelectedConfig config = selector.select(
1✔
118
          (InetSocketAddress) ctx.channel().localAddress(),
1✔
119
          (InetSocketAddress) ctx.channel().remoteAddress());
1✔
120
      if (config == null) {
1✔
121
        log.log(Level.WARNING, "Connection from {0} to {1} has no matching filter chain. Closing",
1✔
122
            new Object[] {ctx.channel().remoteAddress(), ctx.channel().localAddress()});
1✔
123
        ctx.close().addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
1✔
124
        return;
1✔
125
      }
126
      ProtocolNegotiationEvent pne = (ProtocolNegotiationEvent) evt;
1✔
127
      // TODO(zivy): merge into one key and take care of this outer class visibility.
128
      Attributes attr = InternalProtocolNegotiationEvent.getAttributes(pne).toBuilder()
1✔
129
              .set(ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER, config.sslContextProviderSupplier)
1✔
130
              .set(ATTR_SERVER_ROUTING_CONFIG, config.routingConfig)
1✔
131
              .build();
1✔
132
      pne = InternalProtocolNegotiationEvent.withAttributes(pne, attr);
1✔
133
      ctx.pipeline().replace(this, null, delegate.newHandler(grpcHandler));
1✔
134
      ctx.fireUserEventTriggered(pne);
1✔
135
    }
1✔
136

137
    static final class FilterChainSelector {
138
      public static final FilterChainSelector NO_FILTER_CHAIN = new FilterChainSelector(
1✔
139
              Collections.<FilterChain, AtomicReference<ServerRoutingConfig>>emptyMap(),
1✔
140
          null, new AtomicReference<ServerRoutingConfig>());
141
      private final Map<FilterChain, AtomicReference<ServerRoutingConfig>> routingConfigs;
142
      @Nullable
143
      private final SslContextProviderSupplier defaultSslContextProviderSupplier;
144
      private final AtomicReference<ServerRoutingConfig> defaultRoutingConfig;
145

146
      FilterChainSelector(Map<FilterChain, AtomicReference<ServerRoutingConfig>> routingConfigs,
147
                          @Nullable SslContextProviderSupplier defaultSslContextProviderSupplier,
148
                          AtomicReference<ServerRoutingConfig> defaultRoutingConfig) {
1✔
149
        this.routingConfigs = checkNotNull(routingConfigs, "routingConfigs");
1✔
150
        this.defaultSslContextProviderSupplier = defaultSslContextProviderSupplier;
1✔
151
        this.defaultRoutingConfig = checkNotNull(defaultRoutingConfig, "defaultRoutingConfig");
1✔
152
      }
1✔
153

154
      FilterChainSelector(Map<FilterChain, AtomicReference<ServerRoutingConfig>> routingConfigs) {
155
        this(routingConfigs, null, new AtomicReference<>());
1✔
156
      }
1✔
157

158
      @VisibleForTesting
159
      Map<FilterChain, AtomicReference<ServerRoutingConfig>> getRoutingConfigs() {
160
        return routingConfigs;
1✔
161
      }
162

163
      @VisibleForTesting
164
      AtomicReference<ServerRoutingConfig> getDefaultRoutingConfig() {
165
        return defaultRoutingConfig;
1✔
166
      }
167

168
      @VisibleForTesting
169
      SslContextProviderSupplier getDefaultSslContextProviderSupplier() {
170
        return defaultSslContextProviderSupplier;
1✔
171
      }
172

173
      /**
174
       * Throws IllegalStateException when no exact one match, and we should close the connection.
175
       */
176
      SelectedConfig select(InetSocketAddress localAddr, InetSocketAddress remoteAddr) {
177
        Collection<FilterChain> filterChains = routingConfigs.keySet();
1✔
178
        filterChains = filterOnDestinationPort(filterChains);
1✔
179
        filterChains = filterOnIpAddress(filterChains, localAddr.getAddress(), true);
1✔
180
        filterChains = filterOnServerNames(filterChains);
1✔
181
        filterChains = filterOnTransportProtocol(filterChains);
1✔
182
        filterChains = filterOnApplicationProtocols(filterChains);
1✔
183
        filterChains =
1✔
184
                filterOnSourceType(filterChains, remoteAddr.getAddress(), localAddr.getAddress());
1✔
185
        filterChains = filterOnIpAddress(filterChains, remoteAddr.getAddress(), false);
1✔
186
        filterChains = filterOnSourcePort(filterChains, remoteAddr.getPort());
1✔
187

188
        if (filterChains.size() > 1) {
1✔
189
          throw new IllegalStateException("Found more than one matching filter chains. This should "
1✔
190
              + "not be possible as ClientXdsClient validated the chains for uniqueness.");
191
        }
192
        if (filterChains.size() == 1) {
1✔
193
          FilterChain selected = Iterables.getOnlyElement(filterChains);
1✔
194
          return new SelectedConfig(
1✔
195
                  routingConfigs.get(selected), selected.sslContextProviderSupplier());
1✔
196
        }
197
        if (defaultRoutingConfig.get() != null) {
1✔
198
          return new SelectedConfig(defaultRoutingConfig, defaultSslContextProviderSupplier);
1✔
199
        }
200
        return null;
1✔
201
      }
202

203
      // reject if filer-chain-match has non-empty application_protocols
204
      private static Collection<FilterChain> filterOnApplicationProtocols(
205
              Collection<FilterChain> filterChains) {
206
        ArrayList<FilterChain> filtered = new ArrayList<>(filterChains.size());
1✔
207
        for (FilterChain filterChain : filterChains) {
1✔
208
          FilterChainMatch filterChainMatch = filterChain.filterChainMatch();
1✔
209

210
          if (filterChainMatch.applicationProtocols().isEmpty()) {
1✔
211
            filtered.add(filterChain);
1✔
212
          }
213
        }
1✔
214
        return filtered;
1✔
215
      }
216

217
      // reject if filer-chain-match has non-empty transport protocol other than "raw_buffer"
218
      private static Collection<FilterChain> filterOnTransportProtocol(
219
              Collection<FilterChain> filterChains) {
220
        ArrayList<FilterChain> filtered = new ArrayList<>(filterChains.size());
1✔
221
        for (FilterChain filterChain : filterChains) {
1✔
222
          FilterChainMatch filterChainMatch = filterChain.filterChainMatch();
1✔
223

224
          String transportProtocol = filterChainMatch.transportProtocol();
1✔
225
          if (Strings.isNullOrEmpty(transportProtocol) || "raw_buffer".equals(transportProtocol)) {
1✔
226
            filtered.add(filterChain);
1✔
227
          }
228
        }
1✔
229
        return filtered;
1✔
230
      }
231

232
      // reject if filer-chain-match has server_name(s)
233
      private static Collection<FilterChain> filterOnServerNames(
234
              Collection<FilterChain> filterChains) {
235
        ArrayList<FilterChain> filtered = new ArrayList<>(filterChains.size());
1✔
236
        for (FilterChain filterChain : filterChains) {
1✔
237
          FilterChainMatch filterChainMatch = filterChain.filterChainMatch();
1✔
238

239
          if (filterChainMatch.serverNames().isEmpty()) {
1✔
240
            filtered.add(filterChain);
1✔
241
          }
242
        }
1✔
243
        return filtered;
1✔
244
      }
245

246
      // destination_port present => Always fail match
247
      private static Collection<FilterChain> filterOnDestinationPort(
248
              Collection<FilterChain> filterChains) {
249
        ArrayList<FilterChain> filtered = new ArrayList<>(filterChains.size());
1✔
250
        for (FilterChain filterChain : filterChains) {
1✔
251
          FilterChainMatch filterChainMatch = filterChain.filterChainMatch();
1✔
252

253
          if (filterChainMatch.destinationPort()
1✔
254
                  == UInt32Value.getDefaultInstance().getValue()) {
1✔
255
            filtered.add(filterChain);
1✔
256
          }
257
        }
1✔
258
        return filtered;
1✔
259
      }
260

261
      private static Collection<FilterChain> filterOnSourcePort(
262
              Collection<FilterChain> filterChains, int sourcePort) {
263
        ArrayList<FilterChain> filteredOnMatch = new ArrayList<>(filterChains.size());
1✔
264
        ArrayList<FilterChain> filteredOnEmpty = new ArrayList<>(filterChains.size());
1✔
265
        for (FilterChain filterChain : filterChains) {
1✔
266
          FilterChainMatch filterChainMatch = filterChain.filterChainMatch();
1✔
267

268
          List<Integer> sourcePortsToMatch = filterChainMatch.sourcePorts();
1✔
269
          if (sourcePortsToMatch.isEmpty()) {
1✔
270
            filteredOnEmpty.add(filterChain);
1✔
271
          } else if (sourcePortsToMatch.contains(sourcePort)) {
1✔
272
            filteredOnMatch.add(filterChain);
1✔
273
          }
274
        }
1✔
275
        // match against source port is more specific than match against empty list
276
        return filteredOnMatch.isEmpty() ? filteredOnEmpty : filteredOnMatch;
1✔
277
      }
278

279
      private static Collection<FilterChain> filterOnSourceType(
280
              Collection<FilterChain> filterChains, InetAddress sourceAddress,
281
              InetAddress destAddress) {
282
        ArrayList<FilterChain> filtered = new ArrayList<>(filterChains.size());
1✔
283
        for (FilterChain filterChain : filterChains) {
1✔
284
          FilterChainMatch filterChainMatch = filterChain.filterChainMatch();
1✔
285
          ConnectionSourceType sourceType =
1✔
286
                  filterChainMatch.connectionSourceType();
1✔
287

288
          boolean matching = false;
1✔
289
          if (sourceType == ConnectionSourceType.SAME_IP_OR_LOOPBACK) {
1✔
290
            matching =
1✔
291
                    sourceAddress.isLoopbackAddress()
1✔
292
                            || sourceAddress.isAnyLocalAddress()
1✔
293
                            || sourceAddress.equals(destAddress);
1✔
294
          } else if (sourceType == ConnectionSourceType.EXTERNAL) {
1✔
295
            matching = !sourceAddress.isLoopbackAddress() && !sourceAddress.isAnyLocalAddress();
1✔
296
          } else { // ANY or null
297
            matching = true;
1✔
298
          }
299
          if (matching) {
1✔
300
            filtered.add(filterChain);
1✔
301
          }
302
        }
1✔
303
        return filtered;
1✔
304
      }
305

306
      private static int getMatchingPrefixLength(
307
              FilterChainMatch filterChainMatch, InetAddress address, boolean forDestination) {
308
        boolean isIPv6 = address instanceof Inet6Address;
1✔
309
        List<CidrRange> cidrRanges =
310
                forDestination
1✔
311
                        ? filterChainMatch.prefixRanges()
1✔
312
                        : filterChainMatch.sourcePrefixRanges();
1✔
313
        int matchingPrefixLength;
314
        if (cidrRanges.isEmpty()) { // if there is no CidrRange assume 0-length match
1✔
315
          matchingPrefixLength = 0;
1✔
316
        } else {
317
          matchingPrefixLength = -1;
1✔
318
          for (CidrRange cidrRange : cidrRanges) {
1✔
319
            InetAddress cidrAddr = cidrRange.addressPrefix();
1✔
320
            boolean cidrIsIpv6 = cidrAddr instanceof Inet6Address;
1✔
321
            if (isIPv6 == cidrIsIpv6) {
1✔
322
              int prefixLen = cidrRange.prefixLen();
1✔
323
              CidrMatcher matcher = CidrMatcher.create(cidrAddr, prefixLen);
1✔
324
              if (matcher.matches(address) && prefixLen > matchingPrefixLength) {
1✔
325
                matchingPrefixLength = prefixLen;
1✔
326
              }
327
            }
328
          }
1✔
329
        }
330
        return matchingPrefixLength;
1✔
331
      }
332

333
      // use prefix_ranges (CIDR) and get the most specific matches
334
      private static Collection<FilterChain> filterOnIpAddress(
335
              Collection<FilterChain> filterChains, InetAddress address, boolean forDestination) {
336
        // current list of top ones
337
        ArrayList<FilterChain> topOnes = new ArrayList<>(filterChains.size());
1✔
338
        int topMatchingPrefixLen = -1;
1✔
339
        for (FilterChain filterChain : filterChains) {
1✔
340
          int currentMatchingPrefixLen = getMatchingPrefixLength(
1✔
341
                  filterChain.filterChainMatch(), address, forDestination);
1✔
342

343
          if (currentMatchingPrefixLen >= 0) {
1✔
344
            if (currentMatchingPrefixLen < topMatchingPrefixLen) {
1✔
345
              continue;
1✔
346
            }
347
            if (currentMatchingPrefixLen > topMatchingPrefixLen) {
1✔
348
              topMatchingPrefixLen = currentMatchingPrefixLen;
1✔
349
              topOnes.clear();
1✔
350
            }
351
            topOnes.add(filterChain);
1✔
352
          }
353
        }
1✔
354
        return topOnes;
1✔
355
      }
356

357
      @Override
358
      public String toString() {
359
        return MoreObjects.toStringHelper(this)
×
360
            .add("routingConfigs", routingConfigs)
×
361
            .add("defaultSslContextProviderSupplier", defaultSslContextProviderSupplier)
×
362
            .add("defaultRoutingConfig", defaultRoutingConfig)
×
363
            .toString();
×
364
      }
365
    }
366
  }
367

368
  static final class FilterChainMatchingNegotiatorServerFactory
369
          implements InternalProtocolNegotiator.ServerFactory {
370
    private final InternalProtocolNegotiator.ServerFactory delegate;
371

372
    public FilterChainMatchingNegotiatorServerFactory(
373
            InternalProtocolNegotiator.ServerFactory delegate) {
1✔
374
      this.delegate = checkNotNull(delegate, "delegate");
1✔
375
    }
1✔
376

377
    @Override
378
    public ProtocolNegotiator newNegotiator(
379
            final ObjectPool<? extends Executor> offloadExecutorPool) {
380

381
      class FilterChainMatchingNegotiator implements ProtocolNegotiator {
1✔
382

383
        @Override
384
        public AsciiString scheme() {
385
          return SCHEME;
×
386
        }
387

388
        @Override
389
        public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
390
          FilterChainSelectorManager filterChainSelectorManager =
1✔
391
              grpcHandler.getEagAttributes().get(ATTR_FILTER_CHAIN_SELECTOR_MANAGER);
1✔
392
          checkNotNull(filterChainSelectorManager, "filterChainSelectorManager");
1✔
393
          return new FilterChainMatchingHandler(grpcHandler, filterChainSelectorManager,
1✔
394
                  delegate.newNegotiator(offloadExecutorPool));
1✔
395
        }
396

397
        @Override
398
        public void close() {
399
        }
1✔
400
      }
401

402
      return new FilterChainMatchingNegotiator();
1✔
403
    }
404
  }
405

406
  /**
407
   * The FilterChain level configuration.
408
   */
409
  private static final class SelectedConfig {
410
    private final AtomicReference<ServerRoutingConfig> routingConfig;
411
    @Nullable
412
    private final SslContextProviderSupplier sslContextProviderSupplier;
413

414
    private SelectedConfig(AtomicReference<ServerRoutingConfig> routingConfig,
415
                           @Nullable SslContextProviderSupplier sslContextProviderSupplier) {
1✔
416
      this.routingConfig = checkNotNull(routingConfig, "routingConfig");
1✔
417
      this.sslContextProviderSupplier = sslContextProviderSupplier;
1✔
418
    }
1✔
419
  }
420

421
  private static class FilterChainSelectorManagerDeregister implements ChannelFutureListener {
422
    private final FilterChainSelectorManager filterChainSelectorManager;
423
    private final FilterChainSelectorManager.Closer closer;
424

425
    public FilterChainSelectorManagerDeregister(
426
        FilterChainSelectorManager filterChainSelectorManager,
427
        FilterChainSelectorManager.Closer closer) {
1✔
428
      this.filterChainSelectorManager =
1✔
429
          checkNotNull(filterChainSelectorManager, "filterChainSelectorManager");
1✔
430
      this.closer = checkNotNull(closer, "closer");
1✔
431
    }
1✔
432

433
    @Override public void operationComplete(ChannelFuture future) throws Exception {
434
      filterChainSelectorManager.deregister(closer);
1✔
435
    }
1✔
436
  }
437

438
  private static class GracefullyShutdownChannelRunnable implements Runnable {
439
    private final Channel channel;
440
    private final long drainGraceTime;
441
    @Nullable
442
    private final TimeUnit drainGraceTimeUnit;
443

444
    public GracefullyShutdownChannelRunnable(
445
        Channel channel, long drainGraceTime, @Nullable TimeUnit drainGraceTimeUnit) {
1✔
446
      this.channel = checkNotNull(channel, "channel");
1✔
447
      this.drainGraceTime = drainGraceTime;
1✔
448
      this.drainGraceTimeUnit = drainGraceTimeUnit;
1✔
449
    }
1✔
450

451
    @Override public void run() {
452
      Object gracefulCloseCommand = InternalGracefulServerCloseCommand.create(
1✔
453
          "xds_drain", drainGraceTime, drainGraceTimeUnit);
454
      channel.writeAndFlush(gracefulCloseCommand)
1✔
455
          .addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
1✔
456
    }
1✔
457
  }
458
}
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