Clean up the pipeline implementation / Ensure Embedded*Channel does not run pending tasks immediately

- Replace ugly 'prev != null' check with explicit event scheduling
- Fix an incorrect flag operation in freeHandlerBuffersAfterRemoval()
- Fix a bug in AbstractEmbeddedChannel.doRegister where it makes pending tasks immediately, where the pending tasks actually triggers inbound events
- Remove unnecessary suppression of inboundBufferUpdated() event in DefaultChannelPipeline, which potentially hides an event ordering bug. Unfortunately, I don't remember why I added it in cca35454d2.
This commit is contained in:
Trustin Lee 2013-04-22 19:40:23 +09:00
parent 9c4bfa44d9
commit e80fb65c36
4 changed files with 90 additions and 46 deletions

View File

@ -675,7 +675,12 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha
doDisconnect();
promise.setSuccess();
if (wasActive && !isActive()) {
pipeline.fireChannelInactive();
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelInactive();
}
});
}
} catch (Throwable t) {
promise.setFailure(t);
@ -710,7 +715,12 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha
flushFutureNotifier.notifyFlushFutures(closedChannelException);
if (wasActive && !isActive()) {
pipeline.fireChannelInactive();
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelInactive();
}
});
}
deregister(voidFuture());
@ -754,7 +764,12 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha
if (registered) {
registered = false;
promise.setSuccess();
pipeline.fireChannelUnregistered();
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelUnregistered();
}
});
} else {
// Some transports like local and AIO does not allow the deregistration of
// an open channel. Their doDeregister() calls close(). Consequently,
@ -785,8 +800,13 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha
if (eventLoop().inEventLoop()) {
try {
doBeginRead();
} catch (Exception e) {
pipeline().fireExceptionCaught(e);
} catch (final Exception e) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireExceptionCaught(e);
}
});
close(unsafe().voidFuture());
}
} else {
@ -849,10 +869,11 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha
private void flush0() {
if (!inFlushNow) { // Avoid re-entrance
try {
// Flush immediately only when there's no pending flush.
// If there's a pending flush operation, event loop will call flushNow() later,
// and thus there's no need to call it now.
if (!isFlushPending()) {
flushNow();
} else {
// Event loop will call flushNow() later by itself.
}
} catch (Throwable t) {
flushFutureNotifier.notifyFlushFutures(t);
@ -930,6 +951,21 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha
}
close(voidFuture());
}
private void invokeLater(Runnable task) {
// This method is used by outbound operation implementations to trigger an inbound event later.
// They do not trigger an inbound event immediately because an outbound operation might have been
// triggered by another inbound event handler method. If fired immediately, the call stack
// will look like this for example:
//
// handlerA.inboundBufferUpdated() - (1) an inbound handler method closes a connection.
// -> handlerA.ctx.close()
// -> channel.unsafe.close()
// -> handlerA.channelInactive() - (2) another inbound handler method called while in (1) yet
//
// which means the execution of two inbound handler methods of the same handler overlap undesirably.
eventLoop().execute(task);
}
}
/**

View File

@ -24,11 +24,14 @@ import io.netty.buffer.Unpooled;
import io.netty.util.DefaultAttributeMap;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.PlatformDependent;
import java.net.SocketAddress;
import java.nio.channels.ClosedChannelException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import static io.netty.channel.DefaultChannelPipeline.*;
@ -309,17 +312,42 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
void initHeadHandler() {
// Must be called for the head handler.
HeadHandler h = (HeadHandler) handler;
if (h.initialized) {
return;
EventExecutor executor = executor();
if (executor.inEventLoop()) {
HeadHandler h = (HeadHandler) handler;
if (h.initialized) {
return;
}
h.init(this);
h.initialized = true;
outByteBuf = h.byteSink;
outMsgBuf = h.msgSink;
} else {
Future<?> f = executor.submit(new Runnable() {
@Override
public void run() {
initHeadHandler();
}
});
boolean interrupted = false;
try {
while (!f.isDone()) {
try {
f.get();
} catch (InterruptedException e) {
interrupted = true;
} catch (ExecutionException e) {
PlatformDependent.throwException(e);
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
assert executor().inEventLoop();
h.init(this);
h.initialized = true;
outByteBuf = h.byteSink;
outMsgBuf = h.msgSink;
}
private boolean flushInboundBridge() {
@ -401,8 +429,9 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
}
private void freeHandlerBuffersAfterRemoval() {
if (flags == FLAG_REMOVED) { // Removed, but not freed yet
flags |= FLAG_FREED;
int flags = this.flags;
if ((flags & FLAG_REMOVED) != 0 && (flags & FLAG_FREED) == 0) { // Removed, but not freed yet
this.flags |= FLAG_FREED;
final ChannelHandler handler = handler();
@ -691,7 +720,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
public ChannelHandlerContext fireChannelUnregistered() {
final DefaultChannelHandlerContext next = findContextInbound();
EventExecutor executor = next.executor();
if (prev != null && executor.inEventLoop()) {
if (executor.inEventLoop()) {
next.invokeChannelUnregistered();
} else {
executor.execute(new Runnable() {
@ -743,7 +772,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
public ChannelHandlerContext fireChannelInactive() {
final DefaultChannelHandlerContext next = findContextInbound();
EventExecutor executor = next.executor();
if (prev != null && executor.inEventLoop()) {
if (executor.inEventLoop()) {
next.invokeChannelInactive();
} else {
executor.execute(new Runnable() {
@ -778,7 +807,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
private void invokeExceptionCaught(final Throwable cause) {
EventExecutor executor = executor();
if (prev != null && executor.inEventLoop()) {
if (executor.inEventLoop()) {
invokeExceptionCaught0(cause);
} else {
try {
@ -897,7 +926,6 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
private void invokeInboundBufferUpdated() {
ChannelStateHandler handler = (ChannelStateHandler) handler();
if (handler instanceof ChannelInboundHandler) {
for (;;) {
try {
@ -1424,7 +1452,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
void invokeFreeInboundBuffer() {
EventExecutor executor = executor();
if (prev != null && executor.inEventLoop()) {
if (executor.inEventLoop()) {
invokeFreeInboundBuffer0();
} else {
executor.execute(new Runnable() {
@ -1517,11 +1545,7 @@ final class DefaultChannelHandlerContext extends DefaultAttributeMap implements
return;
}
if (handler() instanceof ChannelStateHandler) {
invokeExceptionCaught(cause);
} else {
findContextInbound().invokeExceptionCaught(cause);
}
invokeExceptionCaught(cause);
}
private static boolean inExceptionCaught(Throwable cause) {

View File

@ -65,8 +65,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
private final Map<String, DefaultChannelHandlerContext> name2ctx =
new HashMap<String, DefaultChannelHandlerContext>(4);
private boolean firedChannelActive;
private boolean fireInboundBufferUpdatedOnActivation;
final Map<EventExecutorGroup, EventExecutor> childExecutors =
new IdentityHashMap<EventExecutorGroup, EventExecutor>();
@ -814,7 +812,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
@Override
public ChannelPipeline fireChannelActive() {
firedChannelActive = true;
head.initHeadHandler();
head.fireChannelActive();
@ -822,10 +819,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
channel.read();
}
if (fireInboundBufferUpdatedOnActivation) {
fireInboundBufferUpdatedOnActivation = false;
head.fireInboundBufferUpdated();
}
return this;
}
@ -853,10 +846,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
@Override
public ChannelPipeline fireInboundBufferUpdated() {
if (!firedChannelActive) {
fireInboundBufferUpdatedOnActivation = true;
return this;
}
head.fireInboundBufferUpdated();
return this;
}

View File

@ -226,12 +226,7 @@ public abstract class AbstractEmbeddedChannel<O> extends AbstractChannel {
@Override
protected Runnable doDeregister() throws Exception {
return new Runnable() {
@Override
public void run() {
runPendingTasks();
}
};
return null;
}
@Override