Skip to content

Commit

Permalink
core: Exit idle mode in enterIdle() if there's delayed transport.
Browse files Browse the repository at this point in the history
This change assures that if there are only calls in real transport the
channel will remain in idle mode. Idle mode will be exited if there
are calls in delayed transport to allow them to be processed.
  • Loading branch information
temawi committed Aug 19, 2021
1 parent a795d3c commit 6536a1c
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 1 deletion.
3 changes: 2 additions & 1 deletion core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
Expand Up @@ -423,7 +423,8 @@ private void enterIdleMode() {
delayedTransport.reprocess(null);
channelLogger.log(ChannelLogLevel.INFO, "Entering IDLE state");
channelStateManager.gotoState(IDLE);
if (inUseStateAggregator.isInUse()) {
// If there are still delayed streams we have to exit idle mode in order to handle them.
if (delayedTransport.hasPendingStreams()) {
exitIdleMode();
}
}
Expand Down
Expand Up @@ -26,7 +26,9 @@
import static org.mockito.AdditionalAnswers.delegatesTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atMostOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
Expand Down Expand Up @@ -284,6 +286,22 @@ public void delayedTransportHoldsOffIdleness() throws Exception {
verify(mockLoadBalancer).shutdown();
}

@Test
public void delayedTransportExitsIdleAfterEnter() throws Exception {
// Start a new call that will go to the delayed transport
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata());
deliverResolutionResult();

channel.enterIdle();

// Since we have a call in delayed transport, the call to enterIdle() should have resulted in
// the channel going to idle mode and then immediately exiting. We confirm this by verifying
// that the name resolver was started up twice - once when the call was first created and a
// second time after exiting idle mode.
verify(mockNameResolver, times(2)).start(any(NameResolver.Listener2.class));
}

@Test
public void realTransportsHoldsOffIdleness() throws Exception {
final EquivalentAddressGroup addressGroup = servers.get(1);
Expand Down Expand Up @@ -332,6 +350,50 @@ public void realTransportsHoldsOffIdleness() throws Exception {
verify(mockLoadBalancer).shutdown();
}

@Test
public void enterIdleWhileRealTransportInProgress() {
final EquivalentAddressGroup addressGroup = servers.get(1);

// Start a call, which goes to delayed transport
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, new Metadata());

// Verify that we have exited the idle mode
ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture());
deliverResolutionResult();
Helper helper = helperCaptor.getValue();

// Create a subchannel for the real transport to happen on.
Subchannel subchannel = createSubchannelSafely(helper, addressGroup, Attributes.EMPTY);
requestConnectionSafely(helper, subchannel);
MockClientTransportInfo t0 = newTransports.poll();
t0.listener.transportReady();

SubchannelPicker mockPicker = mock(SubchannelPicker.class);
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
.thenReturn(PickResult.withSubchannel(subchannel));
updateBalancingStateSafely(helper, READY, mockPicker);

// Delayed transport creates real streams in the app executor
executor.runDueTasks();

// Move transport to the in-use state
t0.listener.transportInUse(true);

// Now we enter Idle mode while real transport is happening
channel.enterIdle();

// Verify that the name resolver and the load balance were shut down.
verify(mockNameResolver).shutdown();
verify(mockLoadBalancer).shutdown();

// When there are no pending streams, the call to enterIdle() should stick and
// we remain in idle mode. We verify this by making sure that the name resolver
// was not started up more than once (the initial startup).
verify(mockNameResolver, atMostOnce()).start(isA(NameResolver.Listener2.class));
}

@Test
public void updateSubchannelAddresses_newAddressConnects() {
ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
Expand Down

0 comments on commit 6536a1c

Please sign in to comment.