private static void assertQueueEmpty(PendingWriteQueue queue) {
   Assert.assertTrue(queue.isEmpty());
   Assert.assertEquals(0, queue.size());
   Assert.assertNull(queue.current());
   Assert.assertNull(queue.removeAndWrite());
   Assert.assertNull(queue.removeAndWriteAll());
 }
  @Test
  public void testRemoveAndWriteAllReentrance() {
    EmbeddedChannel channel = new EmbeddedChannel(new ChannelInboundHandlerAdapter());
    final PendingWriteQueue queue = new PendingWriteQueue(channel.pipeline().firstContext());

    ChannelPromise promise = channel.newPromise();
    promise.addListener(
        new ChannelFutureListener() {
          @Override
          public void operationComplete(ChannelFuture future) throws Exception {
            queue.removeAndWriteAll();
          }
        });
    queue.add(1L, promise);

    ChannelPromise promise2 = channel.newPromise();
    queue.add(2L, promise2);
    queue.removeAndWriteAll();
    channel.flush();
    assertTrue(promise.isSuccess());
    assertTrue(promise2.isSuccess());
    assertTrue(channel.finish());

    assertEquals(1L, channel.readOutbound());
    assertEquals(2L, channel.readOutbound());
    assertNull(channel.readOutbound());
    assertNull(channel.readInbound());
  }
 @Override
 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
     throws Exception {
   queue.add(msg, promise);
   Assert.assertFalse(queue.isEmpty());
   Assert.assertEquals(++expectedSize, queue.size());
   Assert.assertNotNull(queue.current());
 }
  @Test
  public void shouldFireChannelWritabilityChangedAfterRemoval() {
    final AtomicReference<ChannelHandlerContext> ctxRef =
        new AtomicReference<ChannelHandlerContext>();
    final AtomicReference<PendingWriteQueue> queueRef = new AtomicReference<PendingWriteQueue>();
    final ByteBuf msg = Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII);

    final EmbeddedChannel channel =
        new EmbeddedChannel(
            new ChannelHandlerAdapter() {
              @Override
              public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
                ctxRef.set(ctx);
                queueRef.set(new PendingWriteQueue(ctx));
              }

              @Override
              public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
                final PendingWriteQueue queue = queueRef.get();

                final ByteBuf msg = (ByteBuf) queue.current();
                if (msg == null) {
                  return;
                }

                assertThat(msg.refCnt(), is(1));

                // This call will trigger another channelWritabilityChanged() event because the
                // number of
                // pending bytes will go below the low watermark.
                //
                // If PendingWriteQueue.remove() did not remove the current entry before triggering
                // channelWritabilityChanged() event, we will end up with attempting to remove the
                // same
                // element twice, resulting in the double release.
                queue.remove();

                assertThat(msg.refCnt(), is(0));
              }
            });

    channel.config().setWriteBufferLowWaterMark(1);
    channel.config().setWriteBufferHighWaterMark(3);

    final PendingWriteQueue queue = queueRef.get();

    // Trigger channelWritabilityChanged() by adding a message that's larger than the high
    // watermark.
    queue.add(msg, channel.newPromise());

    channel.finish();

    assertThat(msg.refCnt(), is(0));
  }
  // See https://github.com/netty/netty/issues/3967
  @Test
  public void testCloseChannelOnCreation() {
    EmbeddedChannel channel = new EmbeddedChannel();
    channel.close().syncUninterruptibly();

    final PendingWriteQueue queue = new PendingWriteQueue(channel.pipeline().firstContext());

    IllegalStateException ex = new IllegalStateException();
    ChannelPromise promise = channel.newPromise();
    queue.add(1L, promise);
    queue.removeAndFailAll(ex);
    assertSame(ex, promise.cause());
  }