2012-06-04 22:31:44 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2012 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.
|
|
|
|
*/
|
2013-02-01 09:02:26 +01:00
|
|
|
package io.netty.channel.nio;
|
2012-05-27 14:07:23 +02:00
|
|
|
|
2012-06-10 04:08:43 +02:00
|
|
|
import io.netty.buffer.ByteBuf;
|
2013-06-25 11:08:39 +02:00
|
|
|
import io.netty.buffer.ByteBufAllocator;
|
2012-05-27 14:07:23 +02:00
|
|
|
import io.netty.channel.Channel;
|
Revamp the core API to reduce memory footprint and consumption
The API changes made so far turned out to increase the memory footprint
and consumption while our intention was actually decreasing them.
Memory consumption issue:
When there are many connections which does not exchange data frequently,
the old Netty 4 API spent a lot more memory than 3 because it always
allocates per-handler buffer for each connection unless otherwise
explicitly stated by a user. In a usual real world load, a client
doesn't always send requests without pausing, so the idea of having a
buffer whose life cycle if bound to the life cycle of a connection
didn't work as expected.
Memory footprint issue:
The old Netty 4 API decreased overall memory footprint by a great deal
in many cases. It was mainly because the old Netty 4 API did not
allocate a new buffer and event object for each read. Instead, it
created a new buffer for each handler in a pipeline. This works pretty
well as long as the number of handlers in a pipeline is only a few.
However, for a highly modular application with many handlers which
handles connections which lasts for relatively short period, it actually
makes the memory footprint issue much worse.
Changes:
All in all, this is about retaining all the good changes we made in 4 so
far such as better thread model and going back to the way how we dealt
with message events in 3.
To fix the memory consumption/footprint issue mentioned above, we made a
hard decision to break the backward compatibility again with the
following changes:
- Remove MessageBuf
- Merge Buf into ByteBuf
- Merge ChannelInboundByte/MessageHandler and ChannelStateHandler into ChannelInboundHandler
- Similar changes were made to the adapter classes
- Merge ChannelOutboundByte/MessageHandler and ChannelOperationHandler into ChannelOutboundHandler
- Similar changes were made to the adapter classes
- Introduce MessageList which is similar to `MessageEvent` in Netty 3
- Replace inboundBufferUpdated(ctx) with messageReceived(ctx, MessageList)
- Replace flush(ctx, promise) with write(ctx, MessageList, promise)
- Remove ByteToByteEncoder/Decoder/Codec
- Replaced by MessageToByteEncoder<ByteBuf>, ByteToMessageDecoder<ByteBuf>, and ByteMessageCodec<ByteBuf>
- Merge EmbeddedByteChannel and EmbeddedMessageChannel into EmbeddedChannel
- Add SimpleChannelInboundHandler which is sometimes more useful than
ChannelInboundHandlerAdapter
- Bring back Channel.isWritable() from Netty 3
- Add ChannelInboundHandler.channelWritabilityChanges() event
- Add RecvByteBufAllocator configuration property
- Similar to ReceiveBufferSizePredictor in Netty 3
- Some existing configuration properties such as
DatagramChannelConfig.receivePacketSize is gone now.
- Remove suspend/resumeIntermediaryDeallocation() in ByteBuf
This change would have been impossible without @normanmaurer's help. He
fixed, ported, and improved many parts of the changes.
2013-05-28 13:40:19 +02:00
|
|
|
import io.netty.channel.ChannelConfig;
|
2016-04-28 19:56:49 +02:00
|
|
|
import io.netty.channel.ChannelFuture;
|
2016-07-15 19:44:32 +02:00
|
|
|
import io.netty.channel.ChannelMetadata;
|
2013-07-18 13:59:14 +02:00
|
|
|
import io.netty.channel.ChannelOutboundBuffer;
|
2012-06-11 16:04:04 +02:00
|
|
|
import io.netty.channel.ChannelPipeline;
|
Revamp the core API to reduce memory footprint and consumption
The API changes made so far turned out to increase the memory footprint
and consumption while our intention was actually decreasing them.
Memory consumption issue:
When there are many connections which does not exchange data frequently,
the old Netty 4 API spent a lot more memory than 3 because it always
allocates per-handler buffer for each connection unless otherwise
explicitly stated by a user. In a usual real world load, a client
doesn't always send requests without pausing, so the idea of having a
buffer whose life cycle if bound to the life cycle of a connection
didn't work as expected.
Memory footprint issue:
The old Netty 4 API decreased overall memory footprint by a great deal
in many cases. It was mainly because the old Netty 4 API did not
allocate a new buffer and event object for each read. Instead, it
created a new buffer for each handler in a pipeline. This works pretty
well as long as the number of handlers in a pipeline is only a few.
However, for a highly modular application with many handlers which
handles connections which lasts for relatively short period, it actually
makes the memory footprint issue much worse.
Changes:
All in all, this is about retaining all the good changes we made in 4 so
far such as better thread model and going back to the way how we dealt
with message events in 3.
To fix the memory consumption/footprint issue mentioned above, we made a
hard decision to break the backward compatibility again with the
following changes:
- Remove MessageBuf
- Merge Buf into ByteBuf
- Merge ChannelInboundByte/MessageHandler and ChannelStateHandler into ChannelInboundHandler
- Similar changes were made to the adapter classes
- Merge ChannelOutboundByte/MessageHandler and ChannelOperationHandler into ChannelOutboundHandler
- Similar changes were made to the adapter classes
- Introduce MessageList which is similar to `MessageEvent` in Netty 3
- Replace inboundBufferUpdated(ctx) with messageReceived(ctx, MessageList)
- Replace flush(ctx, promise) with write(ctx, MessageList, promise)
- Remove ByteToByteEncoder/Decoder/Codec
- Replaced by MessageToByteEncoder<ByteBuf>, ByteToMessageDecoder<ByteBuf>, and ByteMessageCodec<ByteBuf>
- Merge EmbeddedByteChannel and EmbeddedMessageChannel into EmbeddedChannel
- Add SimpleChannelInboundHandler which is sometimes more useful than
ChannelInboundHandlerAdapter
- Bring back Channel.isWritable() from Netty 3
- Add ChannelInboundHandler.channelWritabilityChanges() event
- Add RecvByteBufAllocator configuration property
- Similar to ReceiveBufferSizePredictor in Netty 3
- Some existing configuration properties such as
DatagramChannelConfig.receivePacketSize is gone now.
- Remove suspend/resumeIntermediaryDeallocation() in ByteBuf
This change would have been impossible without @normanmaurer's help. He
fixed, ported, and improved many parts of the changes.
2013-05-28 13:40:19 +02:00
|
|
|
import io.netty.channel.FileRegion;
|
|
|
|
import io.netty.channel.RecvByteBufAllocator;
|
2017-12-08 01:00:52 +01:00
|
|
|
import io.netty.channel.internal.ChannelUtils;
|
2012-12-31 16:35:44 +01:00
|
|
|
import io.netty.channel.socket.ChannelInputShutdownEvent;
|
2017-02-02 19:15:10 +01:00
|
|
|
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
|
2018-03-28 20:02:57 +02:00
|
|
|
import io.netty.channel.socket.SocketChannelConfig;
|
2013-06-10 11:19:58 +02:00
|
|
|
import io.netty.util.internal.StringUtil;
|
2013-04-15 08:26:20 +02:00
|
|
|
|
2012-05-27 14:07:23 +02:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.nio.channels.SelectableChannel;
|
|
|
|
import java.nio.channels.SelectionKey;
|
|
|
|
|
2017-12-08 01:00:52 +01:00
|
|
|
import static io.netty.channel.internal.ChannelUtils.WRITE_STATUS_SNDBUF_FULL;
|
|
|
|
|
2012-12-21 17:59:37 +01:00
|
|
|
/**
|
2013-02-07 15:34:38 +01:00
|
|
|
* {@link AbstractNioChannel} base class for {@link Channel}s that operate on bytes.
|
2012-12-21 17:59:37 +01:00
|
|
|
*/
|
2012-12-30 12:48:32 +01:00
|
|
|
public abstract class AbstractNioByteChannel extends AbstractNioChannel {
|
2016-07-15 19:44:32 +02:00
|
|
|
private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
|
2014-08-05 14:24:49 +02:00
|
|
|
private static final String EXPECTED_TYPES =
|
|
|
|
" (expected: " + StringUtil.simpleClassName(ByteBuf.class) + ", " +
|
|
|
|
StringUtil.simpleClassName(FileRegion.class) + ')';
|
|
|
|
|
2018-02-18 20:49:08 +01:00
|
|
|
private final Runnable flushTask = new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2018-02-22 07:42:49 +01:00
|
|
|
// Calling flush0 directly to ensure we not try to flush messages that were added via write(...) in the
|
|
|
|
// meantime.
|
|
|
|
((AbstractNioUnsafe) unsafe()).flush0();
|
2018-02-18 20:49:08 +01:00
|
|
|
}
|
|
|
|
};
|
2018-03-28 20:02:57 +02:00
|
|
|
private boolean inputClosedSeenErrorOnRead;
|
2012-05-27 14:07:23 +02:00
|
|
|
|
2012-12-21 17:59:37 +01:00
|
|
|
/**
|
|
|
|
* Create a new instance
|
|
|
|
*
|
|
|
|
* @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
|
|
|
|
* @param ch the underlying {@link SelectableChannel} on which it operates
|
|
|
|
*/
|
2013-07-09 07:49:06 +02:00
|
|
|
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
|
|
|
|
super(parent, ch, SelectionKey.OP_READ);
|
2012-05-27 14:07:23 +02:00
|
|
|
}
|
|
|
|
|
2016-04-28 19:56:49 +02:00
|
|
|
/**
|
|
|
|
* Shutdown the input side of the channel.
|
|
|
|
*/
|
|
|
|
protected abstract ChannelFuture shutdownInput();
|
|
|
|
|
2017-02-02 19:15:10 +01:00
|
|
|
protected boolean isInputShutdown0() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-05-27 14:07:23 +02:00
|
|
|
@Override
|
2012-10-24 18:27:26 +02:00
|
|
|
protected AbstractNioUnsafe newUnsafe() {
|
2012-08-28 08:55:51 +02:00
|
|
|
return new NioByteUnsafe();
|
|
|
|
}
|
2012-05-27 14:07:23 +02:00
|
|
|
|
2016-07-15 19:44:32 +02:00
|
|
|
@Override
|
|
|
|
public ChannelMetadata metadata() {
|
|
|
|
return METADATA;
|
|
|
|
}
|
|
|
|
|
2018-03-28 20:02:57 +02:00
|
|
|
final boolean shouldBreakReadReady(ChannelConfig config) {
|
|
|
|
return isInputShutdown0() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config));
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean isAllowHalfClosure(ChannelConfig config) {
|
|
|
|
return config instanceof SocketChannelConfig &&
|
|
|
|
((SocketChannelConfig) config).isAllowHalfClosure();
|
|
|
|
}
|
|
|
|
|
2015-02-06 12:14:59 +01:00
|
|
|
protected class NioByteUnsafe extends AbstractNioUnsafe {
|
Revamp the core API to reduce memory footprint and consumption
The API changes made so far turned out to increase the memory footprint
and consumption while our intention was actually decreasing them.
Memory consumption issue:
When there are many connections which does not exchange data frequently,
the old Netty 4 API spent a lot more memory than 3 because it always
allocates per-handler buffer for each connection unless otherwise
explicitly stated by a user. In a usual real world load, a client
doesn't always send requests without pausing, so the idea of having a
buffer whose life cycle if bound to the life cycle of a connection
didn't work as expected.
Memory footprint issue:
The old Netty 4 API decreased overall memory footprint by a great deal
in many cases. It was mainly because the old Netty 4 API did not
allocate a new buffer and event object for each read. Instead, it
created a new buffer for each handler in a pipeline. This works pretty
well as long as the number of handlers in a pipeline is only a few.
However, for a highly modular application with many handlers which
handles connections which lasts for relatively short period, it actually
makes the memory footprint issue much worse.
Changes:
All in all, this is about retaining all the good changes we made in 4 so
far such as better thread model and going back to the way how we dealt
with message events in 3.
To fix the memory consumption/footprint issue mentioned above, we made a
hard decision to break the backward compatibility again with the
following changes:
- Remove MessageBuf
- Merge Buf into ByteBuf
- Merge ChannelInboundByte/MessageHandler and ChannelStateHandler into ChannelInboundHandler
- Similar changes were made to the adapter classes
- Merge ChannelOutboundByte/MessageHandler and ChannelOperationHandler into ChannelOutboundHandler
- Similar changes were made to the adapter classes
- Introduce MessageList which is similar to `MessageEvent` in Netty 3
- Replace inboundBufferUpdated(ctx) with messageReceived(ctx, MessageList)
- Replace flush(ctx, promise) with write(ctx, MessageList, promise)
- Remove ByteToByteEncoder/Decoder/Codec
- Replaced by MessageToByteEncoder<ByteBuf>, ByteToMessageDecoder<ByteBuf>, and ByteMessageCodec<ByteBuf>
- Merge EmbeddedByteChannel and EmbeddedMessageChannel into EmbeddedChannel
- Add SimpleChannelInboundHandler which is sometimes more useful than
ChannelInboundHandlerAdapter
- Bring back Channel.isWritable() from Netty 3
- Add ChannelInboundHandler.channelWritabilityChanges() event
- Add RecvByteBufAllocator configuration property
- Similar to ReceiveBufferSizePredictor in Netty 3
- Some existing configuration properties such as
DatagramChannelConfig.receivePacketSize is gone now.
- Remove suspend/resumeIntermediaryDeallocation() in ByteBuf
This change would have been impossible without @normanmaurer's help. He
fixed, ported, and improved many parts of the changes.
2013-05-28 13:40:19 +02:00
|
|
|
|
2013-10-23 11:10:12 +02:00
|
|
|
private void closeOnRead(ChannelPipeline pipeline) {
|
2017-02-02 19:15:10 +01:00
|
|
|
if (!isInputShutdown0()) {
|
2018-03-28 20:02:57 +02:00
|
|
|
if (isAllowHalfClosure(config())) {
|
2016-04-28 19:56:49 +02:00
|
|
|
shutdownInput();
|
2013-10-23 11:10:12 +02:00
|
|
|
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
|
|
|
|
} else {
|
|
|
|
close(voidPromise());
|
2013-05-24 12:50:04 +02:00
|
|
|
}
|
2017-02-02 19:15:10 +01:00
|
|
|
} else {
|
2018-03-28 20:02:57 +02:00
|
|
|
inputClosedSeenErrorOnRead = true;
|
2017-02-02 19:15:10 +01:00
|
|
|
pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
|
2013-02-07 15:34:38 +01:00
|
|
|
}
|
2013-10-23 11:10:12 +02:00
|
|
|
}
|
2012-05-27 14:07:23 +02:00
|
|
|
|
2015-06-05 03:36:55 +02:00
|
|
|
private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause, boolean close,
|
|
|
|
RecvByteBufAllocator.Handle allocHandle) {
|
2013-10-23 11:10:12 +02:00
|
|
|
if (byteBuf != null) {
|
|
|
|
if (byteBuf.isReadable()) {
|
2016-04-05 19:13:18 +02:00
|
|
|
readPending = false;
|
2013-10-23 11:10:12 +02:00
|
|
|
pipeline.fireChannelRead(byteBuf);
|
|
|
|
} else {
|
|
|
|
byteBuf.release();
|
|
|
|
}
|
|
|
|
}
|
2015-06-05 03:36:55 +02:00
|
|
|
allocHandle.readComplete();
|
2013-10-23 11:10:12 +02:00
|
|
|
pipeline.fireChannelReadComplete();
|
2013-11-05 09:23:22 +01:00
|
|
|
pipeline.fireExceptionCaught(cause);
|
2013-10-23 11:10:12 +02:00
|
|
|
if (close || cause instanceof IOException) {
|
|
|
|
closeOnRead(pipeline);
|
|
|
|
}
|
|
|
|
}
|
Revamp the core API to reduce memory footprint and consumption
The API changes made so far turned out to increase the memory footprint
and consumption while our intention was actually decreasing them.
Memory consumption issue:
When there are many connections which does not exchange data frequently,
the old Netty 4 API spent a lot more memory than 3 because it always
allocates per-handler buffer for each connection unless otherwise
explicitly stated by a user. In a usual real world load, a client
doesn't always send requests without pausing, so the idea of having a
buffer whose life cycle if bound to the life cycle of a connection
didn't work as expected.
Memory footprint issue:
The old Netty 4 API decreased overall memory footprint by a great deal
in many cases. It was mainly because the old Netty 4 API did not
allocate a new buffer and event object for each read. Instead, it
created a new buffer for each handler in a pipeline. This works pretty
well as long as the number of handlers in a pipeline is only a few.
However, for a highly modular application with many handlers which
handles connections which lasts for relatively short period, it actually
makes the memory footprint issue much worse.
Changes:
All in all, this is about retaining all the good changes we made in 4 so
far such as better thread model and going back to the way how we dealt
with message events in 3.
To fix the memory consumption/footprint issue mentioned above, we made a
hard decision to break the backward compatibility again with the
following changes:
- Remove MessageBuf
- Merge Buf into ByteBuf
- Merge ChannelInboundByte/MessageHandler and ChannelStateHandler into ChannelInboundHandler
- Similar changes were made to the adapter classes
- Merge ChannelOutboundByte/MessageHandler and ChannelOperationHandler into ChannelOutboundHandler
- Similar changes were made to the adapter classes
- Introduce MessageList which is similar to `MessageEvent` in Netty 3
- Replace inboundBufferUpdated(ctx) with messageReceived(ctx, MessageList)
- Replace flush(ctx, promise) with write(ctx, MessageList, promise)
- Remove ByteToByteEncoder/Decoder/Codec
- Replaced by MessageToByteEncoder<ByteBuf>, ByteToMessageDecoder<ByteBuf>, and ByteMessageCodec<ByteBuf>
- Merge EmbeddedByteChannel and EmbeddedMessageChannel into EmbeddedChannel
- Add SimpleChannelInboundHandler which is sometimes more useful than
ChannelInboundHandlerAdapter
- Bring back Channel.isWritable() from Netty 3
- Add ChannelInboundHandler.channelWritabilityChanges() event
- Add RecvByteBufAllocator configuration property
- Similar to ReceiveBufferSizePredictor in Netty 3
- Some existing configuration properties such as
DatagramChannelConfig.receivePacketSize is gone now.
- Remove suspend/resumeIntermediaryDeallocation() in ByteBuf
This change would have been impossible without @normanmaurer's help. He
fixed, ported, and improved many parts of the changes.
2013-05-28 13:40:19 +02:00
|
|
|
|
2013-10-23 11:10:12 +02:00
|
|
|
@Override
|
2015-02-06 12:14:59 +01:00
|
|
|
public final void read() {
|
2013-10-23 11:10:12 +02:00
|
|
|
final ChannelConfig config = config();
|
2018-03-28 20:02:57 +02:00
|
|
|
if (shouldBreakReadReady(config)) {
|
|
|
|
clearReadPending();
|
|
|
|
return;
|
|
|
|
}
|
2013-10-23 11:10:12 +02:00
|
|
|
final ChannelPipeline pipeline = pipeline();
|
|
|
|
final ByteBufAllocator allocator = config.getAllocator();
|
2015-06-05 03:36:55 +02:00
|
|
|
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
|
|
|
|
allocHandle.reset(config);
|
Revamp the core API to reduce memory footprint and consumption
The API changes made so far turned out to increase the memory footprint
and consumption while our intention was actually decreasing them.
Memory consumption issue:
When there are many connections which does not exchange data frequently,
the old Netty 4 API spent a lot more memory than 3 because it always
allocates per-handler buffer for each connection unless otherwise
explicitly stated by a user. In a usual real world load, a client
doesn't always send requests without pausing, so the idea of having a
buffer whose life cycle if bound to the life cycle of a connection
didn't work as expected.
Memory footprint issue:
The old Netty 4 API decreased overall memory footprint by a great deal
in many cases. It was mainly because the old Netty 4 API did not
allocate a new buffer and event object for each read. Instead, it
created a new buffer for each handler in a pipeline. This works pretty
well as long as the number of handlers in a pipeline is only a few.
However, for a highly modular application with many handlers which
handles connections which lasts for relatively short period, it actually
makes the memory footprint issue much worse.
Changes:
All in all, this is about retaining all the good changes we made in 4 so
far such as better thread model and going back to the way how we dealt
with message events in 3.
To fix the memory consumption/footprint issue mentioned above, we made a
hard decision to break the backward compatibility again with the
following changes:
- Remove MessageBuf
- Merge Buf into ByteBuf
- Merge ChannelInboundByte/MessageHandler and ChannelStateHandler into ChannelInboundHandler
- Similar changes were made to the adapter classes
- Merge ChannelOutboundByte/MessageHandler and ChannelOperationHandler into ChannelOutboundHandler
- Similar changes were made to the adapter classes
- Introduce MessageList which is similar to `MessageEvent` in Netty 3
- Replace inboundBufferUpdated(ctx) with messageReceived(ctx, MessageList)
- Replace flush(ctx, promise) with write(ctx, MessageList, promise)
- Remove ByteToByteEncoder/Decoder/Codec
- Replaced by MessageToByteEncoder<ByteBuf>, ByteToMessageDecoder<ByteBuf>, and ByteMessageCodec<ByteBuf>
- Merge EmbeddedByteChannel and EmbeddedMessageChannel into EmbeddedChannel
- Add SimpleChannelInboundHandler which is sometimes more useful than
ChannelInboundHandlerAdapter
- Bring back Channel.isWritable() from Netty 3
- Add ChannelInboundHandler.channelWritabilityChanges() event
- Add RecvByteBufAllocator configuration property
- Similar to ReceiveBufferSizePredictor in Netty 3
- Some existing configuration properties such as
DatagramChannelConfig.receivePacketSize is gone now.
- Remove suspend/resumeIntermediaryDeallocation() in ByteBuf
This change would have been impossible without @normanmaurer's help. He
fixed, ported, and improved many parts of the changes.
2013-05-28 13:40:19 +02:00
|
|
|
|
2013-06-25 11:08:39 +02:00
|
|
|
ByteBuf byteBuf = null;
|
2016-03-24 20:08:37 +01:00
|
|
|
boolean close = false;
|
2012-05-27 14:07:23 +02:00
|
|
|
try {
|
2013-10-23 11:10:12 +02:00
|
|
|
do {
|
2014-05-10 15:21:25 +02:00
|
|
|
byteBuf = allocHandle.allocate(allocator);
|
2015-06-05 03:36:55 +02:00
|
|
|
allocHandle.lastBytesRead(doReadBytes(byteBuf));
|
|
|
|
if (allocHandle.lastBytesRead() <= 0) {
|
|
|
|
// nothing was read. release the buffer.
|
2013-11-07 07:12:19 +01:00
|
|
|
byteBuf.release();
|
2015-01-29 09:26:13 +01:00
|
|
|
byteBuf = null;
|
2016-03-24 20:08:37 +01:00
|
|
|
close = allocHandle.lastBytesRead() < 0;
|
2017-09-28 08:52:04 +02:00
|
|
|
if (close) {
|
|
|
|
// There is nothing left to read as we received an EOF.
|
|
|
|
readPending = false;
|
|
|
|
}
|
2013-06-25 11:08:39 +02:00
|
|
|
break;
|
|
|
|
}
|
2015-06-05 03:36:55 +02:00
|
|
|
|
|
|
|
allocHandle.incMessagesRead(1);
|
2016-04-05 19:13:18 +02:00
|
|
|
readPending = false;
|
2013-07-09 16:09:28 +02:00
|
|
|
pipeline.fireChannelRead(byteBuf);
|
2013-06-25 11:08:39 +02:00
|
|
|
byteBuf = null;
|
2015-06-05 03:36:55 +02:00
|
|
|
} while (allocHandle.continueReading());
|
2013-12-21 11:56:36 +01:00
|
|
|
|
2015-06-05 03:36:55 +02:00
|
|
|
allocHandle.readComplete();
|
2013-07-09 16:09:28 +02:00
|
|
|
pipeline.fireChannelReadComplete();
|
2013-06-25 11:08:39 +02:00
|
|
|
|
2016-03-24 20:08:37 +01:00
|
|
|
if (close) {
|
2013-10-23 11:10:12 +02:00
|
|
|
closeOnRead(pipeline);
|
2012-05-27 14:07:23 +02:00
|
|
|
}
|
2013-10-23 11:10:12 +02:00
|
|
|
} catch (Throwable t) {
|
2016-03-24 20:08:37 +01:00
|
|
|
handleReadException(pipeline, byteBuf, t, close, allocHandle);
|
2014-02-21 21:42:13 +01:00
|
|
|
} finally {
|
|
|
|
// Check if there is a readPending which was not processed yet.
|
|
|
|
// This could be for two reasons:
|
|
|
|
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
|
|
|
|
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
|
|
|
|
//
|
|
|
|
// See https://github.com/netty/netty/issues/2254
|
2016-04-05 19:13:18 +02:00
|
|
|
if (!readPending && !config.isAutoRead()) {
|
2014-02-21 21:42:13 +01:00
|
|
|
removeReadOp();
|
|
|
|
}
|
2012-05-27 14:07:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-08 01:00:52 +01:00
|
|
|
/**
|
|
|
|
* Write objects to the OS.
|
|
|
|
* @param in the collection which contains objects to write.
|
|
|
|
* @return The value that should be decremented from the write quantum which starts at
|
|
|
|
* {@link ChannelConfig#getWriteSpinCount()}. The typical use cases are as follows:
|
|
|
|
* <ul>
|
|
|
|
* <li>0 - if no write was attempted. This is appropriate if an empty {@link ByteBuf} (or other empty content)
|
|
|
|
* is encountered</li>
|
|
|
|
* <li>1 - if a single call to write data was made to the OS</li>
|
|
|
|
* <li>{@link ChannelUtils#WRITE_STATUS_SNDBUF_FULL} - if an attempt to write data was made to the OS, but no
|
|
|
|
* data was accepted</li>
|
|
|
|
* </ul>
|
|
|
|
* @throws Exception if an I/O exception occurs during write.
|
|
|
|
*/
|
|
|
|
protected final int doWrite0(ChannelOutboundBuffer in) throws Exception {
|
|
|
|
Object msg = in.current();
|
|
|
|
if (msg == null) {
|
|
|
|
// Directly return here so incompleteWrite(...) is not called.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return doWriteInternal(in, in.current());
|
|
|
|
}
|
2013-07-25 09:28:54 +02:00
|
|
|
|
2017-12-08 01:00:52 +01:00
|
|
|
private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
|
|
|
|
if (msg instanceof ByteBuf) {
|
|
|
|
ByteBuf buf = (ByteBuf) msg;
|
|
|
|
if (!buf.isReadable()) {
|
|
|
|
in.remove();
|
|
|
|
return 0;
|
2013-06-10 20:54:17 +02:00
|
|
|
}
|
2013-07-18 13:59:14 +02:00
|
|
|
|
2017-12-08 01:00:52 +01:00
|
|
|
final int localFlushedAmount = doWriteBytes(buf);
|
|
|
|
if (localFlushedAmount > 0) {
|
|
|
|
in.progress(localFlushedAmount);
|
|
|
|
if (!buf.isReadable()) {
|
2013-07-18 13:59:14 +02:00
|
|
|
in.remove();
|
2014-10-17 09:04:37 +02:00
|
|
|
}
|
2017-12-08 01:00:52 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} else if (msg instanceof FileRegion) {
|
|
|
|
FileRegion region = (FileRegion) msg;
|
|
|
|
if (region.transferred() >= region.count()) {
|
|
|
|
in.remove();
|
|
|
|
return 0;
|
|
|
|
}
|
2013-09-05 11:36:48 +02:00
|
|
|
|
2017-12-08 01:00:52 +01:00
|
|
|
long localFlushedAmount = doWriteFileRegion(region);
|
|
|
|
if (localFlushedAmount > 0) {
|
|
|
|
in.progress(localFlushedAmount);
|
|
|
|
if (region.transferred() >= region.count()) {
|
2013-07-18 13:59:14 +02:00
|
|
|
in.remove();
|
2013-06-11 09:12:43 +02:00
|
|
|
}
|
2017-12-08 01:00:52 +01:00
|
|
|
return 1;
|
2012-10-24 18:27:26 +02:00
|
|
|
}
|
2017-12-08 01:00:52 +01:00
|
|
|
} else {
|
|
|
|
// Should not reach here.
|
|
|
|
throw new Error();
|
2012-10-24 18:27:26 +02:00
|
|
|
}
|
2017-12-08 01:00:52 +01:00
|
|
|
return WRITE_STATUS_SNDBUF_FULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
|
|
|
|
int writeSpinCount = config().getWriteSpinCount();
|
|
|
|
do {
|
|
|
|
Object msg = in.current();
|
|
|
|
if (msg == null) {
|
|
|
|
// Wrote all messages.
|
|
|
|
clearOpWrite();
|
|
|
|
// Directly return here so incompleteWrite(...) is not called.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
writeSpinCount -= doWriteInternal(in, msg);
|
|
|
|
} while (writeSpinCount > 0);
|
|
|
|
|
|
|
|
incompleteWrite(writeSpinCount < 0);
|
2012-10-24 18:27:26 +02:00
|
|
|
}
|
|
|
|
|
2014-08-05 14:24:49 +02:00
|
|
|
@Override
|
|
|
|
protected final Object filterOutboundMessage(Object msg) {
|
|
|
|
if (msg instanceof ByteBuf) {
|
|
|
|
ByteBuf buf = (ByteBuf) msg;
|
|
|
|
if (buf.isDirect()) {
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
return newDirectBuffer(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg instanceof FileRegion) {
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new UnsupportedOperationException(
|
|
|
|
"unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
|
|
|
|
}
|
|
|
|
|
2013-09-13 21:05:08 +02:00
|
|
|
protected final void incompleteWrite(boolean setOpWrite) {
|
|
|
|
// Did not write completely.
|
|
|
|
if (setOpWrite) {
|
|
|
|
setOpWrite();
|
|
|
|
} else {
|
2018-02-18 20:49:08 +01:00
|
|
|
// It is possible that we have set the write OP, woken up by NIO because the socket is writable, and then
|
|
|
|
// use our write quantum. In this case we no longer want to set the write OP because the socket is still
|
|
|
|
// writable (as far as we know). We will find out next time we attempt to write if the socket is writable
|
|
|
|
// and set the write OP if necessary.
|
|
|
|
clearOpWrite();
|
|
|
|
|
2013-09-13 21:05:08 +02:00
|
|
|
// Schedule flush again later so other tasks can be picked up in the meantime
|
|
|
|
eventLoop().execute(flushTask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Revamp the core API to reduce memory footprint and consumption
The API changes made so far turned out to increase the memory footprint
and consumption while our intention was actually decreasing them.
Memory consumption issue:
When there are many connections which does not exchange data frequently,
the old Netty 4 API spent a lot more memory than 3 because it always
allocates per-handler buffer for each connection unless otherwise
explicitly stated by a user. In a usual real world load, a client
doesn't always send requests without pausing, so the idea of having a
buffer whose life cycle if bound to the life cycle of a connection
didn't work as expected.
Memory footprint issue:
The old Netty 4 API decreased overall memory footprint by a great deal
in many cases. It was mainly because the old Netty 4 API did not
allocate a new buffer and event object for each read. Instead, it
created a new buffer for each handler in a pipeline. This works pretty
well as long as the number of handlers in a pipeline is only a few.
However, for a highly modular application with many handlers which
handles connections which lasts for relatively short period, it actually
makes the memory footprint issue much worse.
Changes:
All in all, this is about retaining all the good changes we made in 4 so
far such as better thread model and going back to the way how we dealt
with message events in 3.
To fix the memory consumption/footprint issue mentioned above, we made a
hard decision to break the backward compatibility again with the
following changes:
- Remove MessageBuf
- Merge Buf into ByteBuf
- Merge ChannelInboundByte/MessageHandler and ChannelStateHandler into ChannelInboundHandler
- Similar changes were made to the adapter classes
- Merge ChannelOutboundByte/MessageHandler and ChannelOperationHandler into ChannelOutboundHandler
- Similar changes were made to the adapter classes
- Introduce MessageList which is similar to `MessageEvent` in Netty 3
- Replace inboundBufferUpdated(ctx) with messageReceived(ctx, MessageList)
- Replace flush(ctx, promise) with write(ctx, MessageList, promise)
- Remove ByteToByteEncoder/Decoder/Codec
- Replaced by MessageToByteEncoder<ByteBuf>, ByteToMessageDecoder<ByteBuf>, and ByteMessageCodec<ByteBuf>
- Merge EmbeddedByteChannel and EmbeddedMessageChannel into EmbeddedChannel
- Add SimpleChannelInboundHandler which is sometimes more useful than
ChannelInboundHandlerAdapter
- Bring back Channel.isWritable() from Netty 3
- Add ChannelInboundHandler.channelWritabilityChanges() event
- Add RecvByteBufAllocator configuration property
- Similar to ReceiveBufferSizePredictor in Netty 3
- Some existing configuration properties such as
DatagramChannelConfig.receivePacketSize is gone now.
- Remove suspend/resumeIntermediaryDeallocation() in ByteBuf
This change would have been impossible without @normanmaurer's help. He
fixed, ported, and improved many parts of the changes.
2013-05-28 13:40:19 +02:00
|
|
|
/**
|
|
|
|
* Write a {@link FileRegion}
|
|
|
|
*
|
|
|
|
* @param region the {@link FileRegion} from which the bytes should be written
|
|
|
|
* @return amount the amount of written bytes
|
|
|
|
*/
|
2013-07-18 13:59:14 +02:00
|
|
|
protected abstract long doWriteFileRegion(FileRegion region) throws Exception;
|
Revamp the core API to reduce memory footprint and consumption
The API changes made so far turned out to increase the memory footprint
and consumption while our intention was actually decreasing them.
Memory consumption issue:
When there are many connections which does not exchange data frequently,
the old Netty 4 API spent a lot more memory than 3 because it always
allocates per-handler buffer for each connection unless otherwise
explicitly stated by a user. In a usual real world load, a client
doesn't always send requests without pausing, so the idea of having a
buffer whose life cycle if bound to the life cycle of a connection
didn't work as expected.
Memory footprint issue:
The old Netty 4 API decreased overall memory footprint by a great deal
in many cases. It was mainly because the old Netty 4 API did not
allocate a new buffer and event object for each read. Instead, it
created a new buffer for each handler in a pipeline. This works pretty
well as long as the number of handlers in a pipeline is only a few.
However, for a highly modular application with many handlers which
handles connections which lasts for relatively short period, it actually
makes the memory footprint issue much worse.
Changes:
All in all, this is about retaining all the good changes we made in 4 so
far such as better thread model and going back to the way how we dealt
with message events in 3.
To fix the memory consumption/footprint issue mentioned above, we made a
hard decision to break the backward compatibility again with the
following changes:
- Remove MessageBuf
- Merge Buf into ByteBuf
- Merge ChannelInboundByte/MessageHandler and ChannelStateHandler into ChannelInboundHandler
- Similar changes were made to the adapter classes
- Merge ChannelOutboundByte/MessageHandler and ChannelOperationHandler into ChannelOutboundHandler
- Similar changes were made to the adapter classes
- Introduce MessageList which is similar to `MessageEvent` in Netty 3
- Replace inboundBufferUpdated(ctx) with messageReceived(ctx, MessageList)
- Replace flush(ctx, promise) with write(ctx, MessageList, promise)
- Remove ByteToByteEncoder/Decoder/Codec
- Replaced by MessageToByteEncoder<ByteBuf>, ByteToMessageDecoder<ByteBuf>, and ByteMessageCodec<ByteBuf>
- Merge EmbeddedByteChannel and EmbeddedMessageChannel into EmbeddedChannel
- Add SimpleChannelInboundHandler which is sometimes more useful than
ChannelInboundHandlerAdapter
- Bring back Channel.isWritable() from Netty 3
- Add ChannelInboundHandler.channelWritabilityChanges() event
- Add RecvByteBufAllocator configuration property
- Similar to ReceiveBufferSizePredictor in Netty 3
- Some existing configuration properties such as
DatagramChannelConfig.receivePacketSize is gone now.
- Remove suspend/resumeIntermediaryDeallocation() in ByteBuf
This change would have been impossible without @normanmaurer's help. He
fixed, ported, and improved many parts of the changes.
2013-05-28 13:40:19 +02:00
|
|
|
|
2012-12-21 17:59:37 +01:00
|
|
|
/**
|
|
|
|
* Read bytes into the given {@link ByteBuf} and return the amount.
|
|
|
|
*/
|
2012-06-10 04:08:43 +02:00
|
|
|
protected abstract int doReadBytes(ByteBuf buf) throws Exception;
|
2012-12-21 17:59:37 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Write bytes form the given {@link ByteBuf} to the underlying {@link java.nio.channels.Channel}.
|
|
|
|
* @param buf the {@link ByteBuf} from which the bytes should be written
|
|
|
|
* @return amount the amount of written bytes
|
|
|
|
*/
|
2013-07-18 13:59:14 +02:00
|
|
|
protected abstract int doWriteBytes(ByteBuf buf) throws Exception;
|
2012-05-27 14:07:23 +02:00
|
|
|
|
2013-07-31 12:07:38 +02:00
|
|
|
protected final void setOpWrite() {
|
|
|
|
final SelectionKey key = selectionKey();
|
2014-01-09 18:27:56 +01:00
|
|
|
// Check first if the key is still valid as it may be canceled as part of the deregistration
|
|
|
|
// from the EventLoop
|
|
|
|
// See https://github.com/netty/netty/issues/2104
|
|
|
|
if (!key.isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
2013-07-31 12:07:38 +02:00
|
|
|
final int interestOps = key.interestOps();
|
|
|
|
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
|
|
|
|
key.interestOps(interestOps | SelectionKey.OP_WRITE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected final void clearOpWrite() {
|
|
|
|
final SelectionKey key = selectionKey();
|
2014-01-09 18:27:56 +01:00
|
|
|
// Check first if the key is still valid as it may be canceled as part of the deregistration
|
|
|
|
// from the EventLoop
|
|
|
|
// See https://github.com/netty/netty/issues/2104
|
|
|
|
if (!key.isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
2013-07-31 12:07:38 +02:00
|
|
|
final int interestOps = key.interestOps();
|
|
|
|
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
|
|
|
|
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
|
2013-06-10 11:19:58 +02:00
|
|
|
}
|
|
|
|
}
|
2012-05-27 14:07:23 +02:00
|
|
|
}
|