c4dbbe39c9
Motivation: We should just add `executor()` to the `ChannelOutboundInvoker` interface and override this method in `Channel` to return `EventLoop`. Modifications: - Add `executor()` method to `ChannelOutboundInvoker` - Let `Channel` override this method and return `EventLoop`. - Adjust all usages of `eventLoop()` - Add some default implementations Result: API cleanup
331 lines
12 KiB
Java
331 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:
|
|
*
|
|
* https://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.ChannelInitializer;
|
|
import io.netty.channel.ChannelOption;
|
|
import io.netty.channel.ChannelPipeline;
|
|
import io.netty.channel.EventLoop;
|
|
import io.netty.channel.EventLoopGroup;
|
|
import io.netty.channel.ReflectiveServerChannelFactory;
|
|
import io.netty.channel.ServerChannel;
|
|
import io.netty.channel.ServerChannelFactory;
|
|
import io.netty.util.AttributeKey;
|
|
import io.netty.util.concurrent.DefaultPromise;
|
|
import io.netty.util.concurrent.Future;
|
|
import io.netty.util.concurrent.Promise;
|
|
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.ConcurrentHashMap;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import static java.util.Objects.requireNonNull;
|
|
|
|
/**
|
|
* {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel}
|
|
*
|
|
*/
|
|
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel,
|
|
ServerChannelFactory<? extends ServerChannel>> {
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
|
|
|
|
// The order in which child ChannelOptions are applied is important they may depend on each other for validation
|
|
// purposes.
|
|
private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<>();
|
|
private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap<>();
|
|
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
|
|
private volatile EventLoopGroup childGroup;
|
|
private volatile ChannelHandler childHandler;
|
|
volatile ServerChannelFactory<? extends ServerChannel> channelFactory;
|
|
|
|
public ServerBootstrap() { }
|
|
|
|
private ServerBootstrap(ServerBootstrap bootstrap) {
|
|
super(bootstrap);
|
|
childGroup = bootstrap.childGroup;
|
|
childHandler = bootstrap.childHandler;
|
|
channelFactory = bootstrap.channelFactory;
|
|
synchronized (bootstrap.childOptions) {
|
|
childOptions.putAll(bootstrap.childOptions);
|
|
}
|
|
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 ServerChannel} and
|
|
* {@link Channel}'s.
|
|
*/
|
|
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
|
|
super.group(parentGroup);
|
|
requireNonNull(childGroup, "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) {
|
|
requireNonNull(childOption, "childOption");
|
|
synchronized (childOptions) {
|
|
if (value == null) {
|
|
childOptions.remove(childOption);
|
|
} else {
|
|
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) {
|
|
requireNonNull(childKey, "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) {
|
|
requireNonNull(childHandler, "childHandler");
|
|
this.childHandler = childHandler;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* The {@link Class} which is used to create {@link ServerChannel} instances from.
|
|
* You either use this or {@link #channelFactory(ServerChannelFactory)} if your
|
|
* {@link Channel} implementation has no no-args constructor.
|
|
*/
|
|
public ServerBootstrap channel(Class<? extends ServerChannel> channelClass) {
|
|
requireNonNull(channelClass, "channelClass");
|
|
return channelFactory(new ReflectiveServerChannelFactory<ServerChannel>(channelClass));
|
|
}
|
|
|
|
/**
|
|
* {@link ServerChannelFactory} which is used to create {@link Channel} instances from
|
|
* when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)}
|
|
* is not working for you because of some more complex needs. If your {@link Channel} implementation
|
|
* has a no-args constructor, its highly recommend to just use {@link #channel(Class)} to
|
|
* simplify your code.
|
|
*/
|
|
public ServerBootstrap channelFactory(ServerChannelFactory<? extends ServerChannel> channelFactory) {
|
|
requireNonNull(channelFactory, "channelFactory");
|
|
if (this.channelFactory != null) {
|
|
throw new IllegalStateException("channelFactory set already");
|
|
}
|
|
|
|
this.channelFactory = channelFactory;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
Future<Channel> init(Channel channel) {
|
|
Promise<Channel> promise = new DefaultPromise<>(channel.executor());
|
|
setChannelOptions(channel, newOptionsArray(), logger);
|
|
setAttributes(channel, newAttributesArray());
|
|
|
|
ChannelPipeline p = channel.pipeline();
|
|
|
|
final ChannelHandler currentChildHandler = childHandler;
|
|
final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
|
|
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
|
|
|
|
p.addLast(new ChannelInitializer<Channel>() {
|
|
@Override
|
|
public void initChannel(final Channel ch) {
|
|
final ChannelPipeline pipeline = ch.pipeline();
|
|
ChannelHandler handler = config.handler();
|
|
if (handler != null) {
|
|
pipeline.addLast(handler);
|
|
}
|
|
|
|
ch.executor().execute(() -> {
|
|
pipeline.addLast(new ServerBootstrapAcceptor(
|
|
ch, currentChildHandler, currentChildOptions, currentChildAttrs));
|
|
promise.setSuccess(ch);
|
|
});
|
|
}
|
|
});
|
|
return promise;
|
|
}
|
|
|
|
@Override
|
|
ServerChannel newChannel(EventLoop eventLoop) throws Exception {
|
|
return channelFactory.newChannel(eventLoop, childGroup);
|
|
}
|
|
|
|
@Override
|
|
public ServerBootstrap validate() {
|
|
super.validate();
|
|
if (childHandler == null) {
|
|
throw new IllegalStateException("childHandler not set");
|
|
}
|
|
if (channelFactory == null) {
|
|
throw new IllegalStateException("channelFactory not set");
|
|
}
|
|
if (childGroup == null) {
|
|
logger.warn("childGroup is not set. Using parentGroup instead.");
|
|
childGroup = config.group();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private static class ServerBootstrapAcceptor implements ChannelHandler {
|
|
|
|
private final ChannelHandler childHandler;
|
|
private final Entry<ChannelOption<?>, Object>[] childOptions;
|
|
private final Entry<AttributeKey<?>, Object>[] childAttrs;
|
|
private final Runnable enableAutoReadTask;
|
|
|
|
ServerBootstrapAcceptor(
|
|
final Channel channel, ChannelHandler childHandler,
|
|
Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
|
|
this.childHandler = childHandler;
|
|
this.childOptions = childOptions;
|
|
this.childAttrs = childAttrs;
|
|
|
|
// Task which is scheduled to re-enable auto-read.
|
|
// It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
|
|
// not be able to load the class because of the file limit it already reached.
|
|
//
|
|
// See https://github.com/netty/netty/issues/1328
|
|
enableAutoReadTask = () -> channel.config().setAutoRead(true);
|
|
}
|
|
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
|
final Channel child = (Channel) msg;
|
|
|
|
EventLoop childEventLoop = child.executor();
|
|
// Ensure we always execute on the child EventLoop.
|
|
if (childEventLoop.inEventLoop()) {
|
|
initChild(child);
|
|
} else {
|
|
try {
|
|
childEventLoop.execute(() -> initChild(child));
|
|
} catch (Throwable cause) {
|
|
forceClose(child, cause);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void initChild(final Channel child) {
|
|
assert child.executor().inEventLoop();
|
|
try {
|
|
setChannelOptions(child, childOptions, logger);
|
|
setAttributes(child, childAttrs);
|
|
|
|
child.pipeline().addLast(childHandler);
|
|
|
|
child.register().addListener(future -> {
|
|
if (!future.isSuccess()) {
|
|
forceClose(child, future.cause());
|
|
}
|
|
});
|
|
} catch (Throwable t) {
|
|
forceClose(child, t);
|
|
}
|
|
}
|
|
|
|
private static void forceClose(Channel child, 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().executor().schedule(enableAutoReadTask, 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);
|
|
}
|
|
|
|
/**
|
|
* Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
|
|
* if non is configured yet.
|
|
*
|
|
* @deprecated Use {@link #config()} instead.
|
|
*/
|
|
@Deprecated
|
|
public EventLoopGroup childGroup() {
|
|
return childGroup;
|
|
}
|
|
|
|
final ChannelHandler childHandler() {
|
|
return childHandler;
|
|
}
|
|
|
|
final Map<ChannelOption<?>, Object> childOptions() {
|
|
synchronized (childOptions) {
|
|
return copiedMap(childOptions);
|
|
}
|
|
}
|
|
|
|
final Map<AttributeKey<?>, Object> childAttrs() {
|
|
return copiedMap(childAttrs);
|
|
}
|
|
|
|
@Override
|
|
public final ServerBootstrapConfig config() {
|
|
return config;
|
|
}
|
|
}
|