/*
* Copyright 2016 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package io.reactivex.netty.channel;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.logging.LoggingHandler;
import io.reactivex.netty.channel.BackpressureManagingHandler.BytesWriteInterceptor;
import io.reactivex.netty.channel.BackpressureManagingHandler.RequestReadIfRequiredEvent;
import io.reactivex.netty.channel.BackpressureManagingHandler.State;
import io.reactivex.netty.test.util.InboundRequestFeeder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.Mockito;
import rx.Observable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class BackpressureManagingHandlerTest {
@Rule
public final HandlerRule handlerRule = new HandlerRule();
@Test(timeout = 60000)
public void testExactDemandAndSupply() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
final String msg1 = "hello1";
final String msg2 = "hello2";
handlerRule.feedMessagesForRead(msg1, msg2); /*Exact supply*/
handlerRule.setMaxMessagesPerRead(2); /*Send all msgs in one iteration*/
handlerRule.requestMessages(2); /*Exact demand*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg1, msg2);
/*Since, the demand is met (requested 2 and got 2) , we move to buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
}
@Test(timeout = 60000)
public void testExactDemandAndSupplyMultiRequests() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
final String msg1 = "hello1";
final String msg2 = "hello2";
handlerRule.feedMessagesForRead(msg1, msg2); /*Exact supply*/
handlerRule.setMaxMessagesPerRead(2); /*Send all msgs in one iteration*/
handlerRule.requestMessages(2); /*Exact demand*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg1, msg2);
/*Since, the demand is met (requested 2 and got 2) , we move to buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
handlerRule.resetReadCount();
assertThat("Unexpected read requested count post reset.", handlerRule.getReadRequestedCount(), is(0));
handlerRule.handler.reset();
final String msg3 = "hello3";
handlerRule.feedMessagesForRead(msg3);
/*No demand, no read fired*/
assertThat("Unexpected read requested count post reset.", handlerRule.getReadRequestedCount(), is(0));
handlerRule.assertMessagesReceived();
handlerRule.requestMessages(1);
/*Read on demand*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg3);
/*Since, the demand is met (requested 3 and got 3) , we move to buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Reading));
}
@Test(timeout = 60000)
public void testMoreDemand() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
final String msg1 = "hello1";
final String msg2 = "hello2";
handlerRule.feedMessagesForRead(msg1, msg2); /*less supply*/
handlerRule.setMaxMessagesPerRead(2); /*Send all msgs in one iteration*/
handlerRule.requestMessages(4); /*More demand*/
/*One read for start and one when the supply completed but demand exists.*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(2));
handlerRule.assertMessagesReceived(msg1, msg2);
/*Since, the demand is not met (requested 4 but got 2) , stay in read requested.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.ReadRequested));
}
@Test(timeout = 60000)
public void testMoreSupply() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
final String msg1 = "hello1";
final String msg2 = "hello2";
final String msg3 = "hello3";
handlerRule.feedMessagesForRead(msg1, msg2, msg3); /*more supply*/
handlerRule.setMaxMessagesPerRead(3); /*Send all msgs in one iteration*/
handlerRule.requestMessages(2); /*less demand*/
/*One read for start.*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg1, msg2);
/*Since, the demand was met (requested 2 and got 2) , but the supply was more (3), we should be buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), hasSize(1));
assertThat("Unexpected buffer contents.", handlerRule.handler.getBuffer(), contains((Object) msg3));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
}
@Test(timeout = 60000)
public void testBufferDrainSingleIteration() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
final String msg1 = "hello1";
final String msg2 = "hello2";
final String msg3 = "hello3";
handlerRule.feedMessagesForRead(msg1, msg2, msg3); /*more supply*/
handlerRule.setMaxMessagesPerRead(3); /*Send all msgs in one iteration & cause buffer*/
handlerRule.requestMessages(2); /*less demand*/
/*One read for start.*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg1, msg2);
/*Since, the demand was met (requested 2 and got 2) , but the supply was more (3), we should be buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), hasSize(1));
assertThat("Unexpected buffer contents.", handlerRule.handler.getBuffer(), contains((Object) msg3));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
handlerRule.resetReadCount();
assertThat("Unexpected read requested count post reset.", handlerRule.getReadRequestedCount(), is(0));
handlerRule.handler.reset();
handlerRule.requestMessages(1); /*Should come from the buffer.*/
assertThat("Unexpected read requested when expected to be fed from buffer.",
handlerRule.getReadRequestedCount(), is(0));
handlerRule.assertMessagesReceived(msg3);
/*Since, the demand is now met (requested 3 and got 3) , we move to buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), is(nullValue()));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
}
@Test(timeout = 60000)
public void testBufferDrainMultiIteration() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
final String msg1 = "hello1";
final String msg2 = "hello2";
final String msg3 = "hello3";
final String msg4 = "hello4";
handlerRule.feedMessagesForRead(msg1, msg2, msg3, msg4); /*more supply*/
handlerRule.setMaxMessagesPerRead(4); /*Send all msgs in one iteration & cause buffer*/
handlerRule.requestMessages(2); /*less demand*/
/*One read for start.*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg1, msg2);
/*Since, the demand was met (requested 2 and got 2) , but the supply was more (4), we should be buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), hasSize(2));
assertThat("Unexpected buffer contents.", handlerRule.handler.getBuffer(), contains((Object) msg3, msg4));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
/*Reset read state before next read*/
handlerRule.resetReadCount();
assertThat("Unexpected read requested count post reset.", handlerRule.getReadRequestedCount(), is(0));
handlerRule.handler.reset();
handlerRule.requestMessages(1); /*Should come from the buffer.*/
assertThat("Unexpected read requested when expected to be fed from buffer.",
handlerRule.getReadRequestedCount(), is(0));
handlerRule.assertMessagesReceived(msg3);
/*Since, the demand is now met (requested 3 and got 3) , we move to buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
/*Buffer does not change till it has data*/
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), hasSize(2));
/*Buffer reader index changes till it has data*/
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(1));
/*Reset read state before next read*/
handlerRule.resetReadCount();
assertThat("Unexpected read requested count post reset.", handlerRule.getReadRequestedCount(), is(0));
handlerRule.handler.reset();
handlerRule.requestMessages(1); /*Should come from the buffer.*/
assertThat("Unexpected read requested when expected to be fed from buffer.",
handlerRule.getReadRequestedCount(), is(0));
handlerRule.assertMessagesReceived(msg4);
/*Since, the demand is now met (requested 4 and got 4) , we move to buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), is(nullValue()));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
}
@Test(timeout = 60000)
public void testBufferDrainWithMoreDemand() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
final String msg1 = "hello1";
final String msg2 = "hello2";
final String msg3 = "hello3";
handlerRule.feedMessagesForRead(msg1, msg2, msg3); /*more supply*/
handlerRule.setMaxMessagesPerRead(3); /*Send all msgs in one iteration & cause buffer*/
handlerRule.requestMessages(2); /*less demand*/
/*One read for start.*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg1, msg2);
/*Since, the demand was met (requested 2 and got 2) , but the supply was more (3), we should be buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), hasSize(1));
assertThat("Unexpected buffer contents.", handlerRule.handler.getBuffer(), contains((Object) msg3));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
handlerRule.resetReadCount();
assertThat("Unexpected read requested count post reset.", handlerRule.getReadRequestedCount(), is(0));
handlerRule.handler.reset();
handlerRule.requestMessages(2); /*Should come from the buffer.*/
/*Since demand can not be fulfilled by the buffer, a read should be requested.*/
assertThat("Unexpected read requested.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg3);
/*Since, the demand is now met (requested 3 and got 3) , we move to buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.ReadRequested));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), is(nullValue()));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
}
@Test(timeout = 60000)
public void testBufferDrainOnRemove() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
final ByteBuf msg1 = Unpooled.buffer().writeBytes("hello1".getBytes());
final ByteBuf msg2 = Unpooled.buffer().writeBytes("hello2".getBytes());
handlerRule.feedMessagesForRead(msg1, msg2); /*More supply then demand*/
handlerRule.setMaxMessagesPerRead(2); /*Send all msgs in one iteration and cause buffer*/
handlerRule.requestMessages(1); /*Less demand*/
assertThat("Unexpected read requested count.", handlerRule.getReadRequestedCount(), is(1));
handlerRule.assertMessagesReceived(msg1);
/*Since, the demand is met (requested 1 and got 1) , we move to buffering.*/
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), hasSize(1));
assertThat("Unexpected buffer contents.", handlerRule.handler.getBuffer(), contains((Object) msg2));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
handlerRule.channel.close(); // Should remove handler.
handlerRule.channel.runPendingTasks();
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Stopped));
assertThat("Unexpected buffer size.", handlerRule.handler.getBuffer(), is(nullValue()));
assertThat("Unexpected buffer read index.", handlerRule.handler.getCurrentBufferIndex(), is(0));
assertThat("Buffered item not released.", msg2.refCnt(), is(0));
}
@Test(timeout = 60000)
public void testDiscardReadWhenStopped() throws Exception {
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Buffering));
handlerRule.channel.close(); // Should remove handler.
handlerRule.channel.runPendingTasks();
assertThat("Unexpected handler state.", handlerRule.handler.getCurrentState(), is(State.Stopped));
final ByteBuf msg = Unpooled.buffer().writeBytes("Hello".getBytes());
handlerRule.handler.channelRead(Mockito.mock(ChannelHandlerContext.class), msg);
assertThat("Message not released when stopped.", msg.refCnt(), is(0));
}
@Test(timeout = 60000)
public void testWriteWithBufferingHandler() throws Exception {
BufferingHandler bufferingHandler = new BufferingHandler();
handlerRule.channel.pipeline()
.addBefore(BytesWriteInterceptor.WRITE_INSPECTOR_HANDLER_NAME, "buffering-handler",
bufferingHandler);
final String[] dataToWrite = {"Hello1", "Hello2"};
handlerRule.channel.writeAndFlush(Observable.from(dataToWrite));/*Using Observable.from() to enable backpressure.*/
assertThat("Messages written to the channel, inspite of buffering", handlerRule.channel.outboundMessages(),
is(empty()));
/*Inspite of the messages, not reaching the channel, the extra demand should be generated and the buffering
handler should contain all messages.*/
assertThat("Unexpected buffer size in buffering handler.", bufferingHandler.buffer, hasSize(2));
}
public static class HandlerRule extends ExternalResource {
private MockBackpressureManagingHandler handler;
private EmbeddedChannel channel;
private InboundRequestFeeder inboundRequestFeeder;
private FixedRecvByteBufAllocator recvByteBufAllocator;
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
inboundRequestFeeder = new InboundRequestFeeder();
channel = new EmbeddedChannel(new LoggingHandler());
String bpName = "backpressure-manager";
channel.pipeline().addFirst(bpName,
handler = new MockBackpressureManagingHandler(bpName));
channel.pipeline().addBefore(bpName, "primitive-converter", new WriteTransformer());
channel.pipeline().addFirst(inboundRequestFeeder);
channel.config().setAutoRead(false);
recvByteBufAllocator = new FixedRecvByteBufAllocator(1024);
channel.config().setRecvByteBufAllocator(recvByteBufAllocator);
base.evaluate();
}
};
}
public void setMaxMessagesPerRead(int maxMessagesPerRead) {
recvByteBufAllocator.maxMessagesPerRead(maxMessagesPerRead);
}
public void assertMessagesReceived(Object... expected) {
final List<Object> msgsReceived = handler.getMsgsReceived();
if (null != expected && expected.length > 0) {
assertThat("Unexpected messages received count.", msgsReceived, hasSize(expected.length));
assertThat("Unexpected messages received.", msgsReceived, contains(expected));
} else {
assertThat("Unexpected messages received.", msgsReceived, is(empty()));
}
}
public int resetReadCount() {
return inboundRequestFeeder.resetReadRequested();
}
public int getReadRequestedCount() {
return inboundRequestFeeder.getReadRequestedCount();
}
public void requestMessages(long requested) throws Exception {
handler.incrementRequested(requested);
channel.pipeline().fireUserEventTriggered(new RequestReadIfRequiredEvent() {
@Override
protected boolean shouldReadMore(ChannelHandlerContext ctx) {
return true;
}
});
channel.runPendingTasks();
}
public void feedMessagesForRead(Object... msgs) {
inboundRequestFeeder.addToTheFeed(msgs);
}
}
private static class MockBackpressureManagingHandler extends BackpressureManagingHandler {
private final List<Object> msgsReceived = new ArrayList<>();
private final AtomicLong requested = new AtomicLong();
protected MockBackpressureManagingHandler(String thisHandlerName) {
super(thisHandlerName);
}
@Override
protected void newMessage(ChannelHandlerContext ctx, Object msg) {
requested.decrementAndGet();
msgsReceived.add(msg);
}
@Override
protected boolean shouldReadMore(ChannelHandlerContext ctx) {
return requested.get() > 0;
}
public List<Object> getMsgsReceived() {
return msgsReceived;
}
public void reset() {
msgsReceived.clear();
requested.set(0);
}
public void incrementRequested(long requested) {
this.requested.addAndGet(requested);
}
}
private static class BufferingHandler extends ChannelOutboundHandlerAdapter {
private final List<Object> buffer = new ArrayList<>();
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
buffer.add(msg);
}
}
}