ae0fbb45e4
Motivation: At the moment the next / prev references are not set to "null" in the DefaultChannelHandlerContext once the ChannelHandler is removed. This is bad as it basically let users still use the ChannelHandlerContext of a ChannelHandler after it is removed and may produce very suprising behaviour. Modifications: - Fail if someone tries to use the ChannelHandlerContext once the ChannelHandler was removed (for outbound operations fail the promise, for inbound fire the error through the ChannelPipeline) - Fix some handlers to ensure we not use the ChannelHandlerContext after the handler was removed - Adjust DefaultChannelPipeline / DefaultChannelHandlerContext to fixes races with removal / replacement of handlers Result: Cleanup behaviour and make it more predictable for pipeline modifications
1169 lines
36 KiB
Java
1169 lines
36 KiB
Java
/*
|
|
* 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 static java.util.Objects.requireNonNull;
|
|
|
|
import io.netty.buffer.ByteBufAllocator;
|
|
import io.netty.util.Attribute;
|
|
import io.netty.util.AttributeKey;
|
|
import io.netty.util.ReferenceCountUtil;
|
|
import io.netty.util.ResourceLeakHint;
|
|
import io.netty.util.concurrent.EventExecutor;
|
|
import io.netty.util.internal.ObjectPool;
|
|
import io.netty.util.internal.PromiseNotificationUtil;
|
|
import io.netty.util.internal.ThrowableUtil;
|
|
import io.netty.util.internal.StringUtil;
|
|
import io.netty.util.internal.SystemPropertyUtil;
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
|
|
import java.net.SocketAddress;
|
|
|
|
import static io.netty.channel.ChannelHandlerMask.*;
|
|
|
|
final class DefaultChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelHandlerContext.class);
|
|
|
|
/**
|
|
* Neither {@link ChannelHandler#handlerAdded(ChannelHandlerContext)}
|
|
* nor {@link ChannelHandler#handlerRemoved(ChannelHandlerContext)} was called.
|
|
*/
|
|
private static final int INIT = 0;
|
|
|
|
/**
|
|
* {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} was called.
|
|
*/
|
|
private static final int ADD_COMPLETE = 1;
|
|
|
|
/**
|
|
* {@link ChannelHandler#handlerRemoved(ChannelHandlerContext)} is about to be called.
|
|
*/
|
|
private static final int REMOVE_STARTED = 2;
|
|
|
|
/**
|
|
* {@link ChannelHandler#handlerRemoved(ChannelHandlerContext)} was called.
|
|
*/
|
|
private static final int REMOVE_COMPLETE = 3;
|
|
|
|
private final int executionMask;
|
|
private final DefaultChannelPipeline pipeline;
|
|
private final ChannelHandler handler;
|
|
private final String name;
|
|
|
|
// Lazily instantiated tasks used to trigger events to a handler with different executor.
|
|
// There is no need to make this volatile as at worse it will just create a few more instances then needed.
|
|
private Tasks invokeTasks;
|
|
private int handlerState = INIT;
|
|
|
|
DefaultChannelHandlerContext next;
|
|
DefaultChannelHandlerContext prev;
|
|
|
|
DefaultChannelHandlerContext(DefaultChannelPipeline pipeline, String name,
|
|
ChannelHandler handler) {
|
|
this.name = requireNonNull(name, "name");
|
|
this.pipeline = pipeline;
|
|
this.executionMask = mask(handler.getClass());
|
|
this.handler = handler;
|
|
}
|
|
|
|
private static void failRemoved(DefaultChannelHandlerContext ctx, ChannelPromise promise) {
|
|
promise.setFailure(newRemovedException(ctx, null));
|
|
}
|
|
|
|
private void notifyHandlerRemovedAlready() {
|
|
notifyHandlerRemovedAlready(null);
|
|
}
|
|
|
|
private void notifyHandlerRemovedAlready(Throwable cause) {
|
|
pipeline().fireExceptionCaught(newRemovedException(this, cause));
|
|
}
|
|
|
|
private static ChannelPipelineException newRemovedException(ChannelHandlerContext ctx, Throwable cause) {
|
|
return new ChannelPipelineException("Context " + ctx + " already removed", cause);
|
|
}
|
|
|
|
private Tasks invokeTasks() {
|
|
Tasks tasks = invokeTasks;
|
|
if (tasks == null) {
|
|
invokeTasks = tasks = new Tasks(this);
|
|
}
|
|
return tasks;
|
|
}
|
|
|
|
@Override
|
|
public EventExecutor executor() {
|
|
return pipeline().executor();
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandler handler() {
|
|
return handler;
|
|
}
|
|
|
|
@Override
|
|
public Channel channel() {
|
|
return pipeline.channel();
|
|
}
|
|
|
|
@Override
|
|
public ChannelPipeline pipeline() {
|
|
return pipeline;
|
|
}
|
|
|
|
@Override
|
|
public ByteBufAllocator alloc() {
|
|
return channel().config().getAllocator();
|
|
}
|
|
|
|
@Override
|
|
public String name() {
|
|
return name;
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireChannelRegistered() {
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeChannelRegistered();
|
|
} else {
|
|
executor.execute(this::findAndInvokeChannelRegistered);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeChannelRegistered() {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_CHANNEL_REGISTERED);
|
|
if (ctx == null) {
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeChannelRegistered();
|
|
}
|
|
|
|
void invokeChannelRegistered() {
|
|
try {
|
|
handler().channelRegistered(this);
|
|
} catch (Throwable t) {
|
|
notifyHandlerException(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireChannelUnregistered() {
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeChannelUnregistered();
|
|
} else {
|
|
executor.execute(this::findAndInvokeChannelUnregistered);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeChannelUnregistered() {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_CHANNEL_UNREGISTERED);
|
|
if (ctx == null) {
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeChannelUnregistered();
|
|
}
|
|
|
|
void invokeChannelUnregistered() {
|
|
try {
|
|
handler().channelUnregistered(this);
|
|
} catch (Throwable t) {
|
|
notifyHandlerException(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireChannelActive() {
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeChannelActive();
|
|
} else {
|
|
executor.execute(this::findAndInvokeChannelActive);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeChannelActive() {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_CHANNEL_ACTIVE);
|
|
if (ctx == null) {
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeChannelActive();
|
|
}
|
|
|
|
void invokeChannelActive() {
|
|
try {
|
|
handler().channelActive(this);
|
|
} catch (Throwable t) {
|
|
notifyHandlerException(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireChannelInactive() {
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeChannelInactive();
|
|
} else {
|
|
executor.execute(this::findAndInvokeChannelInactive);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeChannelInactive() {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_CHANNEL_INACTIVE);
|
|
if (ctx == null) {
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeChannelInactive();
|
|
}
|
|
|
|
void invokeChannelInactive() {
|
|
try {
|
|
handler().channelInactive(this);
|
|
} catch (Throwable t) {
|
|
notifyHandlerException(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireExceptionCaught(Throwable cause) {
|
|
requireNonNull(cause, "cause");
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeExceptionCaught(cause);
|
|
} else {
|
|
try {
|
|
executor.execute(() -> findAndInvokeExceptionCaught(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 findAndInvokeExceptionCaught(Throwable cause) {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_EXCEPTION_CAUGHT);
|
|
if (ctx == null) {
|
|
notifyHandlerRemovedAlready(cause);
|
|
return;
|
|
}
|
|
ctx.invokeExceptionCaught(cause);
|
|
}
|
|
|
|
void invokeExceptionCaught(final Throwable cause) {
|
|
try {
|
|
handler().exceptionCaught(this, cause);
|
|
} catch (Throwable error) {
|
|
if (logger.isDebugEnabled()) {
|
|
logger.debug(
|
|
"An exception {}" +
|
|
"was thrown by a user handler's exceptionCaught() " +
|
|
"method while handling the following exception:",
|
|
ThrowableUtil.stackTraceToString(error), cause);
|
|
} else if (logger.isWarnEnabled()) {
|
|
logger.warn(
|
|
"An exception '{}' [enable DEBUG level for full stacktrace] " +
|
|
"was thrown by a user handler's exceptionCaught() " +
|
|
"method while handling the following exception:", error, cause);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireUserEventTriggered(Object event) {
|
|
requireNonNull(event, "event");
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeUserEventTriggered(event);
|
|
} else {
|
|
executor.execute(() -> findAndInvokeUserEventTriggered(event));
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeUserEventTriggered(Object event) {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_USER_EVENT_TRIGGERED);
|
|
if (ctx == null) {
|
|
ReferenceCountUtil.release(event);
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeUserEventTriggered(event);
|
|
}
|
|
|
|
void invokeUserEventTriggered(Object event) {
|
|
try {
|
|
handler().userEventTriggered(this, event);
|
|
} catch (Throwable t) {
|
|
notifyHandlerException(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireChannelRead(final Object msg) {
|
|
requireNonNull(msg, "msg");
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeChannelRead(msg);
|
|
} else {
|
|
try {
|
|
executor.execute(() -> findAndInvokeChannelRead(msg));
|
|
} catch (Throwable cause) {
|
|
ReferenceCountUtil.release(msg);
|
|
throw cause;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeChannelRead(Object msg) {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_CHANNEL_READ);
|
|
if (ctx == null) {
|
|
ReferenceCountUtil.release(msg);
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeChannelRead(msg);
|
|
}
|
|
|
|
void invokeChannelRead(Object msg) {
|
|
final Object m = pipeline.touch(requireNonNull(msg, "msg"), this);
|
|
try {
|
|
handler().channelRead(this, m);
|
|
} catch (Throwable t) {
|
|
notifyHandlerException(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireChannelReadComplete() {
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeChannelReadComplete();
|
|
} else {
|
|
Tasks tasks = invokeTasks();
|
|
executor.execute(tasks.invokeChannelReadCompleteTask);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeChannelReadComplete() {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_CHANNEL_READ_COMPLETE);
|
|
if (ctx == null) {
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeChannelReadComplete();
|
|
}
|
|
|
|
void invokeChannelReadComplete() {
|
|
try {
|
|
handler().channelReadComplete(this);
|
|
} catch (Throwable t) {
|
|
notifyHandlerException(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext fireChannelWritabilityChanged() {
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeChannelWritabilityChanged();
|
|
} else {
|
|
Tasks tasks = invokeTasks();
|
|
executor.execute(tasks.invokeChannelWritableStateChangedTask);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeChannelWritabilityChanged() {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_CHANNEL_WRITABILITY_CHANGED);
|
|
if (ctx == null) {
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeChannelWritabilityChanged();
|
|
}
|
|
|
|
void invokeChannelWritabilityChanged() {
|
|
try {
|
|
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 register() {
|
|
return register(newPromise());
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture deregister() {
|
|
return deregister(newPromise());
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
|
|
requireNonNull(localAddress, "localAddress");
|
|
if (isNotValidPromise(promise, false)) {
|
|
// cancelled
|
|
return promise;
|
|
}
|
|
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeBind(localAddress, promise);
|
|
} else {
|
|
safeExecute(executor, () -> findAndInvokeBind(localAddress, promise), promise, null);
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
private void findAndInvokeBind(SocketAddress localAddress, ChannelPromise promise) {
|
|
DefaultChannelHandlerContext ctx = findContextOutbound(MASK_BIND);
|
|
if (ctx == null) {
|
|
failRemoved(this, promise);
|
|
return;
|
|
}
|
|
ctx.invokeBind(localAddress, promise);
|
|
}
|
|
|
|
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
|
|
try {
|
|
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) {
|
|
requireNonNull(remoteAddress, "remoteAddress");
|
|
if (isNotValidPromise(promise, false)) {
|
|
// cancelled
|
|
return promise;
|
|
}
|
|
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeConnect(remoteAddress, localAddress, promise);
|
|
} else {
|
|
safeExecute(executor, () -> findAndInvokeConnect(remoteAddress, localAddress, promise), promise, null);
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
private void findAndInvokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
|
|
DefaultChannelHandlerContext ctx = findContextOutbound(MASK_CONNECT);
|
|
if (ctx == null) {
|
|
failRemoved(this, promise);
|
|
return;
|
|
}
|
|
ctx.invokeConnect(remoteAddress, localAddress, promise);
|
|
}
|
|
|
|
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
|
|
try {
|
|
handler().connect(this, remoteAddress, localAddress, promise);
|
|
} catch (Throwable t) {
|
|
notifyOutboundHandlerException(t, promise);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture disconnect(final ChannelPromise promise) {
|
|
if (!channel().metadata().hasDisconnect()) {
|
|
// 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.
|
|
return close(promise);
|
|
}
|
|
|
|
if (isNotValidPromise(promise, false)) {
|
|
// cancelled
|
|
return promise;
|
|
}
|
|
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeDisconnect(promise);
|
|
} else {
|
|
safeExecute(executor, () -> findAndInvokeDisconnect(promise), promise, null);
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
private void findAndInvokeDisconnect(ChannelPromise promise) {
|
|
DefaultChannelHandlerContext ctx = findContextOutbound(MASK_DISCONNECT);
|
|
if (ctx == null) {
|
|
failRemoved(this, promise);
|
|
return;
|
|
}
|
|
ctx.invokeDisconnect(promise);
|
|
}
|
|
|
|
private void invokeDisconnect(ChannelPromise promise) {
|
|
try {
|
|
handler().disconnect(this, promise);
|
|
} catch (Throwable t) {
|
|
notifyOutboundHandlerException(t, promise);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture close(final ChannelPromise promise) {
|
|
if (isNotValidPromise(promise, false)) {
|
|
// cancelled
|
|
return promise;
|
|
}
|
|
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeClose(promise);
|
|
} else {
|
|
safeExecute(executor, () -> findAndInvokeClose(promise), promise, null);
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
private void findAndInvokeClose(ChannelPromise promise) {
|
|
DefaultChannelHandlerContext ctx = findContextOutbound(MASK_CLOSE);
|
|
if (ctx == null) {
|
|
failRemoved(this, promise);
|
|
return;
|
|
}
|
|
ctx.invokeClose(promise);
|
|
}
|
|
|
|
private void invokeClose(ChannelPromise promise) {
|
|
try {
|
|
handler().close(this, promise);
|
|
} catch (Throwable t) {
|
|
notifyOutboundHandlerException(t, promise);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture register(final ChannelPromise promise) {
|
|
if (isNotValidPromise(promise, false)) {
|
|
// cancelled
|
|
return promise;
|
|
}
|
|
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeRegister(promise);
|
|
} else {
|
|
safeExecute(executor, () -> findAndInvokeRegister(promise), promise, null);
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
private void findAndInvokeRegister(ChannelPromise promise) {
|
|
DefaultChannelHandlerContext ctx = findContextOutbound(MASK_REGISTER);
|
|
if (ctx == null) {
|
|
failRemoved(this, promise);
|
|
return;
|
|
}
|
|
ctx.invokeRegister(promise);
|
|
}
|
|
|
|
private void invokeRegister(ChannelPromise promise) {
|
|
try {
|
|
handler().register(this, promise);
|
|
} catch (Throwable t) {
|
|
notifyOutboundHandlerException(t, promise);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture deregister(final ChannelPromise promise) {
|
|
if (isNotValidPromise(promise, false)) {
|
|
// cancelled
|
|
return promise;
|
|
}
|
|
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeDeregister(promise);
|
|
} else {
|
|
safeExecute(executor, () -> findAndInvokeDeregister(promise), promise, null);
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
private void findAndInvokeDeregister(ChannelPromise promise) {
|
|
DefaultChannelHandlerContext ctx = findContextOutbound(MASK_DEREGISTER);
|
|
if (ctx == null) {
|
|
failRemoved(this, promise);
|
|
return;
|
|
}
|
|
ctx.invokeDeregister(promise);
|
|
}
|
|
|
|
private void invokeDeregister(ChannelPromise promise) {
|
|
try {
|
|
handler().deregister(this, promise);
|
|
} catch (Throwable t) {
|
|
notifyOutboundHandlerException(t, promise);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext read() {
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeRead();
|
|
} else {
|
|
Tasks tasks = invokeTasks();
|
|
executor.execute(tasks.invokeReadTask);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeRead() {
|
|
DefaultChannelHandlerContext ctx = findContextOutbound(MASK_READ);
|
|
if (ctx != null) {
|
|
ctx.invokeRead();
|
|
}
|
|
}
|
|
|
|
private void invokeRead() {
|
|
try {
|
|
handler().read(this);
|
|
} catch (Throwable t) {
|
|
invokeExceptionCaughtFromOutbound(t);
|
|
}
|
|
}
|
|
|
|
private void invokeExceptionCaughtFromOutbound(Throwable t) {
|
|
if ((executionMask & MASK_EXCEPTION_CAUGHT) != 0) {
|
|
notifyHandlerException(t);
|
|
} else {
|
|
DefaultChannelHandlerContext ctx = findContextInbound(MASK_EXCEPTION_CAUGHT);
|
|
if (ctx == null) {
|
|
notifyHandlerRemovedAlready();
|
|
return;
|
|
}
|
|
ctx.invokeExceptionCaught(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture write(Object msg) {
|
|
return write(msg, newPromise());
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture write(final Object msg, final ChannelPromise promise) {
|
|
write(msg, false, promise);
|
|
|
|
return promise;
|
|
}
|
|
|
|
private void invokeWrite(Object msg, ChannelPromise promise) {
|
|
final Object m = pipeline.touch(msg, this);
|
|
try {
|
|
handler().write(this, m, promise);
|
|
} catch (Throwable t) {
|
|
notifyOutboundHandlerException(t, promise);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelHandlerContext flush() {
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
findAndInvokeFlush();
|
|
} else {
|
|
Tasks tasks = invokeTasks();
|
|
safeExecute(executor, tasks.invokeFlushTask, channel().voidPromise(), null);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
private void findAndInvokeFlush() {
|
|
DefaultChannelHandlerContext ctx = findContextOutbound(MASK_FLUSH);
|
|
if (ctx != null) {
|
|
ctx.invokeFlush();
|
|
}
|
|
}
|
|
|
|
private void invokeFlush() {
|
|
try {
|
|
handler().flush(this);
|
|
} catch (Throwable t) {
|
|
invokeExceptionCaughtFromOutbound(t);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
|
|
write(msg, true, promise);
|
|
return promise;
|
|
}
|
|
|
|
private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
|
|
invokeWrite(msg, promise);
|
|
invokeFlush();
|
|
}
|
|
|
|
private void write(Object msg, boolean flush, ChannelPromise promise) {
|
|
requireNonNull(msg, "msg");
|
|
try {
|
|
if (isNotValidPromise(promise, true)) {
|
|
ReferenceCountUtil.release(msg);
|
|
// cancelled
|
|
return;
|
|
}
|
|
} catch (RuntimeException e) {
|
|
ReferenceCountUtil.release(msg);
|
|
throw e;
|
|
}
|
|
|
|
EventExecutor executor = executor();
|
|
if (executor.inEventLoop()) {
|
|
final DefaultChannelHandlerContext next = findContextOutbound(flush ?
|
|
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
|
|
if (next == null) {
|
|
ReferenceCountUtil.release(msg);
|
|
failRemoved(this, promise);
|
|
return;
|
|
}
|
|
if (flush) {
|
|
next.invokeWriteAndFlush(msg, promise);
|
|
} else {
|
|
next.invokeWrite(msg, promise);
|
|
}
|
|
} else {
|
|
final AbstractWriteTask task;
|
|
if (flush) {
|
|
task = WriteAndFlushTask.newInstance(this, msg, promise);
|
|
} else {
|
|
task = WriteTask.newInstance(this, msg, promise);
|
|
}
|
|
if (!safeExecute(executor, task, promise, msg)) {
|
|
// We failed to submit the AbstractWriteTask. We need to cancel it so we decrement the pending bytes
|
|
// and put it back in the Recycler for re-use later.
|
|
//
|
|
// See https://github.com/netty/netty/issues/8343.
|
|
task.cancel();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture writeAndFlush(Object msg) {
|
|
return writeAndFlush(msg, newPromise());
|
|
}
|
|
|
|
private static void notifyOutboundHandlerException(Throwable cause, ChannelPromise promise) {
|
|
// Only log if the given promise is not of type VoidChannelPromise as tryFailure(...) is expected to return
|
|
// false.
|
|
PromiseNotificationUtil.tryFailure(promise, cause, promise instanceof VoidChannelPromise ? null : logger);
|
|
}
|
|
|
|
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 pipeline().newPromise();
|
|
}
|
|
|
|
@Override
|
|
public ChannelProgressivePromise newProgressivePromise() {
|
|
return pipeline().newProgressivePromise();
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture newSucceededFuture() {
|
|
return pipeline().newSucceededFuture();
|
|
}
|
|
|
|
@Override
|
|
public ChannelFuture newFailedFuture(Throwable cause) {
|
|
return pipeline().newFailedFuture(cause);
|
|
}
|
|
|
|
private boolean isNotValidPromise(ChannelPromise promise, boolean allowVoidPromise) {
|
|
requireNonNull(promise, "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 true;
|
|
}
|
|
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 false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
private DefaultChannelHandlerContext findContextInbound(int mask) {
|
|
DefaultChannelHandlerContext ctx = this;
|
|
if (ctx.next == null) {
|
|
return null;
|
|
}
|
|
do {
|
|
ctx = ctx.next;
|
|
} while ((ctx.executionMask & mask) == 0 || ctx.handlerState == REMOVE_STARTED);
|
|
return ctx;
|
|
}
|
|
|
|
private DefaultChannelHandlerContext findContextOutbound(int mask) {
|
|
DefaultChannelHandlerContext ctx = this;
|
|
if (ctx.prev == null) {
|
|
return null;
|
|
}
|
|
do {
|
|
ctx = ctx.prev;
|
|
} while ((ctx.executionMask & mask) == 0 || ctx.handlerState == REMOVE_STARTED);
|
|
return ctx;
|
|
}
|
|
|
|
@Override
|
|
public ChannelPromise voidPromise() {
|
|
return channel().voidPromise();
|
|
}
|
|
|
|
boolean setAddComplete() {
|
|
// Ensure we never update when the handlerState is REMOVE_COMPLETE already.
|
|
// oldState is usually ADD_PENDING but can also be REMOVE_COMPLETE when an EventExecutor is used that is not
|
|
// exposing ordering guarantees.
|
|
if (handlerState == INIT) {
|
|
handlerState = ADD_COMPLETE;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void callHandlerAdded() throws Exception {
|
|
// We must call setAddComplete before calling handlerAdded. Otherwise if the handlerAdded method generates
|
|
// any pipeline events ctx.handler() will miss them because the state will not allow it.
|
|
if (setAddComplete()) {
|
|
handler().handlerAdded(this);
|
|
}
|
|
}
|
|
|
|
void callHandlerRemoved() throws Exception {
|
|
try {
|
|
// Only call handlerRemoved(...) if we called handlerAdded(...) before.
|
|
if (handlerState == ADD_COMPLETE) {
|
|
handlerState = REMOVE_STARTED;
|
|
handler().handlerRemoved(this);
|
|
}
|
|
} finally {
|
|
// Mark the handler as removed in any case.
|
|
handlerState = REMOVE_COMPLETE;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isRemoved() {
|
|
return handlerState == REMOVE_COMPLETE;
|
|
}
|
|
|
|
void remove(boolean relink) {
|
|
assert handlerState == REMOVE_COMPLETE;
|
|
if (relink) {
|
|
DefaultChannelHandlerContext prev = this.prev;
|
|
DefaultChannelHandlerContext next = this.next;
|
|
// prev and next may be null if the handler was never really added to the pipeline
|
|
if (prev != null) {
|
|
prev.next = next;
|
|
}
|
|
if (next != null) {
|
|
next.prev = prev;
|
|
}
|
|
}
|
|
|
|
this.prev = null;
|
|
this.next = null;
|
|
}
|
|
|
|
@Override
|
|
public <T> Attribute<T> attr(AttributeKey<T> key) {
|
|
return channel().attr(key);
|
|
}
|
|
|
|
@Override
|
|
public <T> boolean hasAttr(AttributeKey<T> key) {
|
|
return channel().hasAttr(key);
|
|
}
|
|
|
|
private static boolean safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg) {
|
|
try {
|
|
executor.execute(runnable);
|
|
return true;
|
|
} catch (Throwable cause) {
|
|
try {
|
|
promise.setFailure(cause);
|
|
} finally {
|
|
if (msg != null) {
|
|
ReferenceCountUtil.release(msg);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toHintString() {
|
|
return '\'' + name + "' will handle the message from this point.";
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return StringUtil.simpleClassName(ChannelHandlerContext.class) + '(' + name + ", " + channel() + ')';
|
|
}
|
|
|
|
private static final boolean ESTIMATE_TASK_SIZE_ON_SUBMIT =
|
|
SystemPropertyUtil.getBoolean("io.netty.transport.estimateSizeOnSubmit", true);
|
|
|
|
// Assuming a 64-bit JVM, 16 bytes object header, 3 reference fields and one int field, plus alignment
|
|
private static final int WRITE_TASK_OVERHEAD =
|
|
SystemPropertyUtil.getInt("io.netty.transport.writeTaskSizeOverhead", 48);
|
|
|
|
abstract static class AbstractWriteTask implements Runnable {
|
|
|
|
private final ObjectPool.Handle<AbstractWriteTask> handle;
|
|
private DefaultChannelHandlerContext ctx;
|
|
private Object msg;
|
|
private ChannelPromise promise;
|
|
private int size;
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private AbstractWriteTask(ObjectPool.Handle<? extends AbstractWriteTask> handle) {
|
|
this.handle = (ObjectPool.Handle<AbstractWriteTask>) handle;
|
|
}
|
|
|
|
protected static void init(AbstractWriteTask task, DefaultChannelHandlerContext ctx,
|
|
Object msg, ChannelPromise promise) {
|
|
task.ctx = ctx;
|
|
task.msg = msg;
|
|
task.promise = promise;
|
|
|
|
if (ESTIMATE_TASK_SIZE_ON_SUBMIT) {
|
|
task.size = ctx.pipeline.estimatorHandle().size(msg) + WRITE_TASK_OVERHEAD;
|
|
ctx.pipeline.incrementPendingOutboundBytes(task.size);
|
|
} else {
|
|
task.size = 0;
|
|
}
|
|
}
|
|
|
|
protected abstract DefaultChannelHandlerContext findContext(DefaultChannelHandlerContext ctx);
|
|
@Override
|
|
public final void run() {
|
|
try {
|
|
decrementPendingOutboundBytes();
|
|
DefaultChannelHandlerContext next = findContext(ctx);
|
|
if (next == null) {
|
|
ReferenceCountUtil.release(msg);
|
|
failRemoved(ctx, promise);
|
|
return;
|
|
}
|
|
write(next, msg, promise);
|
|
} finally {
|
|
recycle();
|
|
}
|
|
}
|
|
|
|
void cancel() {
|
|
try {
|
|
decrementPendingOutboundBytes();
|
|
} finally {
|
|
recycle();
|
|
}
|
|
}
|
|
|
|
private void decrementPendingOutboundBytes() {
|
|
if (ESTIMATE_TASK_SIZE_ON_SUBMIT) {
|
|
ctx.pipeline.decrementPendingOutboundBytes(size);
|
|
}
|
|
}
|
|
|
|
private void recycle() {
|
|
// Set to null so the GC can collect them directly
|
|
ctx = null;
|
|
msg = null;
|
|
promise = null;
|
|
handle.recycle(this);
|
|
}
|
|
|
|
protected void write(DefaultChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
|
ctx.invokeWrite(msg, promise);
|
|
}
|
|
}
|
|
|
|
static final class WriteTask extends AbstractWriteTask implements SingleThreadEventLoop.NonWakeupRunnable {
|
|
|
|
private static final ObjectPool<WriteTask> RECYCLER = ObjectPool.newPool(WriteTask::new);
|
|
|
|
static WriteTask newInstance(
|
|
DefaultChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
|
WriteTask task = RECYCLER.get();
|
|
init(task, ctx, msg, promise);
|
|
return task;
|
|
}
|
|
|
|
@Override
|
|
protected DefaultChannelHandlerContext findContext(DefaultChannelHandlerContext ctx) {
|
|
return ctx.findContextOutbound(MASK_WRITE);
|
|
}
|
|
|
|
private WriteTask(ObjectPool.Handle<WriteTask> handle) {
|
|
super(handle);
|
|
}
|
|
}
|
|
|
|
static final class WriteAndFlushTask extends AbstractWriteTask {
|
|
|
|
private static final ObjectPool<WriteAndFlushTask> RECYCLER = ObjectPool.newPool(WriteAndFlushTask::new);
|
|
|
|
static WriteAndFlushTask newInstance(
|
|
DefaultChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
|
WriteAndFlushTask task = RECYCLER.get();
|
|
init(task, ctx, msg, promise);
|
|
return task;
|
|
}
|
|
|
|
private WriteAndFlushTask(ObjectPool.Handle<WriteAndFlushTask> handle) {
|
|
super(handle);
|
|
}
|
|
|
|
@Override
|
|
protected DefaultChannelHandlerContext findContext(DefaultChannelHandlerContext ctx) {
|
|
return ctx.findContextOutbound(MASK_WRITE | MASK_FLUSH);
|
|
}
|
|
|
|
@Override
|
|
public void write(DefaultChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
|
super.write(ctx, msg, promise);
|
|
ctx.invokeFlush();
|
|
}
|
|
}
|
|
|
|
private static final class Tasks {
|
|
private final Runnable invokeChannelReadCompleteTask;
|
|
private final Runnable invokeReadTask;
|
|
private final Runnable invokeChannelWritableStateChangedTask;
|
|
private final Runnable invokeFlushTask;
|
|
|
|
Tasks(DefaultChannelHandlerContext ctx) {
|
|
invokeChannelReadCompleteTask = ctx::findAndInvokeChannelReadComplete;
|
|
invokeReadTask = ctx::findAndInvokeRead;
|
|
invokeChannelWritableStateChangedTask = ctx::invokeChannelWritabilityChanged;
|
|
invokeFlushTask = ctx::findAndInvokeFlush;
|
|
}
|
|
}
|
|
}
|