• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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 com.google.common.annotations.VisibleForTesting;
20 import com.google.common.base.Preconditions;
21 import java.util.concurrent.TimeUnit;
22 import javax.annotation.CheckReturnValue;
23 
24 /** Monitors the client's PING usage to make sure the rate is permitted. */
25 class KeepAliveEnforcer {
26   @VisibleForTesting
27   static final int MAX_PING_STRIKES = 2;
28   @VisibleForTesting
29   static final long IMPLICIT_PERMIT_TIME_NANOS = TimeUnit.HOURS.toNanos(2);
30 
31   private final boolean permitWithoutCalls;
32   private final long minTimeNanos;
33   private final Ticker ticker;
34   private final long epoch;
35 
36   private long lastValidPingTime;
37   private boolean hasOutstandingCalls;
38   private int pingStrikes;
39 
KeepAliveEnforcer(boolean permitWithoutCalls, long minTime, TimeUnit unit)40   public KeepAliveEnforcer(boolean permitWithoutCalls, long minTime, TimeUnit unit) {
41     this(permitWithoutCalls, minTime, unit, SystemTicker.INSTANCE);
42   }
43 
44   @VisibleForTesting
KeepAliveEnforcer(boolean permitWithoutCalls, long minTime, TimeUnit unit, Ticker ticker)45   KeepAliveEnforcer(boolean permitWithoutCalls, long minTime, TimeUnit unit, Ticker ticker) {
46     Preconditions.checkArgument(minTime >= 0, "minTime must be non-negative");
47 
48     this.permitWithoutCalls = permitWithoutCalls;
49     this.minTimeNanos = Math.min(unit.toNanos(minTime), IMPLICIT_PERMIT_TIME_NANOS);
50     this.ticker = ticker;
51     this.epoch = ticker.nanoTime();
52     lastValidPingTime = epoch;
53   }
54 
55   /** Returns {@code false} when client is misbehaving and should be disconnected. */
56   @CheckReturnValue
pingAcceptable()57   public boolean pingAcceptable() {
58     long now = ticker.nanoTime();
59     boolean valid;
60     if (!hasOutstandingCalls && !permitWithoutCalls) {
61       valid = compareNanos(lastValidPingTime + IMPLICIT_PERMIT_TIME_NANOS, now) <= 0;
62     } else {
63       valid = compareNanos(lastValidPingTime + minTimeNanos, now) <= 0;
64     }
65     if (!valid) {
66       pingStrikes++;
67       return !(pingStrikes > MAX_PING_STRIKES);
68     } else {
69       lastValidPingTime = now;
70       return true;
71     }
72   }
73 
74   /**
75    * Reset any counters because PINGs are allowed in response to something sent. Typically called
76    * when sending HEADERS and DATA frames.
77    */
resetCounters()78   public void resetCounters() {
79     lastValidPingTime = epoch;
80     pingStrikes = 0;
81   }
82 
83   /** There are outstanding RPCs on the transport. */
onTransportActive()84   public void onTransportActive() {
85     hasOutstandingCalls = true;
86   }
87 
88   /** There are no outstanding RPCs on the transport. */
onTransportIdle()89   public void onTransportIdle() {
90     hasOutstandingCalls = false;
91   }
92 
93   /**
94    * Positive when time1 is greater; negative when time2 is greater; 0 when equal. It is important
95    * to use something like this instead of directly comparing nano times. See {@link
96    * System#nanoTime}.
97    */
compareNanos(long time1, long time2)98   private static long compareNanos(long time1, long time2) {
99     // Possibility of overflow/underflow is on purpose and necessary for correctness
100     return time1 - time2;
101   }
102 
103   @VisibleForTesting
104   interface Ticker {
nanoTime()105     long nanoTime();
106   }
107 
108   @VisibleForTesting
109   static class SystemTicker implements Ticker {
110     public static final SystemTicker INSTANCE = new SystemTicker();
111 
112     @Override
nanoTime()113     public long nanoTime() {
114       return System.nanoTime();
115     }
116   }
117 }
118