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