@Before public void beforeMethod() { channelMock = mock(Channel.class); channelPoolMock = mock(ChannelPool.class); eventLoopMock = mock(EventLoop.class); contentChunkMock = mock(HttpContent.class); callActiveHolder = new ObjectHolder<>(); callActiveHolder.heldObject = true; downstreamLastChunkSentHolder = new ObjectHolder<>(); downstreamLastChunkSentHolder.heldObject = false; spanForDownstreamCallMock = mock(Span.class); proxySpanTaggingStrategyMock = mock(ProxyRouterSpanNamingAndTaggingStrategy.class); streamingChannelSpy = spy(new StreamingChannel( channelMock, channelPoolMock, callActiveHolder, downstreamLastChunkSentHolder, null, null, spanForDownstreamCallMock, proxySpanTaggingStrategyMock )); writeAndFlushChannelFutureMock = mock(ChannelFuture.class); doReturn(eventLoopMock).when(channelMock).eventLoop(); doReturn(writeAndFlushChannelFutureMock).when(channelMock).writeAndFlush(contentChunkMock); channelIsBrokenAttrMock = mock(Attribute.class); doReturn(channelIsBrokenAttrMock).when(channelMock).attr(CHANNEL_IS_BROKEN_ATTR); streamChunkChannelPromiseMock = mock(ChannelPromise.class); doReturn(streamChunkChannelPromiseMock).when(channelMock).newPromise(); failedFutureMock = mock(ChannelFuture.class); doReturn(failedFutureMock).when(channelMock).newFailedFuture(any(Throwable.class)); }
@Test public void StreamingChannel_closeChannelDueToUnrecoverableError_calls_the_do_method_and_sets_field_to_true_when_not_closed_and_call_active() { // given Throwable unrecoverableError = new RuntimeException("kaboom"); streamingChannelSpy.channelClosedDueToUnrecoverableError = false; streamingChannelSpy.callActiveHolder.heldObject = true; // when streamingChannelSpy.closeChannelDueToUnrecoverableError(unrecoverableError); // then assertThat(streamingChannelSpy.channelClosedDueToUnrecoverableError).isTrue(); ArgumentCaptor<Runnable> taskCaptor = ArgumentCaptor.forClass(Runnable.class); verify(eventLoopMock).execute(taskCaptor.capture()); Runnable task = taskCaptor.getValue(); assertThat(task).isNotNull(); // and given verify(streamingChannelSpy, never()).doCloseChannelDueToUnrecoverableError(any(Throwable.class)); // when task.run(); // then verify(streamingChannelSpy).doCloseChannelDueToUnrecoverableError(unrecoverableError); }
() -> doStreamChunk(chunkToWrite).addListener(future -> { if (future.isCancelled()) { result.cancel(true);
streamingChannel.complete(new StreamingChannel( ch, pool, callActiveHolder, lastChunkSentDownstreamHolder, distributedSpanStackToUse, mdcContextToUse, spanForDownstreamCall,
streamingChannel.complete(new StreamingChannel( ch, pool, callActiveHolder, lastChunkSentDownstreamHolder, distributedSpanStackToUse, mdcContextToUse, spanForDownstreamCall,
ChannelFuture writeFuture = sc.streamChunk(msgContent); writeFuture.addListener(future -> { try { String errorMsg = "Chunk streaming ChannelFuture came back as being unsuccessful. " + "downstream_channel_id=" + sc.getChannel().toString(); Throwable errorToFire = new WrapperException(errorMsg, future.cause()); StreamingCallback callback = proxyRouterState.getStreamingCallback(); sc.closeChannelDueToUnrecoverableError(future.cause()); String downstreamChannelId = (scToNotify == null) ? "UNKNOWN" : scToNotify.getChannel().toString(); String errorMsg = "Chunk streaming future came back as being unsuccessful. " + "downstream_channel_id=" + downstreamChannelId; scToNotify.closeChannelDueToUnrecoverableError(cause);
ChannelFuture writeFuture = sc.streamChunk(msgContent); writeFuture.addListener(future -> { try { String errorMsg = "Chunk streaming ChannelFuture came back as being unsuccessful. " + "downstream_channel_id=" + sc.getChannel().toString(); Throwable errorToFire = new WrapperException(errorMsg, future.cause()); StreamingCallback callback = proxyRouterState.getStreamingCallback(); sc.closeChannelDueToUnrecoverableError(future.cause()); String downstreamChannelId = (scToNotify == null) ? "UNKNOWN" : scToNotify.getChannel().toString(); String errorMsg = "Chunk streaming future came back as being unsuccessful. " + "downstream_channel_id=" + downstreamChannelId; scToNotify.closeChannelDueToUnrecoverableError(cause);
).run(); handleWireSendFinishAnnotationIfNecessary(); chunkToWrite.release(); return channel.newSucceededFuture(); handleWireSendFinishAnnotationIfNecessary();
).run(); handleWireSendFinishAnnotationIfNecessary(); chunkToWrite.release(); return channel.newSucceededFuture(); handleWireSendFinishAnnotationIfNecessary();
() -> doStreamChunk(chunkToWrite).addListener(future -> { if (future.isCancelled()) { result.cancel(true);
doReturn(doStreamChunkFutureMock).when(streamingChannelSpy).doStreamChunk(any(HttpContent.class)); ChannelFuture result = streamingChannelSpy.streamChunk(contentChunkMock); verify(streamingChannelSpy, never()).doStreamChunk(any(HttpContent.class)); // not yet called verify(streamingChannelSpy).doStreamChunk(contentChunkMock); ArgumentCaptor<GenericFutureListener> listenerCaptor = ArgumentCaptor.forClass(GenericFutureListener.class); verify(doStreamChunkFutureMock).addListener(listenerCaptor.capture());
public void closeChannelDueToUnrecoverableError(Throwable cause) { try { // Ignore subsequent calls to this method, and only try to do something if the call is still active. // If the call is *not* active, then everything has already been cleaned up and we shouldn't // do anything because the channel might have already been handed out for a different call. if (!channelClosedDueToUnrecoverableError && callActiveHolder.heldObject) { // Schedule the close on the channel's event loop. channel.eventLoop().execute(() -> doCloseChannelDueToUnrecoverableError(cause)); return; } if (!alreadyLoggedMessageAboutIgnoringCloseDueToError && logger.isDebugEnabled()) { runnableWithTracingAndMdc( () -> logger.debug( "Ignoring calls to StreamingChannel.closeChannelDueToUnrecoverableError() because it " + "has already been called, or the call is no longer active. " + "previously_called={}, call_is_active={}", channelClosedDueToUnrecoverableError, callActiveHolder.heldObject ), distributedTracingSpanStack, distributedTracingMdcInfo ).run(); } alreadyLoggedMessageAboutIgnoringCloseDueToError = true; } finally { channelClosedDueToUnrecoverableError = true; } }
public void closeChannelDueToUnrecoverableError(Throwable cause) { try { // Ignore subsequent calls to this method, and only try to do something if the call is still active. // If the call is *not* active, then everything has already been cleaned up and we shouldn't // do anything because the channel might have already been handed out for a different call. if (!channelClosedDueToUnrecoverableError && callActiveHolder.heldObject) { // Schedule the close on the channel's event loop. channel.eventLoop().execute(() -> doCloseChannelDueToUnrecoverableError(cause)); return; } if (!alreadyLoggedMessageAboutIgnoringCloseDueToError && logger.isDebugEnabled()) { runnableWithTracingAndMdc( () -> logger.debug( "Ignoring calls to StreamingChannel.closeChannelDueToUnrecoverableError() because it " + "has already been called, or the call is no longer active. " + "previously_called={}, call_is_active={}", channelClosedDueToUnrecoverableError, callActiveHolder.heldObject ), distributedTracingSpanStack, distributedTracingMdcInfo ).run(); } alreadyLoggedMessageAboutIgnoringCloseDueToError = true; } finally { channelClosedDueToUnrecoverableError = true; } }
@DataProvider(value = { "false | 0", "true | 42" }, splitBy = "\\|") @Test public void StreamingChannel_doStreamChunk_works_as_expected_when_last_chunk_already_sent_downstream_and_incoming_chunk_does_not_match_requirements( boolean chunkIsLastChunk, int readableBytes ) { // given streamingChannelSpy.downstreamLastChunkSentHolder.heldObject = true; streamingChannelSpy.callActiveHolder.heldObject = true; streamingChannelSpy.channelClosedDueToUnrecoverableError = false; HttpContent contentChunkMock = (chunkIsLastChunk) ? mock(LastHttpContent.class) : mock(HttpContent.class); ByteBuf contentByteBufMock = mock(ByteBuf.class); doReturn(contentByteBufMock).when(contentChunkMock).content(); doReturn(readableBytes).when(contentByteBufMock).readableBytes(); doReturn(writeAndFlushChannelFutureMock).when(channelMock).writeAndFlush(contentChunkMock); // when ChannelFuture result = streamingChannelSpy.doStreamChunk(contentChunkMock); // then verify(channelMock).writeAndFlush(contentChunkMock); assertThat(result).isSameAs(writeAndFlushChannelFutureMock); }
@DataProvider(value = { "true", "false" }) @Test public void StreamingChannel_doCloseChannelDueToUnrecoverableError_works_as_expected(boolean callActive) { // given streamingChannelSpy.callActiveHolder.heldObject = callActive; Throwable unrecoverableError = new RuntimeException("kaboom"); // when streamingChannelSpy.doCloseChannelDueToUnrecoverableError(unrecoverableError); // then if (callActive) { verify(channelIsBrokenAttrMock).set(true); verifyChannelReleasedBackToPool(streamingChannelSpy.callActiveHolder, channelPoolMock, channelMock); verify(channelMock).close(); } else { verify(channelIsBrokenAttrMock, never()).set(anyBoolean()); verify(channelMock, never()).close(); } }
@Test public void StreamingChannel_constructor_sets_fields_as_expected() { // given Deque<Span> spanStackMock = mock(Deque.class); Map<String, String> mdcInfoMock = mock(Map.class); // when StreamingChannel sc = new StreamingChannel( channelMock, channelPoolMock, callActiveHolder, downstreamLastChunkSentHolder, spanStackMock, mdcInfoMock, spanForDownstreamCallMock, proxySpanTaggingStrategyMock ); // then assertThat(sc.channel).isSameAs(channelMock); assertThat(sc.getChannel()).isSameAs(sc.channel); assertThat(sc.pool).isSameAs(channelPoolMock); assertThat(sc.callActiveHolder).isSameAs(callActiveHolder); assertThat(sc.downstreamLastChunkSentHolder).isSameAs(downstreamLastChunkSentHolder); assertThat(sc.distributedTracingSpanStack).isSameAs(spanStackMock); assertThat(sc.distributedTracingMdcInfo).isSameAs(mdcInfoMock); assertThat(sc.spanForDownstreamCall).isSameAs(spanForDownstreamCallMock); assertThat(sc.proxySpanTaggingStrategy).isSameAs(proxySpanTaggingStrategyMock); }
@DataProvider(value = { "true | true", "false | false", "true | false" }, splitBy = "\\|") @Test public void StreamingChannel_closeChannelDueToUnrecoverableError_sets_field_to_true_but_otherwise_does_nothing_if_already_closed_or_call_inactive( boolean alreadyClosed, boolean callActive ) { // given Throwable unrecoverableError = new RuntimeException("kaboom"); streamingChannelSpy.channelClosedDueToUnrecoverableError = alreadyClosed; streamingChannelSpy.callActiveHolder.heldObject = callActive; // when streamingChannelSpy.closeChannelDueToUnrecoverableError(unrecoverableError); // then assertThat(streamingChannelSpy.channelClosedDueToUnrecoverableError).isTrue(); verifyZeroInteractions(channelMock); }
@Test public void StreamingChannel_doStreamChunk_works_as_expected_when_last_chunk_already_sent_downstream_and_incoming_chunk_is_empty_last_chunk() { // given streamingChannelSpy.downstreamLastChunkSentHolder.heldObject = true; LastHttpContent contentChunkMock = mock(LastHttpContent.class); ByteBuf contentByteBufMock = mock(ByteBuf.class); doReturn(contentByteBufMock).when(contentChunkMock).content(); doReturn(0).when(contentByteBufMock).readableBytes(); ChannelFuture successFutureMock = mock(ChannelFuture.class); doReturn(successFutureMock).when(channelMock).newSucceededFuture(); // when ChannelFuture result = streamingChannelSpy.doStreamChunk(contentChunkMock); // then verify(channelMock, never()).writeAndFlush(any(Object.class)); verify(contentChunkMock).release(); verify(channelMock).newSucceededFuture(); assertThat(result).isSameAs(successFutureMock); }
@Test public void StreamingChannel_closeChannelDueToUnrecoverableError_sets_field_to_true_even_if_crazy_exception_occurs() { // given streamingChannelSpy.channelClosedDueToUnrecoverableError = false; streamingChannelSpy.callActiveHolder.heldObject = true; Throwable crazyEx = new RuntimeException("kaboom"); doThrow(crazyEx).when(channelMock).eventLoop(); // when Throwable caughtEx = catchThrowable( () -> streamingChannelSpy.closeChannelDueToUnrecoverableError(new RuntimeException("some other error")) ); // then assertThat(caughtEx).isSameAs(crazyEx); assertThat(streamingChannelSpy.channelClosedDueToUnrecoverableError).isTrue(); }
@Test public void StreamingChannel_doStreamChunk_works_as_expected_when_crazy_exception_is_thrown() { // given Throwable crazyEx = new RuntimeException("kaboom"); doThrow(crazyEx).when(channelMock).writeAndFlush(any(Object.class)); // when ChannelFuture result = streamingChannelSpy.doStreamChunk(contentChunkMock); // then verify(channelMock).writeAndFlush(any(Object.class)); verify(contentChunkMock, never()).release(); verifyFailedChannelFuture( result, "StreamingChannel.doStreamChunk() threw an exception", crazyEx ); }