Fix a dead lock in ServerBootstrap as described in #1175

- Reduce code duplication between bootstrap implementations
This commit is contained in:
Trustin Lee 2013-03-21 21:34:13 +09:00
parent 9b208028ef
commit 8fb80e9179
4 changed files with 120 additions and 121 deletions

View File

@ -19,8 +19,10 @@ package io.netty.bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoopGroup;
import io.netty.util.AttributeKey;
@ -265,7 +267,75 @@ abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Ch
return doBind(localAddress);
}
abstract ChannelFuture doBind(SocketAddress localAddress);
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regPromise = initAndRegister();
final Channel channel = regPromise.channel();
final ChannelPromise promise = channel.newPromise();
if (regPromise.isDone()) {
doBind0(regPromise, channel, localAddress, promise);
} else {
regPromise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doBind0(future, channel, localAddress, promise);
}
});
}
return promise;
}
final ChannelFuture initAndRegister() {
final Channel channel = channelFactory().newChannel();
try {
init(channel);
} catch (Throwable t) {
channel.unsafe().closeForcibly();
return channel.newFailedFuture(t);
}
ChannelPromise regPromise = channel.newPromise();
group().register(channel, regPromise);
if (regPromise.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now beause the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return regPromise;
}
abstract void init(Channel channel) throws Exception;
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
/**
* the {@link ChannelHandler} to use for serving the requests.

View File

@ -76,39 +76,6 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
return this;
}
@Override
ChannelFuture doBind(final SocketAddress localAddress) {
final Channel channel = channelFactory().newChannel();
ChannelPromise initPromise = init(channel);
if (initPromise.cause() != null) {
return initPromise;
}
final ChannelPromise promise = channel.newPromise();
if (initPromise.isDone()) {
doBind0(initPromise, channel, localAddress, promise);
} else {
initPromise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doBind0(future, channel, localAddress, promise);
}
});
}
return promise;
}
private static void doBind0(
ChannelFuture initFuture, Channel channel, SocketAddress localAddress, ChannelPromise promise) {
if (initFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(initFuture.cause());
}
}
/**
* Connect a {@link Channel} to the remote peer.
*/
@ -163,20 +130,20 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
* @see {@link #connect()}
*/
private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
final Channel channel = channelFactory().newChannel();
ChannelPromise initPromise = init(channel);
if (initPromise.cause() != null) {
return initPromise;
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
final ChannelPromise promise = channel.newPromise();
if (initPromise.isDone()) {
doConnect0(initPromise, channel, remoteAddress, localAddress, promise);
if (regFuture.isDone()) {
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
} else {
initPromise.addListener(new ChannelFutureListener() {
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doConnect0(future, channel, remoteAddress, localAddress, promise);
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
}
});
}
@ -185,10 +152,15 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
}
private static void doConnect0(
ChannelFuture initFuture, Channel channel,
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
final ChannelFuture regFuture, final Channel channel,
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (initFuture.isSuccess()) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
if (localAddress == null) {
channel.connect(remoteAddress, promise);
} else {
@ -196,14 +168,15 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
}
promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(initFuture.cause());
promise.setFailure(regFuture.cause());
}
}
});
}
@Override
@SuppressWarnings("unchecked")
private ChannelPromise init(Channel channel) {
ChannelPromise promise = channel.newPromise();
try {
void init(Channel channel) throws Exception {
ChannelPipeline p = channel.pipeline();
p.addLast(handler());
@ -226,30 +199,6 @@ public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
}
group().register(channel, promise);
} catch (Throwable t) {
promise.setFailure(t);
}
if (promise.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now beause the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return promise;
}
@Override

View File

@ -18,8 +18,6 @@ package io.netty.bootstrap;
import io.netty.buffer.MessageBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundMessageHandler;
@ -34,7 +32,6 @@ import io.netty.util.AttributeKey;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.SocketAddress;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@ -140,18 +137,11 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
}
@Override
ChannelFuture doBind(SocketAddress localAddress) {
Channel channel = channelFactory().newChannel();
try {
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options();
synchronized (options) {
channel.config().setOptions(options);
}
} catch (Exception e) {
channel.close();
return channel.newFailedFuture(e);
}
final Map<AttributeKey<?>, Object> attrs = attrs();
synchronized (attrs) {
@ -185,13 +175,6 @@ public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Se
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
ChannelFuture f = group().register(channel).awaitUninterruptibly();
if (!f.isSuccess()) {
return f;
}
return channel.bind(localAddress).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
@Override

View File

@ -156,11 +156,8 @@ public abstract class AbstractAioChannel extends AbstractChannel {
assert eventLoop().inEventLoop();
assert connectPromise != null;
try {
boolean wasActive = isActive();
connectPromise.setSuccess();
if (!wasActive && isActive()) {
pipeline().fireChannelActive();
}
} catch (Throwable t) {
connectPromise.setFailure(t);
closeIfClosed();