1 /* 2 * Copyright 2020 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.binder.internal; 18 19 import com.google.common.base.Ticker; 20 import io.grpc.Status; 21 import io.grpc.StatusException; 22 import io.grpc.internal.ClientTransport.PingCallback; 23 import java.util.concurrent.Executor; 24 import javax.annotation.Nullable; 25 import javax.annotation.concurrent.GuardedBy; 26 27 /** 28 * Tracks an ongoing ping request for a client-side binder transport. We only handle a single active 29 * ping at a time, since that's all gRPC appears to need. 30 */ 31 final class PingTracker { 32 33 interface PingSender { 34 /** 35 * Send a ping to the remote endpoint. We expect a subsequent call to {@link #onPingResponse} 36 * with the same ID (assuming the ping succeeds). 37 */ sendPing(int id)38 void sendPing(int id) throws StatusException; 39 } 40 41 private final Ticker ticker; 42 private final PingSender pingSender; 43 44 @GuardedBy("this") 45 @Nullable 46 private Ping pendingPing; 47 48 @GuardedBy("this") 49 private int nextPingId; 50 PingTracker(Ticker ticker, PingSender pingSender)51 PingTracker(Ticker ticker, PingSender pingSender) { 52 this.ticker = ticker; 53 this.pingSender = pingSender; 54 } 55 56 /** 57 * Start a ping. 58 * 59 * <p>See also {@link ClientTransport#ping}. 60 * 61 * @param callback The callback to report the ping result on. 62 * @param executor An executor to call callbacks on. 63 * <p>Note that only one ping callback will be active at a time. 64 */ startPing(PingCallback callback, Executor executor)65 synchronized void startPing(PingCallback callback, Executor executor) { 66 pendingPing = new Ping(callback, executor, nextPingId++); 67 try { 68 pingSender.sendPing(pendingPing.id); 69 } catch (StatusException se) { 70 pendingPing.fail(se.getStatus()); 71 pendingPing = null; 72 } 73 } 74 75 /** Callback when a ping response with the given ID is received. */ onPingResponse(int id)76 synchronized void onPingResponse(int id) { 77 if (pendingPing != null && pendingPing.id == id) { 78 pendingPing.success(); 79 pendingPing = null; 80 } 81 } 82 83 private final class Ping { 84 private final PingCallback callback; 85 private final Executor executor; 86 private final int id; 87 private final long startTimeNanos; 88 89 @GuardedBy("this") 90 private boolean done; 91 Ping(PingCallback callback, Executor executor, int id)92 Ping(PingCallback callback, Executor executor, int id) { 93 this.callback = callback; 94 this.executor = executor; 95 this.id = id; 96 this.startTimeNanos = ticker.read(); 97 } 98 fail(Status status)99 private synchronized void fail(Status status) { 100 if (!done) { 101 done = true; 102 executor.execute(() -> callback.onFailure(status.asException())); 103 } 104 } 105 success()106 private synchronized void success() { 107 if (!done) { 108 done = true; 109 executor.execute( 110 () -> callback.onSuccess(ticker.read() - startTimeNanos)); 111 } 112 } 113 } 114 } 115