More graceful registration failure
- Fixes #2060 - Ensure to return a future/promise implementation that does not fail with 'not registered to an event loop' error for registration operations - If there is no usable event loop available, GlobalEventExecutor.INSTANCE is used as a fallback. - Add VoidChannel, which is used when an instantiation of a channel fails.
This commit is contained in:
parent
e1c47632e8
commit
4e05c52c2e
@ -22,9 +22,12 @@ import io.netty.channel.ChannelFutureListener;
|
|||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelOption;
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.DefaultChannelPromise;
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.channel.VoidChannel;
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
import io.netty.util.concurrent.EventExecutorGroup;
|
||||||
|
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||||
import io.netty.util.internal.StringUtil;
|
import io.netty.util.internal.StringUtil;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@ -228,16 +231,23 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ChannelFuture doBind(final SocketAddress localAddress) {
|
private ChannelFuture doBind(final SocketAddress localAddress) {
|
||||||
final ChannelFuture regPromise = initAndRegister();
|
final ChannelFuture regFuture = initAndRegister();
|
||||||
final Channel channel = regPromise.channel();
|
final Channel channel = regFuture.channel();
|
||||||
final ChannelPromise promise = channel.newPromise();
|
if (regFuture.cause() != null) {
|
||||||
if (regPromise.isDone()) {
|
return regFuture;
|
||||||
doBind0(regPromise, channel, localAddress, promise);
|
}
|
||||||
|
|
||||||
|
final ChannelPromise promise;
|
||||||
|
if (regFuture.isDone()) {
|
||||||
|
promise = channel.newPromise();
|
||||||
|
doBind0(regFuture, channel, localAddress, promise);
|
||||||
} else {
|
} else {
|
||||||
regPromise.addListener(new ChannelFutureListener() {
|
// Registration future is almost always fulfilled already, but just in case it's not.
|
||||||
|
promise = new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE);
|
||||||
|
regFuture.addListener(new ChannelFutureListener() {
|
||||||
@Override
|
@Override
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
doBind0(future, channel, localAddress, promise);
|
doBind0(regFuture, channel, localAddress, promise);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -248,7 +258,13 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
|||||||
abstract Channel createChannel();
|
abstract Channel createChannel();
|
||||||
|
|
||||||
final ChannelFuture initAndRegister() {
|
final ChannelFuture initAndRegister() {
|
||||||
Channel channel = createChannel();
|
Channel channel;
|
||||||
|
try {
|
||||||
|
channel = createChannel();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return VoidChannel.INSTANCE.newFailedFuture(t);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
init(channel);
|
init(channel);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
@ -256,9 +272,9 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
|||||||
return channel.newFailedFuture(t);
|
return channel.newFailedFuture(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelPromise regPromise = channel.newPromise();
|
ChannelPromise regFuture = channel.newPromise();
|
||||||
channel.unsafe().register(regPromise);
|
channel.unsafe().register(regFuture);
|
||||||
if (regPromise.cause() != null) {
|
if (regFuture.cause() != null) {
|
||||||
if (channel.isRegistered()) {
|
if (channel.isRegistered()) {
|
||||||
channel.close();
|
channel.close();
|
||||||
} else {
|
} else {
|
||||||
@ -275,7 +291,7 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
|||||||
// because bind() or connect() will be executed *after* the scheduled registration task is executed
|
// 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.
|
// because register(), bind(), and connect() are all bound to the same thread.
|
||||||
|
|
||||||
return regPromise;
|
return regFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void init(Channel channel) throws Exception;
|
abstract void init(Channel channel) throws Exception;
|
||||||
@ -286,7 +302,6 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
|
|||||||
|
|
||||||
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
|
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
|
||||||
// the pipeline in its channelRegistered() implementation.
|
// the pipeline in its channelRegistered() implementation.
|
||||||
|
|
||||||
channel.eventLoop().execute(new Runnable() {
|
channel.eventLoop().execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
172
transport/src/main/java/io/netty/channel/VoidChannel.java
Normal file
172
transport/src/main/java/io/netty/channel/VoidChannel.java
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 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.channel;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Channel} that represents a non-existing {@link Channel} which could not be instantiated successfully.
|
||||||
|
*/
|
||||||
|
public final class VoidChannel extends AbstractChannel {
|
||||||
|
|
||||||
|
public static final VoidChannel INSTANCE = new VoidChannel();
|
||||||
|
|
||||||
|
private VoidChannel() {
|
||||||
|
super(null, new AbstractEventLoop(null) {
|
||||||
|
private final ChannelHandlerInvoker invoker =
|
||||||
|
new DefaultChannelHandlerInvoker(GlobalEventExecutor.INSTANCE);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void shutdown() {
|
||||||
|
GlobalEventExecutor.INSTANCE.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerInvoker asInvoker() {
|
||||||
|
return invoker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inEventLoop(Thread thread) {
|
||||||
|
return GlobalEventExecutor.INSTANCE.inEventLoop(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShuttingDown() {
|
||||||
|
return GlobalEventExecutor.INSTANCE.isShuttingDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
|
||||||
|
return GlobalEventExecutor.INSTANCE.shutdownGracefully(quietPeriod, timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> terminationFuture() {
|
||||||
|
return GlobalEventExecutor.INSTANCE.terminationFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return GlobalEventExecutor.INSTANCE.isShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return GlobalEventExecutor.INSTANCE.isTerminated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
return GlobalEventExecutor.INSTANCE.awaitTermination(timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable command) {
|
||||||
|
GlobalEventExecutor.INSTANCE.execute(command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AbstractUnsafe newUnsafe() {
|
||||||
|
return new AbstractUnsafe() {
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isCompatible(EventLoop loop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SocketAddress localAddress0() {
|
||||||
|
return reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SocketAddress remoteAddress0() {
|
||||||
|
return reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doBind(SocketAddress localAddress) throws Exception {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doDisconnect() throws Exception {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doClose() throws Exception {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doBeginRead() throws Exception {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelConfig config() {
|
||||||
|
return reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelMetadata metadata() {
|
||||||
|
return reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return StringUtil.simpleClassName(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T reject() {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
StringUtil.simpleClassName(VoidChannel.class) +
|
||||||
|
" is only for the representation of a non-existing " +
|
||||||
|
StringUtil.simpleClassName(Channel.class) + '.');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 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.channel.oio;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.ChannelException;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelFutureListener;
|
||||||
|
import io.netty.channel.ChannelHandlerAdapter;
|
||||||
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.channel.socket.oio.OioServerSocketChannel;
|
||||||
|
import io.netty.channel.socket.oio.OioSocketChannel;
|
||||||
|
import io.netty.util.NetUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class OioEventLoopTest {
|
||||||
|
@Test
|
||||||
|
public void testTooManyServerChannels() throws Exception {
|
||||||
|
EventLoopGroup g = new OioEventLoopGroup(1);
|
||||||
|
ServerBootstrap b = new ServerBootstrap();
|
||||||
|
b.channel(OioServerSocketChannel.class);
|
||||||
|
b.group(g);
|
||||||
|
b.childHandler(new ChannelHandlerAdapter());
|
||||||
|
ChannelFuture f1 = b.bind(0);
|
||||||
|
f1.sync();
|
||||||
|
|
||||||
|
ChannelFuture f2 = b.bind(0);
|
||||||
|
f2.await();
|
||||||
|
|
||||||
|
assertThat(f2.cause(), is(instanceOf(ChannelException.class)));
|
||||||
|
assertThat(f2.cause().getMessage().toLowerCase(), containsString("too many channels"));
|
||||||
|
|
||||||
|
final CountDownLatch notified = new CountDownLatch(1);
|
||||||
|
f2.addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
|
notified.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
notified.await();
|
||||||
|
g.shutdownGracefully();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTooManyClientChannels() throws Exception {
|
||||||
|
EventLoopGroup g = new OioEventLoopGroup(1);
|
||||||
|
ServerBootstrap sb = new ServerBootstrap();
|
||||||
|
sb.channel(OioServerSocketChannel.class);
|
||||||
|
sb.group(g);
|
||||||
|
sb.childHandler(new ChannelHandlerAdapter());
|
||||||
|
ChannelFuture f1 = sb.bind(0);
|
||||||
|
f1.sync();
|
||||||
|
|
||||||
|
Bootstrap cb = new Bootstrap();
|
||||||
|
cb.channel(OioSocketChannel.class);
|
||||||
|
cb.group(g);
|
||||||
|
cb.handler(new ChannelHandlerAdapter());
|
||||||
|
ChannelFuture f2 = cb.connect(NetUtil.LOCALHOST, ((InetSocketAddress) f1.channel().localAddress()).getPort());
|
||||||
|
f2.await();
|
||||||
|
|
||||||
|
assertThat(f2.cause(), is(instanceOf(ChannelException.class)));
|
||||||
|
assertThat(f2.cause().getMessage().toLowerCase(), containsString("too many channels"));
|
||||||
|
|
||||||
|
final CountDownLatch notified = new CountDownLatch(1);
|
||||||
|
f2.addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
|
notified.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
notified.await();
|
||||||
|
g.shutdownGracefully();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTooManyAcceptedChannels() throws Exception {
|
||||||
|
EventLoopGroup g = new OioEventLoopGroup(1);
|
||||||
|
ServerBootstrap sb = new ServerBootstrap();
|
||||||
|
sb.channel(OioServerSocketChannel.class);
|
||||||
|
sb.group(g);
|
||||||
|
sb.childHandler(new ChannelHandlerAdapter());
|
||||||
|
ChannelFuture f1 = sb.bind(0);
|
||||||
|
f1.sync();
|
||||||
|
|
||||||
|
Socket s = new Socket(NetUtil.LOCALHOST, ((InetSocketAddress) f1.channel().localAddress()).getPort());
|
||||||
|
assertThat(s.getInputStream().read(), is(-1));
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
g.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user