@Before public void setUp() { MockitoAnnotations.initMocks(this); when(provider.get(any(CallOptions.class))).thenReturn(transport); when(transport.newStream( any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class))) .thenReturn(stream); }
@Test public void advertisedEncodingsAreSent() { MethodDescriptor<Void, Void> descriptor = MethodDescriptor.create( MethodType.UNARY, "service/method", new TestMarshaller<Void>(), new TestMarshaller<Void>()); final ClientTransport transport = mock(ClientTransport.class); final ClientStream stream = mock(ClientStream.class); ClientTransportProvider provider = new ClientTransportProvider() { @Override public ListenableFuture<ClientTransport> get(CallOptions callOptions) { return Futures.immediateFuture(transport); } }; when(transport.newStream( any(MethodDescriptor.class), any(Metadata.class), any(ClientStreamListener.class))) .thenReturn(stream); ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( descriptor, executor, CallOptions.DEFAULT, provider, deadlineCancellationExecutor) .setDecompressorRegistry(decompressorRegistry); call.start(new TestClientCallListener<Void>(), new Metadata()); ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class); verify(transport) .newStream(eq(descriptor), metadataCaptor.capture(), isA(ClientStreamListener.class)); Metadata actual = metadataCaptor.getValue(); Set<String> acceptedEncodings = ImmutableSet.copyOf(actual.getAll(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY)); assertEquals(decompressorRegistry.getAdvertisedMessageEncodings(), acceptedEncodings); }
@Override public void start(final Listener<RespT> observer, Metadata headers) { checkState(stream == null, "Already started"); checkNotNull(observer, "observer"); checkNotNull(headers, "headers"); // Create the context final Deadline effectiveDeadline = min(callOptions.getDeadline(), parentContext.getDeadline()); if (effectiveDeadline != parentContext.getDeadline()) { context = parentContext.withDeadline(effectiveDeadline, deadlineCancellationExecutor); } else { context = parentContext.withCancellation(); } if (context.isCancelled()) { // Context is already cancelled so no need to create a real stream, just notify the observer // of cancellation via callback on the executor stream = NoopClientStream.INSTANCE; callExecutor.execute( new ContextRunnable(context) { @Override public void runInContext() { observer.onClose(statusFromCancelled(context), new Metadata()); } }); return; } final String compressorName = callOptions.getCompressor(); Compressor compressor = null; if (compressorName != null) { compressor = compressorRegistry.lookupCompressor(compressorName); if (compressor == null) { stream = NoopClientStream.INSTANCE; callExecutor.execute( new ContextRunnable(context) { @Override public void runInContext() { observer.onClose( Status.INTERNAL.withDescription( String.format("Unable to find compressor by name %s", compressorName)), new Metadata()); } }); return; } } else { compressor = Codec.Identity.NONE; } prepareHeaders(headers, callOptions, userAgent, decompressorRegistry, compressor); final boolean deadlineExceeded = effectiveDeadline != null && effectiveDeadline.isExpired(); if (!deadlineExceeded) { updateTimeoutHeaders( effectiveDeadline, callOptions.getDeadline(), parentContext.getDeadline(), headers); ClientTransport transport = clientTransportProvider.get(callOptions); stream = transport.newStream(method, headers); } else { stream = new FailingClientStream(DEADLINE_EXCEEDED); } if (callOptions.getAuthority() != null) { stream.setAuthority(callOptions.getAuthority()); } stream.setCompressor(compressor); stream.start(new ClientStreamListenerImpl(observer)); if (compressor != Codec.Identity.NONE) { stream.setMessageCompression(true); } // Delay any sources of cancellation after start(), because most of the transports are broken if // they receive cancel before start. Issue #1343 has more details // Propagate later Context cancellation to the remote side. context.addListener(this, directExecutor()); if (contextListenerShouldBeRemoved) { // Race detected! ClientStreamListener.closed may have been called before // deadlineCancellationFuture was set, thereby preventing the future from being cancelled. // Go ahead and cancel again, just to be sure it was cancelled. context.removeListener(this); } }
@Test public void shutdownBeforeTransportCreatedWithPendingStream() throws Exception { SocketAddress addr = mock(SocketAddress.class); createTransportSet(addr); // First transport is created immediately ClientTransport pick = transportSet.obtainActiveTransport(); assertEquals(ConnectivityState.CONNECTING, transportSet.getState(false)); verify(mockTransportFactory).newClientTransport(addr, AUTHORITY, USER_AGENT); assertNotNull(pick); // Fail this one MockClientTransportInfo transportInfo = transports.poll(); transportInfo.listener.transportShutdown(Status.UNAVAILABLE); assertEquals(ConnectivityState.TRANSIENT_FAILURE, transportSet.getState(false)); transportInfo.listener.transportTerminated(); // Second transport will wait for back-off pick = transportSet.obtainActiveTransport(); assertTrue(pick instanceof DelayedClientTransport); // Start a stream, which will be pending in the delayed transport ClientStream pendingStream = pick.newStream(method, headers, waitForReadyCallOptions, statsTraceCtx); pendingStream.start(mockStreamListener); // Shut down TransportSet before the transport is created. Further call to // obtainActiveTransport() gets failing transports assertEquals(ConnectivityState.TRANSIENT_FAILURE, transportSet.getState(false)); transportSet.shutdown(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); pick = transportSet.obtainActiveTransport(); assertNotNull(pick); assertTrue(pick instanceof FailingClientTransport); verify(mockTransportFactory).newClientTransport(addr, AUTHORITY, USER_AGENT); // Reconnect will eventually happen, even though TransportSet has been shut down fakeClock.forwardMillis(10); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verify(mockTransportFactory, times(2)).newClientTransport(addr, AUTHORITY, USER_AGENT); // The pending stream will be started on this newly started transport after it's ready. // The transport is shut down by TransportSet right after the stream is created. transportInfo = transports.poll(); verify(transportInfo.transport, times(0)).shutdown(); assertEquals(0, fakeExecutor.numPendingTasks()); transportInfo.listener.transportReady(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verify(transportInfo.transport, times(0)) .newStream(any(MethodDescriptor.class), any(Metadata.class)); assertEquals(1, fakeExecutor.runDueTasks()); verify(transportInfo.transport) .newStream( same(method), same(headers), same(waitForReadyCallOptions), any(StatsTraceContext.class)); verify(transportInfo.transport).shutdown(); transportInfo.listener.transportShutdown(Status.UNAVAILABLE); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verify(mockTransportSetCallback, never()).onTerminated(any(TransportSet.class)); // Terminating the transport will let TransportSet to be terminated. transportInfo.listener.transportTerminated(); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verify(mockTransportSetCallback).onTerminated(transportSet); // No more transports will be created. fakeClock.forwardMillis(10000); assertEquals(ConnectivityState.SHUTDOWN, transportSet.getState(false)); verifyNoMoreInteractions(mockTransportFactory); assertEquals(0, transports.size()); }