/* * Copyright 2016 The Netty Project * * The Netty Project licenses this file to you 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.netty.handler.flush; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.embedded.EmbeddedChannel; import org.junit.Test; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.*; public class FlushConsolidationHandlerTest { private static final int EXPLICIT_FLUSH_AFTER_FLUSHES = 3; @Test public void testFlushViaScheduledTask() { final AtomicInteger flushCount = new AtomicInteger(); EmbeddedChannel channel = newChannel(flushCount, true); // Flushes should not go through immediately, as they're scheduled as an async task channel.flush(); assertEquals(0, flushCount.get()); channel.flush(); assertEquals(0, flushCount.get()); // Trigger the execution of the async task channel.runPendingTasks(); assertEquals(1, flushCount.get()); assertFalse(channel.finish()); } @Test public void testFlushViaThresholdOutsideOfReadLoop() { final AtomicInteger flushCount = new AtomicInteger(); EmbeddedChannel channel = newChannel(flushCount, true); // After a given threshold, the async task should be bypassed and a flush should be triggered immediately for (int i = 0; i < EXPLICIT_FLUSH_AFTER_FLUSHES; i++) { channel.flush(); } assertEquals(1, flushCount.get()); assertFalse(channel.finish()); } @Test public void testImmediateFlushOutsideOfReadLoop() { final AtomicInteger flushCount = new AtomicInteger(); EmbeddedChannel channel = newChannel(flushCount, false); channel.flush(); assertEquals(1, flushCount.get()); assertFalse(channel.finish()); } @Test public void testFlushViaReadComplete() { final AtomicInteger flushCount = new AtomicInteger(); EmbeddedChannel channel = newChannel(flushCount, false); // Flush should go through as there is no read loop in progress. channel.flush(); channel.runPendingTasks(); assertEquals(1, flushCount.get()); // Simulate read loop; channel.pipeline().fireChannelRead(1L); assertEquals(1, flushCount.get()); channel.pipeline().fireChannelRead(2L); assertEquals(1, flushCount.get()); assertNull(channel.readOutbound()); channel.pipeline().fireChannelReadComplete(); assertEquals(2, flushCount.get()); // Now flush again as the read loop is complete. channel.flush(); channel.runPendingTasks(); assertEquals(3, flushCount.get()); assertEquals(1L, channel.readOutbound()); assertEquals(2L, channel.readOutbound()); assertNull(channel.readOutbound()); assertFalse(channel.finish()); } @Test public void testFlushViaClose() { final AtomicInteger flushCount = new AtomicInteger(); EmbeddedChannel channel = newChannel(flushCount, false); // Simulate read loop; channel.pipeline().fireChannelRead(1L); assertEquals(0, flushCount.get()); assertNull(channel.readOutbound()); channel.close(); assertEquals(1, flushCount.get()); assertEquals(1L, channel.readOutbound()); assertNull(channel.readOutbound()); assertFalse(channel.finish()); } @Test public void testFlushViaDisconnect() { final AtomicInteger flushCount = new AtomicInteger(); EmbeddedChannel channel = newChannel(flushCount, false); // Simulate read loop; channel.pipeline().fireChannelRead(1L); assertEquals(0, flushCount.get()); assertNull(channel.readOutbound()); channel.disconnect(); assertEquals(1, flushCount.get()); assertEquals(1L, channel.readOutbound()); assertNull(channel.readOutbound()); assertFalse(channel.finish()); } @Test(expected = IllegalStateException.class) public void testFlushViaException() { final AtomicInteger flushCount = new AtomicInteger(); EmbeddedChannel channel = newChannel(flushCount, false); // Simulate read loop; channel.pipeline().fireChannelRead(1L); assertEquals(0, flushCount.get()); assertNull(channel.readOutbound()); channel.pipeline().fireExceptionCaught(new IllegalStateException()); assertEquals(1, flushCount.get()); assertEquals(1L, channel.readOutbound()); assertNull(channel.readOutbound()); channel.finish(); } @Test public void testFlushViaRemoval() { final AtomicInteger flushCount = new AtomicInteger(); EmbeddedChannel channel = newChannel(flushCount, false); // Simulate read loop; channel.pipeline().fireChannelRead(1L); assertEquals(0, flushCount.get()); assertNull(channel.readOutbound()); channel.pipeline().remove(FlushConsolidationHandler.class); assertEquals(1, flushCount.get()); assertEquals(1L, channel.readOutbound()); assertNull(channel.readOutbound()); assertFalse(channel.finish()); } private static EmbeddedChannel newChannel(final AtomicInteger flushCount, boolean consolidateWhenNoReadInProgress) { return new EmbeddedChannel( new ChannelOutboundHandlerAdapter() { @Override public void flush(ChannelHandlerContext ctx) throws Exception { flushCount.incrementAndGet(); ctx.flush(); } }, new FlushConsolidationHandler(EXPLICIT_FLUSH_AFTER_FLUSHES, consolidateWhenNoReadInProgress), new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.writeAndFlush(msg); } }); } }