14158070bf
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.
326 lines
12 KiB
Java
326 lines
12 KiB
Java
/*
|
|
* 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.
|
|
*/
|
|
package io.netty.bootstrap;
|
|
|
|
import io.netty.channel.Channel;
|
|
import io.netty.channel.ChannelConfig;
|
|
import io.netty.channel.ChannelHandler;
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
import io.netty.channel.ChannelInitializer;
|
|
import io.netty.channel.ChannelOption;
|
|
import io.netty.channel.ChannelPipeline;
|
|
import io.netty.channel.EventLoopGroup;
|
|
import io.netty.channel.MessageList;
|
|
import io.netty.channel.ServerChannel;
|
|
import io.netty.channel.socket.SocketChannel;
|
|
import io.netty.util.AttributeKey;
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
|
|
import java.util.LinkedHashMap;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/**
|
|
* {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel}
|
|
*
|
|
*/
|
|
public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
|
|
|
|
private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
|
|
private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
|
|
private volatile EventLoopGroup childGroup;
|
|
private volatile ChannelHandler childHandler;
|
|
|
|
public ServerBootstrap() { }
|
|
|
|
private ServerBootstrap(ServerBootstrap bootstrap) {
|
|
super(bootstrap);
|
|
childGroup = bootstrap.childGroup;
|
|
childHandler = bootstrap.childHandler;
|
|
synchronized (bootstrap.childOptions) {
|
|
childOptions.putAll(bootstrap.childOptions);
|
|
}
|
|
synchronized (bootstrap.childAttrs) {
|
|
childAttrs.putAll(bootstrap.childAttrs);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Specify the {@link EventLoopGroup} which is used for the parent (acceptor) and the child (client).
|
|
*/
|
|
@Override
|
|
public ServerBootstrap group(EventLoopGroup group) {
|
|
return group(group, group);
|
|
}
|
|
|
|
/**
|
|
* Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These
|
|
* {@link EventLoopGroup}'s are used to handle all the events and IO for {@link SocketChannel} and
|
|
* {@link Channel}'s.
|
|
*/
|
|
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
|
|
super.group(parentGroup);
|
|
if (childGroup == null) {
|
|
throw new NullPointerException("childGroup");
|
|
}
|
|
if (this.childGroup != null) {
|
|
throw new IllegalStateException("childGroup set already");
|
|
}
|
|
this.childGroup = childGroup;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they get created
|
|
* (after the acceptor accepted the {@link Channel}). Use a value of {@code null} to remove a previous set
|
|
* {@link ChannelOption}.
|
|
*/
|
|
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
|
|
if (childOption == null) {
|
|
throw new NullPointerException("childOption");
|
|
}
|
|
if (value == null) {
|
|
synchronized (childOptions) {
|
|
childOptions.remove(childOption);
|
|
}
|
|
} else {
|
|
synchronized (childOptions) {
|
|
childOptions.put(childOption, value);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the specific {@link AttributeKey} with the given value on every child {@link Channel}. If the value is
|
|
* {@code null} the {@link AttributeKey} is removed
|
|
*/
|
|
public <T> ServerBootstrap childAttr(AttributeKey<T> childKey, T value) {
|
|
if (childKey == null) {
|
|
throw new NullPointerException("childKey");
|
|
}
|
|
if (value == null) {
|
|
childAttrs.remove(childKey);
|
|
} else {
|
|
childAttrs.put(childKey, value);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the {@link ChannelHandler} which is used to serve the request for the {@link Channel}'s.
|
|
*/
|
|
public ServerBootstrap childHandler(ChannelHandler childHandler) {
|
|
if (childHandler == null) {
|
|
throw new NullPointerException("childHandler");
|
|
}
|
|
this.childHandler = childHandler;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
void init(Channel channel) throws Exception {
|
|
final Map<ChannelOption<?>, Object> options = options();
|
|
synchronized (options) {
|
|
channel.config().setOptions(options);
|
|
}
|
|
|
|
final Map<AttributeKey<?>, Object> attrs = attrs();
|
|
synchronized (attrs) {
|
|
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
|
|
@SuppressWarnings("unchecked")
|
|
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
|
|
channel.attr(key).set(e.getValue());
|
|
}
|
|
}
|
|
|
|
ChannelPipeline p = channel.pipeline();
|
|
if (handler() != null) {
|
|
p.addLast(handler());
|
|
}
|
|
|
|
final EventLoopGroup currentChildGroup = childGroup;
|
|
final ChannelHandler currentChildHandler = childHandler;
|
|
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
|
|
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
|
|
synchronized (childOptions) {
|
|
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
|
|
}
|
|
synchronized (childAttrs) {
|
|
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
|
|
}
|
|
|
|
p.addLast(new ChannelInitializer<Channel>() {
|
|
@Override
|
|
public void initChannel(Channel ch) throws Exception {
|
|
ch.pipeline().addLast(new ServerBootstrapAcceptor(
|
|
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
@Deprecated
|
|
@SuppressWarnings("deprecation")
|
|
public void shutdown() {
|
|
super.shutdown();
|
|
if (childGroup != null) {
|
|
childGroup.shutdown();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ServerBootstrap validate() {
|
|
super.validate();
|
|
if (childHandler == null) {
|
|
throw new IllegalStateException("childHandler not set");
|
|
}
|
|
if (childGroup == null) {
|
|
logger.warn("childGroup is not set. Using parentGroup instead.");
|
|
childGroup = group();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static Entry<ChannelOption<?>, Object>[] newOptionArray(int size) {
|
|
return new Entry[size];
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static Entry<AttributeKey<?>, Object>[] newAttrArray(int size) {
|
|
return new Entry[size];
|
|
}
|
|
|
|
private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
|
|
|
|
private final EventLoopGroup childGroup;
|
|
private final ChannelHandler childHandler;
|
|
private final Entry<ChannelOption<?>, Object>[] childOptions;
|
|
private final Entry<AttributeKey<?>, Object>[] childAttrs;
|
|
|
|
@SuppressWarnings("unchecked")
|
|
ServerBootstrapAcceptor(
|
|
EventLoopGroup childGroup, ChannelHandler childHandler,
|
|
Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
|
|
this.childGroup = childGroup;
|
|
this.childHandler = childHandler;
|
|
this.childOptions = childOptions;
|
|
this.childAttrs = childAttrs;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public void messageReceived(ChannelHandlerContext ctx, MessageList<Object> msgs) {
|
|
int size = msgs.size();
|
|
for (int i = 0; i < size; i ++) {
|
|
Channel child = (Channel) msgs.get(i);
|
|
child.pipeline().addLast(childHandler);
|
|
|
|
for (Entry<ChannelOption<?>, Object> e: childOptions) {
|
|
try {
|
|
if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
|
|
logger.warn("Unknown channel option: " + e);
|
|
}
|
|
} catch (Throwable t) {
|
|
logger.warn("Failed to set a channel option: " + child, t);
|
|
}
|
|
}
|
|
|
|
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
|
|
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
|
|
}
|
|
|
|
try {
|
|
childGroup.register(child);
|
|
} catch (Throwable t) {
|
|
child.unsafe().closeForcibly();
|
|
logger.warn("Failed to register an accepted channel: " + child, t);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
final ChannelConfig config = ctx.channel().config();
|
|
if (config.isAutoRead()) {
|
|
// stop accept new connections for 1 second to allow the channel to recover
|
|
// See https://github.com/netty/netty/issues/1328
|
|
config.setAutoRead(false);
|
|
ctx.channel().eventLoop().schedule(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
config.setAutoRead(true);
|
|
}
|
|
}, 1, TimeUnit.SECONDS);
|
|
}
|
|
// still let the exceptionCaught event flow through the pipeline to give the user
|
|
// a chance to do something with it
|
|
ctx.fireExceptionCaught(cause);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("CloneDoesntCallSuperClone")
|
|
public ServerBootstrap clone() {
|
|
return new ServerBootstrap(this);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder buf = new StringBuilder(super.toString());
|
|
buf.setLength(buf.length() - 1);
|
|
buf.append(", ");
|
|
if (childGroup != null) {
|
|
buf.append("childGroup: ");
|
|
buf.append(childGroup.getClass().getSimpleName());
|
|
buf.append(", ");
|
|
}
|
|
synchronized (childOptions) {
|
|
if (!childOptions.isEmpty()) {
|
|
buf.append("childOptions: ");
|
|
buf.append(childOptions);
|
|
buf.append(", ");
|
|
}
|
|
}
|
|
synchronized (childAttrs) {
|
|
if (!childAttrs.isEmpty()) {
|
|
buf.append("childAttrs: ");
|
|
buf.append(childAttrs);
|
|
buf.append(", ");
|
|
}
|
|
}
|
|
if (childHandler != null) {
|
|
buf.append("childHandler: ");
|
|
buf.append(childHandler);
|
|
buf.append(", ");
|
|
}
|
|
if (buf.charAt(buf.length() - 1) == '(') {
|
|
buf.append(')');
|
|
} else {
|
|
buf.setCharAt(buf.length() - 2, ')');
|
|
buf.setLength(buf.length() - 1);
|
|
}
|
|
|
|
return buf.toString();
|
|
}
|
|
}
|