Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't schedule multiple pings. #3295

Merged
merged 1 commit into from
Aug 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions core/src/main/java/io/grpc/internal/KeepAliveManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.grpc.internal;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.MoreExecutors;
Expand Down Expand Up @@ -61,6 +62,7 @@ public void run() {
private final Runnable sendPing = new LogExceptionRunnable(new Runnable() {
@Override
public void run() {
pingFuture = null;
boolean shouldSendPing = false;
synchronized (KeepAliveManager.this) {
if (state == State.PING_SCHEDULED) {
Expand Down Expand Up @@ -170,8 +172,8 @@ public synchronized void onDataReceived() {
}
// schedule a new ping
state = State.PING_SCHEDULED;
pingFuture =
scheduler.schedule(sendPing, keepAliveTimeInNanos, TimeUnit.NANOSECONDS);
checkState(pingFuture == null, "There should be no outstanding pingFuture");
pingFuture = scheduler.schedule(sendPing, keepAliveTimeInNanos, TimeUnit.NANOSECONDS);
}
}

Expand All @@ -183,10 +185,12 @@ public synchronized void onTransportActive() {
// When the transport goes active, we do not reset the nextKeepaliveTime. This allows us to
// quickly check whether the connection is still working.
state = State.PING_SCHEDULED;
pingFuture = scheduler.schedule(
sendPing,
nextKeepaliveTime - ticker.read(),
TimeUnit.NANOSECONDS);
if (pingFuture == null) {
pingFuture = scheduler.schedule(
sendPing,
nextKeepaliveTime - ticker.read(),
TimeUnit.NANOSECONDS);
}
} else if (state == State.IDLE_AND_PING_SENT) {
state = State.PING_SENT;
} // Other states are possible when keepAliveDuringTransportIdle == true
Expand Down Expand Up @@ -218,6 +222,7 @@ public synchronized void onTransportTermination() {
}
if (pingFuture != null) {
pingFuture.cancel(false);
pingFuture = null;
}
}
}
Expand Down
36 changes: 34 additions & 2 deletions core/src/test/java/io/grpc/internal/KeepAliveManagerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ public void sendKeepAlivePings() {

@Test
public void keepAlivePingDelayedByIncomingData() {
ScheduledFuture<?> future = mock(ScheduledFuture.class);
doReturn(future)
.when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));

// Transport becomes active. We should schedule keepalive pings.
keepAliveManager.onTransportActive();
ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
Expand Down Expand Up @@ -218,6 +222,10 @@ public void keepAlivePingTimesOut() {

@Test
public void transportGoesIdle() {
ScheduledFuture<?> pingFuture = mock(ScheduledFuture.class);
doReturn(pingFuture)
.when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));

// Transport becomes active. We should schedule keepalive pings.
keepAliveManager.onTransportActive();
ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
Expand All @@ -229,10 +237,14 @@ public void transportGoesIdle() {
keepAliveManager.onTransportIdle();
sendPing.run();
// Ping was not sent.
verify(transport, times(0)).ping(isA(ClientTransport.PingCallback.class),
isA(Executor.class));
verify(transport, times(0)).ping(isA(ClientTransport.PingCallback.class), isA(Executor.class));
// No new ping got scheduled.
verify(scheduler, times(1)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));

// But when transport goes back to active
keepAliveManager.onTransportActive();
// Then we do schedule another ping
verify(scheduler, times(2)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
}

@Test
Expand Down Expand Up @@ -294,6 +306,26 @@ public void transportGoesIdleAfterPingSent() {
verify(scheduler, times(3)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
}

@Test
public void transportGoesIdleBeforePingSent() {
// Transport becomes active. We should schedule keepalive pings.
ScheduledFuture<?> pingFuture = mock(ScheduledFuture.class);
doReturn(pingFuture)
.when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
keepAliveManager.onTransportActive();
verify(scheduler, times(1)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));

// Data is received, and we go to ping delayed
keepAliveManager.onDataReceived();

// Transport becomes idle while the 1st ping is still scheduled
keepAliveManager.onTransportIdle();

// Transport becomes active again, we don't need to reschedule another ping
keepAliveManager.onTransportActive();
verify(scheduler, times(1)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
}

@Test
public void transportShutsdownAfterPingScheduled() {
ScheduledFuture<?> pingFuture = mock(ScheduledFuture.class);
Expand Down