Make Bootstrap and ServerBootstrap thread-safe
- Additional fix for: #970 - Use LinkedHashMap again to save memory consumption - ServerBootstrap now makes a copy of child parameters so that modifying ServerBootstrap after bind() does not affect the already-bound servers. This also makes child channel initialization potentially faster due to reduced garbage iterator.
This commit is contained in:
parent
604b359d9e
commit
152c969eab
@ -27,8 +27,8 @@ import io.netty.util.AttributeKey;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* {@link AbstractBootstrap} is a helper class that makes it easy to bootstrap a {@link Channel}. It support
|
||||
@ -37,12 +37,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
abstract class AbstractBootstrap<B extends AbstractBootstrap<?, C>, C extends Channel> implements Cloneable {
|
||||
|
||||
private EventLoopGroup group;
|
||||
private ChannelFactory<? extends C> channelFactory;
|
||||
private SocketAddress localAddress;
|
||||
private final Map<ChannelOption<?>, Object> options = new ConcurrentHashMap<ChannelOption<?>, Object>();
|
||||
private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
|
||||
private ChannelHandler handler;
|
||||
private volatile EventLoopGroup group;
|
||||
private volatile ChannelFactory<? extends C> channelFactory;
|
||||
private volatile SocketAddress localAddress;
|
||||
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
|
||||
private final Map<AttributeKey<?>, Object> attrs = new LinkedHashMap<AttributeKey<?>, Object>();
|
||||
private volatile ChannelHandler handler;
|
||||
|
||||
AbstractBootstrap() {
|
||||
// Disallow extending from a different package.
|
||||
@ -53,9 +53,13 @@ abstract class AbstractBootstrap<B extends AbstractBootstrap<?, C>, C extends Ch
|
||||
channelFactory = bootstrap.channelFactory;
|
||||
handler = bootstrap.handler;
|
||||
localAddress = bootstrap.localAddress;
|
||||
synchronized (bootstrap.options) {
|
||||
options.putAll(bootstrap.options);
|
||||
}
|
||||
synchronized (bootstrap.attrs) {
|
||||
attrs.putAll(bootstrap.attrs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link EventLoopGroup} which is used to handle all the events for the to-be-creates
|
||||
@ -146,10 +150,14 @@ abstract class AbstractBootstrap<B extends AbstractBootstrap<?, C>, C extends Ch
|
||||
throw new NullPointerException("option");
|
||||
}
|
||||
if (value == null) {
|
||||
synchronized (options) {
|
||||
options.remove(option);
|
||||
}
|
||||
} else {
|
||||
synchronized (options) {
|
||||
options.put(option, value);
|
||||
}
|
||||
}
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@ -162,10 +170,14 @@ abstract class AbstractBootstrap<B extends AbstractBootstrap<?, C>, C extends Ch
|
||||
throw new NullPointerException("key");
|
||||
}
|
||||
if (value == null) {
|
||||
synchronized (attrs) {
|
||||
attrs.remove(key);
|
||||
}
|
||||
} else {
|
||||
synchronized (attrs) {
|
||||
attrs.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
B b = (B) this;
|
||||
@ -307,16 +319,20 @@ abstract class AbstractBootstrap<B extends AbstractBootstrap<?, C>, C extends Ch
|
||||
buf.append(localAddress);
|
||||
buf.append(", ");
|
||||
}
|
||||
if (options != null && !options.isEmpty()) {
|
||||
synchronized (options) {
|
||||
if (!options.isEmpty()) {
|
||||
buf.append("options: ");
|
||||
buf.append(options);
|
||||
buf.append(", ");
|
||||
}
|
||||
if (attrs != null && !attrs.isEmpty()) {
|
||||
}
|
||||
synchronized (attrs) {
|
||||
if (!attrs.isEmpty()) {
|
||||
buf.append("attrs: ");
|
||||
buf.append(attrs);
|
||||
buf.append(", ");
|
||||
}
|
||||
}
|
||||
if (handler != null) {
|
||||
buf.append("handler: ");
|
||||
buf.append(handler);
|
||||
|
@ -27,6 +27,7 @@ import io.netty.util.AttributeKey;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
@ -38,7 +39,7 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);
|
||||
|
||||
private SocketAddress remoteAddress;
|
||||
private volatile SocketAddress remoteAddress;
|
||||
|
||||
public Bootstrap() { }
|
||||
|
||||
@ -163,7 +164,9 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
ChannelPipeline p = channel.pipeline();
|
||||
p.addLast(handler());
|
||||
|
||||
for (Entry<ChannelOption<?>, Object> e: options().entrySet()) {
|
||||
final Map<ChannelOption<?>, Object> options = options();
|
||||
synchronized (options) {
|
||||
for (Entry<ChannelOption<?>, Object> e: options.entrySet()) {
|
||||
try {
|
||||
if (!channel.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
|
||||
logger.warn("Unknown channel option: " + e);
|
||||
@ -172,10 +175,14 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
logger.warn("Failed to set a channel option: " + channel, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<AttributeKey<?>, Object> e: attrs().entrySet()) {
|
||||
final Map<AttributeKey<?>, Object> attrs = attrs();
|
||||
synchronized (attrs) {
|
||||
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
|
||||
channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
group().register(channel).syncUninterruptibly();
|
||||
}
|
||||
|
@ -47,17 +47,10 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
|
||||
|
||||
private final ChannelHandler acceptor = new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
public void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline().addLast(new Acceptor());
|
||||
}
|
||||
};
|
||||
|
||||
private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
|
||||
private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
|
||||
private EventLoopGroup childGroup;
|
||||
private ChannelHandler childHandler;
|
||||
private volatile EventLoopGroup childGroup;
|
||||
private volatile ChannelHandler childHandler;
|
||||
|
||||
public ServerBootstrap() { }
|
||||
|
||||
@ -65,9 +58,13 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
|
||||
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).
|
||||
@ -104,10 +101,14 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
|
||||
throw new NullPointerException("childOption");
|
||||
}
|
||||
if (value == null) {
|
||||
synchronized (childOptions) {
|
||||
childOptions.remove(childOption);
|
||||
}
|
||||
} else {
|
||||
synchronized (childOptions) {
|
||||
childOptions.put(childOption, value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -143,23 +144,47 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
|
||||
Channel channel = channelFactory().newChannel();
|
||||
|
||||
try {
|
||||
channel.config().setOptions(options());
|
||||
final Map<ChannelOption<?>, Object> options = options();
|
||||
synchronized (options) {
|
||||
channel.config().setOptions(options);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
channel.close();
|
||||
return channel.newFailedFuture(e);
|
||||
}
|
||||
|
||||
for (Entry<AttributeKey<?>, Object> e: attrs().entrySet()) {
|
||||
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());
|
||||
}
|
||||
p.addLast(acceptor);
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
||||
ChannelFuture f = group().register(channel).awaitUninterruptibly();
|
||||
if (!f.isSuccess()) {
|
||||
@ -189,9 +214,34 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
|
||||
}
|
||||
}
|
||||
|
||||
private class Acceptor
|
||||
@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 implements ChannelInboundMessageHandler<Channel> {
|
||||
|
||||
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
|
||||
public MessageBuf<Channel> newInboundBuffer(ChannelHandlerContext ctx) throws Exception {
|
||||
return Unpooled.messageBuffer();
|
||||
@ -209,7 +259,7 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
|
||||
|
||||
child.pipeline().addLast(childHandler);
|
||||
|
||||
for (Entry<ChannelOption<?>, Object> e: childOptions.entrySet()) {
|
||||
for (Entry<ChannelOption<?>, Object> e: childOptions) {
|
||||
try {
|
||||
if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
|
||||
logger.warn("Unknown channel option: " + e);
|
||||
@ -219,7 +269,7 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<AttributeKey<?>, Object> e: childAttrs.entrySet()) {
|
||||
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
|
||||
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
|
||||
}
|
||||
|
||||
@ -249,16 +299,20 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
|
||||
buf.append(childGroup.getClass().getSimpleName());
|
||||
buf.append(", ");
|
||||
}
|
||||
if (childOptions != null && !childOptions.isEmpty()) {
|
||||
synchronized (childOptions) {
|
||||
if (!childOptions.isEmpty()) {
|
||||
buf.append("childOptions: ");
|
||||
buf.append(childOptions);
|
||||
buf.append(", ");
|
||||
}
|
||||
if (childAttrs != null && !childAttrs.isEmpty()) {
|
||||
}
|
||||
synchronized (childAttrs) {
|
||||
if (!childAttrs.isEmpty()) {
|
||||
buf.append("childAttrs: ");
|
||||
buf.append(childAttrs);
|
||||
buf.append(", ");
|
||||
}
|
||||
}
|
||||
if (childHandler != null) {
|
||||
buf.append("childHandler: ");
|
||||
buf.append(childHandler);
|
||||
|
Loading…
Reference in New Issue
Block a user