/* * Copyright 2012 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.buffer.ByteBufAllocator; import io.netty.util.DefaultAttributeMap; import io.netty.util.Recycler; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.internal.OneTimeTask; import io.netty.util.internal.RecyclableMpscLinkedQueueNode; import io.netty.util.internal.StringUtil; import java.net.SocketAddress; import static io.netty.channel.DefaultChannelPipeline.*; abstract class AbstractChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext { volatile AbstractChannelHandlerContext next; volatile AbstractChannelHandlerContext prev; private final boolean inbound; private final boolean outbound; private final AbstractChannel channel; private final DefaultChannelPipeline pipeline; private final String name; /** * Set when the {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} of * this context's handler is invoked. * Cleared when a user calls {@link #fireChannelReadComplete()} on this context. * * See {@link #fireChannelReadComplete()} to understand how this flag is used. */ boolean invokedThisChannelRead; /** * Set when a user calls {@link #fireChannelRead(Object)} on this context. * Cleared when a user calls {@link #fireChannelReadComplete()} on this context. * * See {@link #fireChannelReadComplete()} to understand how this flag is used. */ private volatile boolean invokedNextChannelRead; /** * Set when a user calls {@link #read()} on this context. * Cleared when a user calls {@link #fireChannelReadComplete()} on this context. * * See {@link #fireChannelReadComplete()} to understand how this flag is used. */ private volatile boolean invokedPrevRead; /** * {@code true} if and only if this context has been removed from the pipeline. */ private boolean removed; // Will be set to null if no child executor should be used, otherwise it will be set to the // child executor. final EventExecutor executor; private ChannelFuture succeededFuture; // Lazily instantiated tasks used to trigger events to a handler with different executor. // These needs to be volatile as otherwise an other Thread may see an half initialized instance. // See the JMM for more details private volatile Runnable invokeChannelReadCompleteTask; private volatile Runnable invokeReadTask; private volatile Runnable invokeChannelWritableStateChangedTask; private volatile Runnable invokeFlushTask; AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, boolean inbound, boolean outbound) { if (name == null) { throw new NullPointerException("name"); } channel = pipeline.channel; this.pipeline = pipeline; this.name = name; if (group != null) { // Pin one of the child executors once and remember it so that the same child executor // is used to fire events for the same channel. EventExecutor childExecutor = pipeline.childExecutors.get(group); if (childExecutor == null) { childExecutor = group.next(); pipeline.childExecutors.put(group, childExecutor); } executor = childExecutor; } else { executor = null; } this.inbound = inbound; this.outbound = outbound; } @Override public Channel channel() { return channel; } @Override public ChannelPipeline pipeline() { return pipeline; } @Override public ByteBufAllocator alloc() { return channel().config().getAllocator(); } @Override public EventExecutor executor() { if (executor == null) { return channel().eventLoop(); } else { return executor; } } @Override public String name() { return name; } @Override public ChannelHandlerContext fireChannelRegistered() { final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRegistered(); } else { executor.execute(new OneTimeTask() { @Override public void run() { next.invokeChannelRegistered(); } }); } return this; } private void invokeChannelRegistered() { try { ((ChannelInboundHandler) handler()).channelRegistered(this); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelHandlerContext fireChannelUnregistered() { final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelUnregistered(); } else { executor.execute(new OneTimeTask() { @Override public void run() { next.invokeChannelUnregistered(); } }); } return this; } private void invokeChannelUnregistered() { try { ((ChannelInboundHandler) handler()).channelUnregistered(this); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelHandlerContext fireChannelActive() { final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelActive(); } else { executor.execute(new OneTimeTask() { @Override public void run() { next.invokeChannelActive(); } }); } return this; } private void invokeChannelActive() { try { ((ChannelInboundHandler) handler()).channelActive(this); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelHandlerContext fireChannelInactive() { final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelInactive(); } else { executor.execute(new OneTimeTask() { @Override public void run() { next.invokeChannelInactive(); } }); } return this; } private void invokeChannelInactive() { try { ((ChannelInboundHandler) handler()).channelInactive(this); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelHandlerContext fireExceptionCaught(final Throwable cause) { if (cause == null) { throw new NullPointerException("cause"); } final AbstractChannelHandlerContext next = this.next; EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeExceptionCaught(cause); } else { try { executor.execute(new OneTimeTask() { @Override public void run() { next.invokeExceptionCaught(cause); } }); } catch (Throwable t) { if (logger.isWarnEnabled()) { logger.warn("Failed to submit an exceptionCaught() event.", t); logger.warn("The exceptionCaught() event that was failed to submit was:", cause); } } } return this; } private void invokeExceptionCaught(final Throwable cause) { try { handler().exceptionCaught(this, cause); } catch (Throwable t) { if (logger.isWarnEnabled()) { logger.warn( "An exception was thrown by a user handler's " + "exceptionCaught() method while handling the following exception:", cause); } } } @Override public ChannelHandlerContext fireUserEventTriggered(final Object event) { if (event == null) { throw new NullPointerException("event"); } final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeUserEventTriggered(event); } else { executor.execute(new OneTimeTask() { @Override public void run() { next.invokeUserEventTriggered(event); } }); } return this; } private void invokeUserEventTriggered(Object event) { try { ((ChannelInboundHandler) handler()).userEventTriggered(this, event); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelHandlerContext fireChannelRead(final Object msg) { if (msg == null) { throw new NullPointerException("msg"); } invokedNextChannelRead = true; final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRead(msg); } else { executor.execute(new OneTimeTask() { @Override public void run() { next.invokeChannelRead(msg); } }); } return this; } private void invokeChannelRead(Object msg) { invokedThisChannelRead = true; try { ((ChannelInboundHandler) handler()).channelRead(this, msg); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelHandlerContext fireChannelReadComplete() { /** * If the handler of this context did not produce any messages via {@link #fireChannelRead(Object)}, * there's no reason to trigger {@code channelReadComplete()} even if the handler called this method. * * This is pretty common for the handlers that transform multiple messages into one message, * such as byte-to-message decoder and message aggregators. * * Only one exception is when nobody invoked the channelRead() method of this context's handler. * It means the handler has been added later dynamically. */ if (invokedNextChannelRead || // The handler of this context produced a message, or !invokedThisChannelRead) { // it is not required to produce a message to trigger the event. invokedNextChannelRead = false; invokedPrevRead = false; final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelReadComplete(); } else { Runnable task = next.invokeChannelReadCompleteTask; if (task == null) { next.invokeChannelReadCompleteTask = task = new Runnable() { @Override public void run() { next.invokeChannelReadComplete(); } }; } executor.execute(task); } return this; } /** * At this point, we are sure the handler of this context did not produce anything and * we suppressed the {@code channelReadComplete()} event. * * If the next handler invoked {@link #read()} to read something but nothing was produced * by the handler of this context, someone has to issue another {@link #read()} operation * until the handler of this context produces something. * * Why? Because otherwise the next handler will not receive {@code channelRead()} nor * {@code channelReadComplete()} event at all for the {@link #read()} operation it issued. */ if (invokedPrevRead && !channel().config().isAutoRead()) { /** * The next (or upstream) handler invoked {@link #read()}, but it didn't get any * {@code channelRead()} event. We should read once more, so that the handler of the current * context have a chance to produce something. */ read(); } else { invokedPrevRead = false; } return this; } private void invokeChannelReadComplete() { try { ((ChannelInboundHandler) handler()).channelReadComplete(this); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelHandlerContext fireChannelWritabilityChanged() { final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelWritabilityChanged(); } else { Runnable task = next.invokeChannelWritableStateChangedTask; if (task == null) { next.invokeChannelWritableStateChangedTask = task = new Runnable() { @Override public void run() { next.invokeChannelWritabilityChanged(); } }; } executor.execute(task); } return this; } private void invokeChannelWritabilityChanged() { try { ((ChannelInboundHandler) handler()).channelWritabilityChanged(this); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelFuture bind(SocketAddress localAddress) { return bind(localAddress, newPromise()); } @Override public ChannelFuture connect(SocketAddress remoteAddress) { return connect(remoteAddress, newPromise()); } @Override public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { return connect(remoteAddress, localAddress, newPromise()); } @Override public ChannelFuture disconnect() { return disconnect(newPromise()); } @Override public ChannelFuture close() { return close(newPromise()); } @Override public ChannelFuture deregister() { return deregister(newPromise()); } @Override public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) { if (localAddress == null) { throw new NullPointerException("localAddress"); } if (!validatePromise(promise, false)) { // cancelled return promise; } final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeBind(localAddress, promise); } else { safeExecute(executor, new OneTimeTask() { @Override public void run() { next.invokeBind(localAddress, promise); } }, promise, null); } return promise; } private void invokeBind(SocketAddress localAddress, ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } } @Override public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { return connect(remoteAddress, null, promise); } @Override public ChannelFuture connect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { if (remoteAddress == null) { throw new NullPointerException("remoteAddress"); } if (!validatePromise(promise, false)) { // cancelled return promise; } final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeConnect(remoteAddress, localAddress, promise); } else { safeExecute(executor, new OneTimeTask() { @Override public void run() { next.invokeConnect(remoteAddress, localAddress, promise); } }, promise, null); } return promise; } private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } } @Override public ChannelFuture disconnect(final ChannelPromise promise) { if (!validatePromise(promise, false)) { // cancelled return promise; } final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { // Translate disconnect to close if the channel has no notion of disconnect-reconnect. // So far, UDP/IP is the only transport that has such behavior. if (!channel().metadata().hasDisconnect()) { next.invokeClose(promise); } else { next.invokeDisconnect(promise); } } else { safeExecute(executor, new OneTimeTask() { @Override public void run() { if (!channel().metadata().hasDisconnect()) { next.invokeClose(promise); } else { next.invokeDisconnect(promise); } } }, promise, null); } return promise; } private void invokeDisconnect(ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).disconnect(this, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } } @Override public ChannelFuture close(final ChannelPromise promise) { if (!validatePromise(promise, false)) { // cancelled return promise; } final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeClose(promise); } else { safeExecute(executor, new OneTimeTask() { @Override public void run() { next.invokeClose(promise); } }, promise, null); } return promise; } private void invokeClose(ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).close(this, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } } @Override public ChannelFuture deregister(final ChannelPromise promise) { if (!validatePromise(promise, false)) { // cancelled return promise; } final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeDeregister(promise); } else { safeExecute(executor, new OneTimeTask() { @Override public void run() { next.invokeDeregister(promise); } }, promise, null); } return promise; } private void invokeDeregister(ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).deregister(this, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } } @Override public ChannelHandlerContext read() { invokedPrevRead = true; final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeRead(); } else { Runnable task = next.invokeReadTask; if (task == null) { next.invokeReadTask = task = new Runnable() { @Override public void run() { next.invokeRead(); } }; } executor.execute(task); } return this; } private void invokeRead() { try { ((ChannelOutboundHandler) handler()).read(this); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelFuture write(Object msg) { return write(msg, newPromise()); } @Override public ChannelFuture write(final Object msg, final ChannelPromise promise) { if (msg == null) { throw new NullPointerException("msg"); } if (!validatePromise(promise, true)) { ReferenceCountUtil.release(msg); // cancelled return promise; } write(msg, false, promise); return promise; } private void invokeWrite(Object msg, ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).write(this, msg, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } } @Override public ChannelHandlerContext flush() { final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeFlush(); } else { Runnable task = next.invokeFlushTask; if (task == null) { next.invokeFlushTask = task = new Runnable() { @Override public void run() { next.invokeFlush(); } }; } safeExecute(executor, task, channel.voidPromise(), null); } return this; } private void invokeFlush() { try { ((ChannelOutboundHandler) handler()).flush(this); } catch (Throwable t) { notifyHandlerException(t); } } @Override public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { if (msg == null) { throw new NullPointerException("msg"); } if (!validatePromise(promise, true)) { ReferenceCountUtil.release(msg); // cancelled return promise; } write(msg, true, promise); return promise; } private void write(Object msg, boolean flush, ChannelPromise promise) { AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeWrite(msg, promise); if (flush) { next.invokeFlush(); } } else { int size = channel.estimatorHandle().size(msg); if (size > 0) { ChannelOutboundBuffer buffer = channel.unsafe().outboundBuffer(); // Check for null as it may be set to null if the channel is closed already if (buffer != null) { buffer.incrementPendingOutboundBytes(size); } } Runnable task; if (flush) { task = WriteAndFlushTask.newInstance(next, msg, size, promise); } else { task = WriteTask.newInstance(next, msg, size, promise); } safeExecute(executor, task, promise, msg); } } @Override public ChannelFuture writeAndFlush(Object msg) { return writeAndFlush(msg, newPromise()); } private static void notifyOutboundHandlerException(Throwable cause, ChannelPromise promise) { if (!promise.tryFailure(cause) && !(promise instanceof VoidChannelPromise)) { if (logger.isWarnEnabled()) { logger.warn("Failed to fail the promise because it's done already: {}", promise, cause); } } } private void notifyHandlerException(Throwable cause) { if (inExceptionCaught(cause)) { if (logger.isWarnEnabled()) { logger.warn( "An exception was thrown by a user handler " + "while handling an exceptionCaught event", cause); } return; } invokeExceptionCaught(cause); } private static boolean inExceptionCaught(Throwable cause) { do { StackTraceElement[] trace = cause.getStackTrace(); if (trace != null) { for (StackTraceElement t : trace) { if (t == null) { break; } if ("exceptionCaught".equals(t.getMethodName())) { return true; } } } cause = cause.getCause(); } while (cause != null); return false; } @Override public ChannelPromise newPromise() { return new DefaultChannelPromise(channel(), executor()); } @Override public ChannelProgressivePromise newProgressivePromise() { return new DefaultChannelProgressivePromise(channel(), executor()); } @Override public ChannelFuture newSucceededFuture() { ChannelFuture succeededFuture = this.succeededFuture; if (succeededFuture == null) { this.succeededFuture = succeededFuture = new SucceededChannelFuture(channel(), executor()); } return succeededFuture; } @Override public ChannelFuture newFailedFuture(Throwable cause) { return new FailedChannelFuture(channel(), executor(), cause); } private boolean validatePromise(ChannelPromise promise, boolean allowVoidPromise) { if (promise == null) { throw new NullPointerException("promise"); } if (promise.isDone()) { // Check if the promise was cancelled and if so signal that the processing of the operation // should not be performed. // // See https://github.com/netty/netty/issues/2349 if (promise.isCancelled()) { return false; } throw new IllegalArgumentException("promise already done: " + promise); } if (promise.channel() != channel()) { throw new IllegalArgumentException(String.format( "promise.channel does not match: %s (expected: %s)", promise.channel(), channel())); } if (promise.getClass() == DefaultChannelPromise.class) { return true; } if (!allowVoidPromise && promise instanceof VoidChannelPromise) { throw new IllegalArgumentException( StringUtil.simpleClassName(VoidChannelPromise.class) + " not allowed for this operation"); } if (promise instanceof AbstractChannel.CloseFuture) { throw new IllegalArgumentException( StringUtil.simpleClassName(AbstractChannel.CloseFuture.class) + " not allowed in a pipeline"); } return true; } private AbstractChannelHandlerContext findContextInbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.next; } while (!ctx.inbound); return ctx; } private AbstractChannelHandlerContext findContextOutbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while (!ctx.outbound); return ctx; } @Override public ChannelPromise voidPromise() { return channel.voidPromise(); } void setRemoved() { removed = true; } @Override public boolean isRemoved() { return removed; } private static void safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg) { try { executor.execute(runnable); } catch (Throwable cause) { try { promise.setFailure(cause); } finally { if (msg != null) { ReferenceCountUtil.release(msg); } } } } abstract static class AbstractWriteTask extends RecyclableMpscLinkedQueueNode<Runnable> implements Runnable { private AbstractChannelHandlerContext ctx; private Object msg; private ChannelPromise promise; private int size; private AbstractWriteTask(Recycler.Handle handle) { super(handle); } protected static void init(AbstractWriteTask task, AbstractChannelHandlerContext ctx, Object msg, int size, ChannelPromise promise) { task.ctx = ctx; task.msg = msg; task.promise = promise; task.size = size; } @Override public final void run() { try { if (size > 0) { ChannelOutboundBuffer buffer = ctx.channel.unsafe().outboundBuffer(); // Check for null as it may be set to null if the channel is closed already if (buffer != null) { buffer.decrementPendingOutboundBytes(size); } } write(ctx, msg, promise); } finally { // Set to null so the GC can collect them directly ctx = null; msg = null; promise = null; } } @Override public Runnable value() { return this; } protected void write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) { ctx.invokeWrite(msg, promise); } } static final class WriteTask extends AbstractWriteTask implements SingleThreadEventLoop.NonWakeupRunnable { private static final Recycler<WriteTask> RECYCLER = new Recycler<WriteTask>() { @Override protected WriteTask newObject(Handle handle) { return new WriteTask(handle); } }; private static WriteTask newInstance( AbstractChannelHandlerContext ctx, Object msg, int size, ChannelPromise promise) { WriteTask task = RECYCLER.get(); init(task, ctx, msg, size, promise); return task; } private WriteTask(Recycler.Handle handle) { super(handle); } @Override protected void recycle(Recycler.Handle handle) { RECYCLER.recycle(this, handle); } } static final class WriteAndFlushTask extends AbstractWriteTask { private static final Recycler<WriteAndFlushTask> RECYCLER = new Recycler<WriteAndFlushTask>() { @Override protected WriteAndFlushTask newObject(Handle handle) { return new WriteAndFlushTask(handle); } }; private static WriteAndFlushTask newInstance( AbstractChannelHandlerContext ctx, Object msg, int size, ChannelPromise promise) { WriteAndFlushTask task = RECYCLER.get(); init(task, ctx, msg, size, promise); return task; } private WriteAndFlushTask(Recycler.Handle handle) { super(handle); } @Override public void write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) { super.write(ctx, msg, promise); ctx.invokeFlush(); } @Override protected void recycle(Recycler.Handle handle) { RECYCLER.recycle(this, handle); } } }