|
42 | 42 | import io.grpc.ChannelLogger; |
43 | 43 | import io.grpc.ClientCall; |
44 | 44 | import io.grpc.ClientInterceptor; |
| 45 | +import io.grpc.ClientStreamTracer; |
45 | 46 | import io.grpc.ConnectivityState; |
46 | 47 | import io.grpc.EquivalentAddressGroup; |
47 | 48 | import io.grpc.IntegerMarshaller; |
| 49 | +import io.grpc.InternalConfigSelector; |
48 | 50 | import io.grpc.LoadBalancer; |
49 | 51 | import io.grpc.LoadBalancer.CreateSubchannelArgs; |
50 | 52 | import io.grpc.LoadBalancer.Helper; |
|
61 | 63 | import io.grpc.MethodDescriptor; |
62 | 64 | import io.grpc.MethodDescriptor.MethodType; |
63 | 65 | import io.grpc.NameResolver; |
| 66 | +import io.grpc.NameResolver.ConfigOrError; |
64 | 67 | import io.grpc.NameResolver.ResolutionResult; |
65 | 68 | import io.grpc.NameResolverProvider; |
66 | 69 | import io.grpc.Status; |
|
79 | 82 | import java.util.concurrent.BlockingQueue; |
80 | 83 | import java.util.concurrent.Executor; |
81 | 84 | import java.util.concurrent.TimeUnit; |
| 85 | +import java.util.concurrent.atomic.AtomicInteger; |
82 | 86 | import java.util.concurrent.atomic.AtomicReference; |
83 | 87 | import org.junit.After; |
84 | 88 | import org.junit.Before; |
@@ -314,6 +318,66 @@ public void pendingCallExitsIdleAfterEnter() throws Exception { |
314 | 318 | verify(mockNameResolver, times(2)).start(any(NameResolver.Listener2.class)); |
315 | 319 | } |
316 | 320 |
|
| 321 | + @Test |
| 322 | + public void newCallAfterIdleWaitsForFreshConfigSelector() { |
| 323 | + AtomicInteger staleSelectCount = new AtomicInteger(); |
| 324 | + AtomicInteger freshSelectCount = new AtomicInteger(); |
| 325 | + Attributes staleSelectorAttrs = Attributes.newBuilder() |
| 326 | + .set(InternalConfigSelector.KEY, countingConfigSelector(staleSelectCount)) |
| 327 | + .build(); |
| 328 | + Attributes freshSelectorAttrs = Attributes.newBuilder() |
| 329 | + .set(InternalConfigSelector.KEY, countingConfigSelector(freshSelectCount)) |
| 330 | + .build(); |
| 331 | + ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(Helper.class); |
| 332 | + |
| 333 | + assertEquals(ConnectivityState.IDLE, channel.getState(true)); |
| 334 | + deliverResolutionResult(staleSelectorAttrs, 1); |
| 335 | + |
| 336 | + channel.enterIdle(); |
| 337 | + |
| 338 | + Metadata headers = new Metadata(); |
| 339 | + ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT); |
| 340 | + call.start(mockCallListener, headers); |
| 341 | + |
| 342 | + assertEquals(0, staleSelectCount.get()); |
| 343 | + assertEquals(0, freshSelectCount.get()); |
| 344 | + verify(mockLoadBalancerProvider, times(2)).newLoadBalancer(helperCaptor.capture()); |
| 345 | + Helper helper = helperCaptor.getAllValues().get(1); |
| 346 | + |
| 347 | + deliverResolutionResult(freshSelectorAttrs, 2); |
| 348 | + |
| 349 | + assertEquals(0, staleSelectCount.get()); |
| 350 | + verifyPendingCallDrainedToTransport(helper); |
| 351 | + assertEquals(1, freshSelectCount.get()); |
| 352 | + |
| 353 | + channel.enterIdle(); |
| 354 | + } |
| 355 | + |
| 356 | + @Test |
| 357 | + public void enterIdleWithInitialPendingCallDoesNotReprocessUntilConfigArrives() { |
| 358 | + ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(Helper.class); |
| 359 | + Metadata headers = new Metadata(); |
| 360 | + |
| 361 | + assertEquals(ConnectivityState.IDLE, channel.getState(true)); |
| 362 | + ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT); |
| 363 | + call.start(mockCallListener, headers); |
| 364 | + |
| 365 | + channel.enterIdle(); |
| 366 | + |
| 367 | + assertFalse(channel.isInPanicMode()); |
| 368 | + verify(mockCallListener, never()).onClose(any(Status.class), any(Metadata.class)); |
| 369 | + verify(mockLoadBalancerProvider, times(2)).newLoadBalancer(helperCaptor.capture()); |
| 370 | + Helper helper = helperCaptor.getAllValues().get(1); |
| 371 | + |
| 372 | + deliverResolutionResult(Attributes.EMPTY, 2); |
| 373 | + |
| 374 | + assertFalse(channel.isInPanicMode()); |
| 375 | + verify(mockCallListener, never()).onClose(any(Status.class), any(Metadata.class)); |
| 376 | + verifyPendingCallDrainedToTransport(helper); |
| 377 | + |
| 378 | + channel.enterIdle(); |
| 379 | + } |
| 380 | + |
317 | 381 | @Test |
318 | 382 | public void delayedTransportExitsIdleAfterEnter() throws Exception { |
319 | 383 | // Start a new call that will go to the delayed transport |
@@ -612,18 +676,64 @@ public void run() { |
612 | 676 | } |
613 | 677 |
|
614 | 678 | private void deliverResolutionResult() { |
615 | | - verify(mockNameResolver).start(nameResolverListenerCaptor.capture()); |
| 679 | + deliverResolutionResult(Attributes.EMPTY, 1); |
| 680 | + } |
| 681 | + |
| 682 | + private void deliverResolutionResult(Attributes attributes, int nameResolverStartCount) { |
| 683 | + verify(mockNameResolver, times(nameResolverStartCount)).start( |
| 684 | + nameResolverListenerCaptor.capture()); |
616 | 685 | // Simulate new address resolved to make sure the LoadBalancer is correctly linked to |
617 | 686 | // the NameResolver. |
618 | | - ResolutionResult resolutionResult = |
| 687 | + ResolutionResult.Builder resultBuilder = |
619 | 688 | ResolutionResult.newBuilder() |
620 | 689 | .setAddressesOrError(StatusOr.fromValue(servers)) |
621 | | - .setAttributes(Attributes.EMPTY) |
622 | | - .build(); |
623 | | - nameResolverListenerCaptor.getValue().onResult(resolutionResult); |
| 690 | + .setAttributes(attributes); |
| 691 | + if (attributes.get(InternalConfigSelector.KEY) != null) { |
| 692 | + resultBuilder.setServiceConfig( |
| 693 | + ConfigOrError.fromConfig(ManagedChannelServiceConfig.empty())); |
| 694 | + } |
| 695 | + ResolutionResult resolutionResult = resultBuilder.build(); |
| 696 | + List<NameResolver.Listener2> listeners = nameResolverListenerCaptor.getAllValues(); |
| 697 | + listeners.get(listeners.size() - 1).onResult(resolutionResult); |
624 | 698 | executor.runDueTasks(); |
625 | 699 | } |
626 | 700 |
|
| 701 | + private void verifyPendingCallDrainedToTransport(Helper helper) { |
| 702 | + ClientStream mockStream = mock(ClientStream.class); |
| 703 | + Subchannel subchannel = createSubchannelSafely(helper, servers.get(0), Attributes.EMPTY); |
| 704 | + requestConnectionSafely(helper, subchannel); |
| 705 | + MockClientTransportInfo transportInfo = newTransports.poll(); |
| 706 | + when(transportInfo.transport.newStream( |
| 707 | + same(method), any(Metadata.class), any(CallOptions.class), |
| 708 | + any(ClientStreamTracer[].class))) |
| 709 | + .thenReturn(mockStream); |
| 710 | + transportInfo.listener.transportReady(); |
| 711 | + SubchannelPicker picker = mock(SubchannelPicker.class); |
| 712 | + when(picker.pickSubchannel(any(PickSubchannelArgs.class))) |
| 713 | + .thenReturn(PickResult.withSubchannel(subchannel)); |
| 714 | + |
| 715 | + updateBalancingStateSafely(helper, READY, picker); |
| 716 | + executor.runDueTasks(); |
| 717 | + |
| 718 | + verify(transportInfo.transport).newStream( |
| 719 | + same(method), any(Metadata.class), any(CallOptions.class), |
| 720 | + any(ClientStreamTracer[].class)); |
| 721 | + verify(mockStream).start(any(ClientStreamListener.class)); |
| 722 | + } |
| 723 | + |
| 724 | + private static InternalConfigSelector countingConfigSelector( |
| 725 | + final AtomicInteger selectCount) { |
| 726 | + return new InternalConfigSelector() { |
| 727 | + @Override |
| 728 | + public Result selectConfig(PickSubchannelArgs args) { |
| 729 | + selectCount.incrementAndGet(); |
| 730 | + return Result.newBuilder() |
| 731 | + .setConfig(ManagedChannelServiceConfig.empty()) |
| 732 | + .build(); |
| 733 | + } |
| 734 | + }; |
| 735 | + } |
| 736 | + |
627 | 737 | private static void requestConnectionSafely(Helper helper, final Subchannel subchannel) { |
628 | 738 | helper.getSynchronizationContext().execute( |
629 | 739 | new Runnable() { |
|
0 commit comments