Let CombinedChannelDuplexHandler correctly handle exceptionCaught. Related to [#4528]
Motivation: ChannelInboundHandler and ChannelOutboundHandler both can implement exceptionCaught(...) method and so we need to dispatch to both of them. Modifications: - Correctly first dispatch exceptionCaught to the ChannelInboundHandler but also make sure the next handler it will be dispatched to will be the ChannelOutboundHandler - Add removeInboundHandler() and removeOutboundHandler() which allows to remove one of the combined handlers Result: Correctly handle events
This commit is contained in:
parent
8bfbb35979
commit
d7d64137e0
@ -15,15 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.channel;
|
package io.netty.channel;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.util.Attribute;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import io.netty.util.concurrent.EventExecutor;
|
||||||
|
import io.netty.util.internal.OneTimeTask;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines a {@link ChannelInboundHandler} and a {@link ChannelOutboundHandler} into one {@link ChannelHandler}.
|
* Combines a {@link ChannelInboundHandler} and a {@link ChannelOutboundHandler} into one {@link ChannelHandler}.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler, O extends ChannelOutboundHandler>
|
public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler, O extends ChannelOutboundHandler>
|
||||||
extends ChannelDuplexHandler {
|
extends ChannelDuplexHandler {
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(CombinedChannelDuplexHandler.class);
|
||||||
|
|
||||||
|
private DelegatingChannelHandlerContext inboundCtx;
|
||||||
|
private DelegatingChannelHandlerContext outboundCtx;
|
||||||
|
private volatile boolean handlerAdded;
|
||||||
|
|
||||||
private I inboundHandler;
|
private I inboundHandler;
|
||||||
private O outboundHandler;
|
private O outboundHandler;
|
||||||
|
|
||||||
@ -88,6 +101,28 @@ public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler, O ext
|
|||||||
return outboundHandler;
|
return outboundHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkAdded() {
|
||||||
|
if (!handlerAdded) {
|
||||||
|
throw new IllegalStateException("handler not added to pipeline yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the {@link ChannelInboundHandler} that was combined in this {@link CombinedChannelDuplexHandler}.
|
||||||
|
*/
|
||||||
|
public final void removeInboundHandler() {
|
||||||
|
checkAdded();
|
||||||
|
inboundCtx.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the {@link ChannelOutboundHandler} that was combined in this {@link CombinedChannelDuplexHandler}.
|
||||||
|
*/
|
||||||
|
public final void removeOutboundHandler() {
|
||||||
|
checkAdded();
|
||||||
|
outboundCtx.remove();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||||
if (inboundHandler == null) {
|
if (inboundHandler == null) {
|
||||||
@ -96,67 +131,151 @@ public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler, O ext
|
|||||||
" if " + CombinedChannelDuplexHandler.class.getSimpleName() +
|
" if " + CombinedChannelDuplexHandler.class.getSimpleName() +
|
||||||
" was constructed with the default constructor.");
|
" was constructed with the default constructor.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outboundCtx = new DelegatingChannelHandlerContext(ctx, outboundHandler);
|
||||||
|
inboundCtx = new DelegatingChannelHandlerContext(ctx, inboundHandler) {
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireExceptionCaught(Throwable cause) {
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
try {
|
try {
|
||||||
inboundHandler.handlerAdded(ctx);
|
// We directly delegate to the ChannelOutboundHandler as this may override exceptionCaught(...)
|
||||||
|
// as well
|
||||||
|
outboundHandler.exceptionCaught(outboundCtx, cause);
|
||||||
|
} catch (Throwable error) {
|
||||||
|
if (logger.isWarnEnabled()) {
|
||||||
|
logger.warn(
|
||||||
|
"An exception was thrown by a user handler's " +
|
||||||
|
"exceptionCaught() method while handling the following exception:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.fireExceptionCaught(cause);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The inboundCtx and outboundCtx were created and set now it's safe to call removeInboundHandler() and
|
||||||
|
// removeOutboundHandler().
|
||||||
|
handlerAdded = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
inboundHandler.handlerAdded(inboundCtx);
|
||||||
} finally {
|
} finally {
|
||||||
outboundHandler.handlerAdded(ctx);
|
outboundHandler.handlerAdded(outboundCtx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||||
try {
|
try {
|
||||||
inboundHandler.handlerRemoved(ctx);
|
inboundCtx.remove();
|
||||||
} finally {
|
} finally {
|
||||||
outboundHandler.handlerRemoved(ctx);
|
outboundCtx.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
||||||
inboundHandler.channelRegistered(ctx);
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.channelRegistered(inboundCtx);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireChannelRegistered();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
||||||
inboundHandler.channelUnregistered(ctx);
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.channelUnregistered(inboundCtx);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireChannelUnregistered();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
inboundHandler.channelActive(ctx);
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.channelActive(inboundCtx);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireChannelActive();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
inboundHandler.channelInactive(ctx);
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.channelInactive(inboundCtx);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireChannelInactive();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
inboundHandler.exceptionCaught(ctx, cause);
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.exceptionCaught(inboundCtx, cause);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireExceptionCaught(cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
inboundHandler.userEventTriggered(ctx, evt);
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.userEventTriggered(inboundCtx, evt);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireUserEventTriggered(evt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
inboundHandler.channelRead(ctx, msg);
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.channelRead(inboundCtx, msg);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||||
inboundHandler.channelReadComplete(ctx);
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.channelReadComplete(inboundCtx);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireChannelReadComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
assert ctx == inboundCtx.ctx;
|
||||||
|
if (!inboundCtx.removed) {
|
||||||
|
inboundHandler.channelWritabilityChanged(inboundCtx);
|
||||||
|
} else {
|
||||||
|
inboundCtx.fireChannelWritabilityChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bind(
|
public void bind(
|
||||||
ChannelHandlerContext ctx,
|
ChannelHandlerContext ctx,
|
||||||
SocketAddress localAddress, ChannelPromise promise) throws Exception {
|
SocketAddress localAddress, ChannelPromise promise) throws Exception {
|
||||||
outboundHandler.bind(ctx, localAddress, promise);
|
assert ctx == outboundCtx.ctx;
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
|
outboundHandler.bind(outboundCtx, localAddress, promise);
|
||||||
|
} else {
|
||||||
|
outboundCtx.bind(localAddress, promise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -164,41 +283,321 @@ public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler, O ext
|
|||||||
ChannelHandlerContext ctx,
|
ChannelHandlerContext ctx,
|
||||||
SocketAddress remoteAddress, SocketAddress localAddress,
|
SocketAddress remoteAddress, SocketAddress localAddress,
|
||||||
ChannelPromise promise) throws Exception {
|
ChannelPromise promise) throws Exception {
|
||||||
outboundHandler.connect(ctx, remoteAddress, localAddress, promise);
|
assert ctx == outboundCtx.ctx;
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
|
outboundHandler.connect(outboundCtx, remoteAddress, localAddress, promise);
|
||||||
|
} else {
|
||||||
|
outboundCtx.connect(localAddress, promise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||||
outboundHandler.disconnect(ctx, promise);
|
assert ctx == outboundCtx.ctx;
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
|
outboundHandler.disconnect(outboundCtx, promise);
|
||||||
|
} else {
|
||||||
|
outboundCtx.disconnect(promise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||||
outboundHandler.close(ctx, promise);
|
assert ctx == outboundCtx.ctx;
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
|
outboundHandler.close(outboundCtx, promise);
|
||||||
|
} else {
|
||||||
|
outboundCtx.close(promise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||||
outboundHandler.deregister(ctx, promise);
|
assert ctx == outboundCtx.ctx;
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
|
outboundHandler.deregister(outboundCtx, promise);
|
||||||
|
} else {
|
||||||
|
outboundCtx.deregister(promise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(ChannelHandlerContext ctx) throws Exception {
|
public void read(ChannelHandlerContext ctx) throws Exception {
|
||||||
outboundHandler.read(ctx);
|
assert ctx == outboundCtx.ctx;
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
|
outboundHandler.read(outboundCtx);
|
||||||
|
} else {
|
||||||
|
outboundCtx.read();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
outboundHandler.write(ctx, msg, promise);
|
assert ctx == outboundCtx.ctx;
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
|
outboundHandler.write(outboundCtx, msg, promise);
|
||||||
|
} else {
|
||||||
|
outboundCtx.write(msg, promise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush(ChannelHandlerContext ctx) throws Exception {
|
public void flush(ChannelHandlerContext ctx) throws Exception {
|
||||||
outboundHandler.flush(ctx);
|
assert ctx == outboundCtx.ctx;
|
||||||
|
if (!outboundCtx.removed) {
|
||||||
|
outboundHandler.flush(outboundCtx);
|
||||||
|
} else {
|
||||||
|
outboundCtx.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DelegatingChannelHandlerContext implements ChannelHandlerContext {
|
||||||
|
|
||||||
|
private final ChannelHandlerContext ctx;
|
||||||
|
private final ChannelHandler handler;
|
||||||
|
boolean removed;
|
||||||
|
|
||||||
|
DelegatingChannelHandlerContext(ChannelHandlerContext ctx, ChannelHandler handler) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
public Channel channel() {
|
||||||
inboundHandler.channelWritabilityChanged(ctx);
|
return ctx.channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventExecutor executor() {
|
||||||
|
return ctx.executor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return ctx.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandler handler() {
|
||||||
|
return ctx.handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRemoved() {
|
||||||
|
return removed || ctx.isRemoved();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireChannelRegistered() {
|
||||||
|
ctx.fireChannelRegistered();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireChannelUnregistered() {
|
||||||
|
ctx.fireChannelUnregistered();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireChannelActive() {
|
||||||
|
ctx.fireChannelActive();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireChannelInactive() {
|
||||||
|
ctx.fireChannelInactive();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireExceptionCaught(Throwable cause) {
|
||||||
|
ctx.fireExceptionCaught(cause);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireUserEventTriggered(Object event) {
|
||||||
|
ctx.fireUserEventTriggered(event);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireChannelRead(Object msg) {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireChannelReadComplete() {
|
||||||
|
ctx.fireChannelReadComplete();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext fireChannelWritabilityChanged() {
|
||||||
|
ctx.fireChannelWritabilityChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture bind(SocketAddress localAddress) {
|
||||||
|
return ctx.bind(localAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress remoteAddress) {
|
||||||
|
return ctx.connect(remoteAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
|
||||||
|
return ctx.connect(remoteAddress, localAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture disconnect() {
|
||||||
|
return ctx.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture close() {
|
||||||
|
return ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture deregister() {
|
||||||
|
return ctx.deregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
|
||||||
|
return ctx.bind(localAddress, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
|
||||||
|
return ctx.connect(remoteAddress, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(
|
||||||
|
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
|
||||||
|
return ctx.connect(remoteAddress, localAddress, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture disconnect(ChannelPromise promise) {
|
||||||
|
return ctx.disconnect(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture close(ChannelPromise promise) {
|
||||||
|
return ctx.close(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture deregister(ChannelPromise promise) {
|
||||||
|
return ctx.deregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext read() {
|
||||||
|
ctx.read();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture write(Object msg) {
|
||||||
|
return ctx.write(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture write(Object msg, ChannelPromise promise) {
|
||||||
|
return ctx.write(msg, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext flush() {
|
||||||
|
ctx.flush();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
|
||||||
|
return ctx.writeAndFlush(msg, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture writeAndFlush(Object msg) {
|
||||||
|
return ctx.writeAndFlush(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline pipeline() {
|
||||||
|
return ctx.pipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBufAllocator alloc() {
|
||||||
|
return ctx.alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPromise newPromise() {
|
||||||
|
return ctx.newPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelProgressivePromise newProgressivePromise() {
|
||||||
|
return ctx.newProgressivePromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture newSucceededFuture() {
|
||||||
|
return ctx.newSucceededFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture newFailedFuture(Throwable cause) {
|
||||||
|
return ctx.newFailedFuture(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPromise voidPromise() {
|
||||||
|
return ctx.voidPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Attribute<T> attr(AttributeKey<T> key) {
|
||||||
|
return ctx.attr(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
final void remove() {
|
||||||
|
EventExecutor executor = executor();
|
||||||
|
if (executor.inEventLoop()) {
|
||||||
|
remove0();
|
||||||
|
} else {
|
||||||
|
executor.execute(new OneTimeTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
remove0();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove0() {
|
||||||
|
if (!removed) {
|
||||||
|
removed = true;
|
||||||
|
try {
|
||||||
|
handler.handlerRemoved(this);
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
fireExceptionCaught(new ChannelPipelineException(
|
||||||
|
handler.getClass().getName() + ".handlerRemoved() has thrown an exception.", cause));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,324 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 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.channel.embedded.EmbeddedChannel;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class CombinedChannelDuplexHandlerTest {
|
||||||
|
|
||||||
|
private static final Object MSG = new Object();
|
||||||
|
private static final SocketAddress ADDRESS = new InetSocketAddress(0);
|
||||||
|
|
||||||
|
private enum Event {
|
||||||
|
REGISTERED,
|
||||||
|
UNREGISTERED,
|
||||||
|
ACTIVE,
|
||||||
|
INACTIVE,
|
||||||
|
CHANNEL_READ,
|
||||||
|
CHANNEL_READ_COMPLETE,
|
||||||
|
EXCEPTION_CAUGHT,
|
||||||
|
USER_EVENT_TRIGGERED,
|
||||||
|
CHANNEL_WRITABILITY_CHANGED,
|
||||||
|
HANDLER_ADDED,
|
||||||
|
HANDLER_REMOVED,
|
||||||
|
BIND,
|
||||||
|
CONNECT,
|
||||||
|
WRITE,
|
||||||
|
FLUSH,
|
||||||
|
READ,
|
||||||
|
REGISTER,
|
||||||
|
DEREGISTER,
|
||||||
|
CLOSE,
|
||||||
|
DISCONNECT
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testInboundRemoveBeforeAdded() {
|
||||||
|
CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler> handler =
|
||||||
|
new CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler>(
|
||||||
|
new ChannelInboundHandlerAdapter(), new ChannelOutboundHandlerAdapter());
|
||||||
|
handler.removeInboundHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testOutboundRemoveBeforeAdded() {
|
||||||
|
CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler> handler =
|
||||||
|
new CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler>(
|
||||||
|
new ChannelInboundHandlerAdapter(), new ChannelOutboundHandlerAdapter());
|
||||||
|
handler.removeOutboundHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testInboundHandlerImplementsOutboundHandler() {
|
||||||
|
new CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler>(
|
||||||
|
new ChannelDuplexHandler(), new ChannelOutboundHandlerAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testOutboundHandlerImplementsInbboundHandler() {
|
||||||
|
new CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler>(
|
||||||
|
new ChannelInboundHandlerAdapter(), new ChannelDuplexHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testInitNotCalledBeforeAdded() throws Exception {
|
||||||
|
CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler> handler =
|
||||||
|
new CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler>() { };
|
||||||
|
handler.handlerAdded(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExceptionCaughtBothCombinedHandlers() {
|
||||||
|
final Exception exception = new Exception();
|
||||||
|
final Queue<ChannelHandler> queue = new ArrayDeque<ChannelHandler>();
|
||||||
|
|
||||||
|
ChannelInboundHandler inboundHandler = new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
assertSame(exception, cause);
|
||||||
|
queue.add(this);
|
||||||
|
ctx.fireExceptionCaught(cause);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ChannelOutboundHandler outboundHandler = new ChannelOutboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
assertSame(exception, cause);
|
||||||
|
queue.add(this);
|
||||||
|
ctx.fireExceptionCaught(cause);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ChannelInboundHandler lastHandler = new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
assertSame(exception, cause);
|
||||||
|
queue.add(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
EmbeddedChannel channel = new EmbeddedChannel(
|
||||||
|
new CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler>(
|
||||||
|
inboundHandler, outboundHandler), lastHandler);
|
||||||
|
channel.pipeline().fireExceptionCaught(exception);
|
||||||
|
assertFalse(channel.finish());
|
||||||
|
assertSame(inboundHandler, queue.poll());
|
||||||
|
assertSame(outboundHandler, queue.poll());
|
||||||
|
assertSame(lastHandler, queue.poll());
|
||||||
|
assertTrue(queue.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInboundEvents() {
|
||||||
|
final Queue<Event> queue = new ArrayDeque<Event>();
|
||||||
|
|
||||||
|
ChannelInboundHandler inboundHandler = new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.HANDLER_ADDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.HANDLER_REMOVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.REGISTERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.UNREGISTERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.INACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
queue.add(Event.CHANNEL_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.CHANNEL_READ_COMPLETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
queue.add(Event.USER_EVENT_TRIGGERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.CHANNEL_WRITABILITY_CHANGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
queue.add(Event.EXCEPTION_CAUGHT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler> handler =
|
||||||
|
new CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler>(
|
||||||
|
inboundHandler, new ChannelOutboundHandlerAdapter());
|
||||||
|
|
||||||
|
EmbeddedChannel channel = new EmbeddedChannel(handler);
|
||||||
|
channel.pipeline().fireChannelWritabilityChanged();
|
||||||
|
channel.pipeline().fireUserEventTriggered(MSG);
|
||||||
|
channel.pipeline().fireChannelRead(MSG);
|
||||||
|
channel.pipeline().fireChannelReadComplete();
|
||||||
|
|
||||||
|
assertEquals(Event.HANDLER_ADDED, queue.poll());
|
||||||
|
assertEquals(Event.REGISTERED, queue.poll());
|
||||||
|
assertEquals(Event.ACTIVE, queue.poll());
|
||||||
|
assertEquals(Event.CHANNEL_WRITABILITY_CHANGED, queue.poll());
|
||||||
|
assertEquals(Event.USER_EVENT_TRIGGERED, queue.poll());
|
||||||
|
assertEquals(Event.CHANNEL_READ, queue.poll());
|
||||||
|
assertEquals(Event.CHANNEL_READ_COMPLETE, queue.poll());
|
||||||
|
|
||||||
|
handler.removeInboundHandler();
|
||||||
|
assertEquals(Event.HANDLER_REMOVED, queue.poll());
|
||||||
|
|
||||||
|
// These should not be handled by the inboundHandler anymore as it was removed before
|
||||||
|
channel.pipeline().fireChannelWritabilityChanged();
|
||||||
|
channel.pipeline().fireUserEventTriggered(MSG);
|
||||||
|
channel.pipeline().fireChannelRead(MSG);
|
||||||
|
channel.pipeline().fireChannelReadComplete();
|
||||||
|
|
||||||
|
// Should have not received any more events as it was removed before via removeInboundHandler()
|
||||||
|
assertTrue(queue.isEmpty());
|
||||||
|
assertTrue(channel.finish());
|
||||||
|
assertTrue(queue.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOutboundEvents() {
|
||||||
|
final Queue<Event> queue = new ArrayDeque<Event>();
|
||||||
|
|
||||||
|
ChannelInboundHandler inboundHandler = new ChannelInboundHandlerAdapter();
|
||||||
|
ChannelOutboundHandler outboundHandler = new ChannelOutboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.HANDLER_ADDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.HANDLER_REMOVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
|
||||||
|
throws Exception {
|
||||||
|
queue.add(Event.BIND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
||||||
|
SocketAddress localAddress, ChannelPromise promise) throws Exception {
|
||||||
|
queue.add(Event.CONNECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||||
|
queue.add(Event.DISCONNECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||||
|
queue.add(Event.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||||
|
queue.add(Event.DEREGISTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
queue.add(Event.WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
queue.add(Event.FLUSH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler> handler =
|
||||||
|
new CombinedChannelDuplexHandler<ChannelInboundHandler, ChannelOutboundHandler>(
|
||||||
|
inboundHandler, outboundHandler);
|
||||||
|
|
||||||
|
EmbeddedChannel channel = new EmbeddedChannel();
|
||||||
|
channel.pipeline().addFirst(handler);
|
||||||
|
|
||||||
|
doOutboundOperations(channel);
|
||||||
|
|
||||||
|
assertEquals(Event.HANDLER_ADDED, queue.poll());
|
||||||
|
assertEquals(Event.BIND, queue.poll());
|
||||||
|
assertEquals(Event.CONNECT, queue.poll());
|
||||||
|
assertEquals(Event.WRITE, queue.poll());
|
||||||
|
assertEquals(Event.FLUSH, queue.poll());
|
||||||
|
assertEquals(Event.READ, queue.poll());
|
||||||
|
assertEquals(Event.CLOSE, queue.poll());
|
||||||
|
assertEquals(Event.CLOSE, queue.poll());
|
||||||
|
assertEquals(Event.DEREGISTER, queue.poll());
|
||||||
|
|
||||||
|
handler.removeOutboundHandler();
|
||||||
|
assertEquals(Event.HANDLER_REMOVED, queue.poll());
|
||||||
|
|
||||||
|
// These should not be handled by the inboundHandler anymore as it was removed before
|
||||||
|
doOutboundOperations(channel);
|
||||||
|
|
||||||
|
// Should have not received any more events as it was removed before via removeInboundHandler()
|
||||||
|
assertTrue(queue.isEmpty());
|
||||||
|
assertTrue(channel.finish());
|
||||||
|
assertTrue(queue.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void doOutboundOperations(Channel channel) {
|
||||||
|
channel.pipeline().bind(ADDRESS);
|
||||||
|
channel.pipeline().connect(ADDRESS);
|
||||||
|
channel.pipeline().write(MSG);
|
||||||
|
channel.pipeline().flush();
|
||||||
|
channel.pipeline().read();
|
||||||
|
channel.pipeline().disconnect();
|
||||||
|
channel.pipeline().close();
|
||||||
|
channel.pipeline().deregister();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user