a564b70d51
Motivation:
8331248671
did make some changes to fix a race in ChannelInitializer when using with a custom EventExecutor. Unfortunally these where a bit racy and so the testcase failed sometimes.
Modifications:
- More correct fix when using a custom EventExecutor
- Adjust the testcase to be more correct.
Result:
Proper fix for https://github.com/netty/netty/issues/8616.
161 lines
6.6 KiB
Java
161 lines
6.6 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 io.netty.bootstrap.Bootstrap;
|
|
import io.netty.bootstrap.ServerBootstrap;
|
|
import io.netty.channel.ChannelHandler.Sharable;
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
|
|
import java.util.Collections;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
/**
|
|
* A special {@link ChannelInboundHandler} which offers an easy way to initialize a {@link Channel} once it was
|
|
* registered to its {@link EventLoop}.
|
|
*
|
|
* Implementations are most often used in the context of {@link Bootstrap#handler(ChannelHandler)} ,
|
|
* {@link ServerBootstrap#handler(ChannelHandler)} and {@link ServerBootstrap#childHandler(ChannelHandler)} to
|
|
* setup the {@link ChannelPipeline} of a {@link Channel}.
|
|
*
|
|
* <pre>
|
|
*
|
|
* public class MyChannelInitializer extends {@link ChannelInitializer} {
|
|
* public void initChannel({@link Channel} channel) {
|
|
* channel.pipeline().addLast("myHandler", new MyHandler());
|
|
* }
|
|
* }
|
|
*
|
|
* {@link ServerBootstrap} bootstrap = ...;
|
|
* ...
|
|
* bootstrap.childHandler(new MyChannelInitializer());
|
|
* ...
|
|
* </pre>
|
|
* Be aware that this class is marked as {@link Sharable} and so the implementation must be safe to be re-used.
|
|
*
|
|
* @param <C> A sub-type of {@link Channel}
|
|
*/
|
|
@Sharable
|
|
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);
|
|
// We use a Set as a ChannelInitializer is usually shared between all Channels in a Bootstrap /
|
|
// ServerBootstrap. This way we can reduce the memory usage compared to use Attributes.
|
|
private final Set<ChannelHandlerContext> initMap = Collections.newSetFromMap(
|
|
new ConcurrentHashMap<ChannelHandlerContext, Boolean>());
|
|
|
|
/**
|
|
* This method will be called once the {@link Channel} was registered. After the method returns this instance
|
|
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
|
|
*
|
|
* @param ch the {@link Channel} which was registered.
|
|
* @throws Exception is thrown if an error occurs. In that case it will be handled by
|
|
* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
|
|
* the {@link Channel}.
|
|
*/
|
|
protected abstract void initChannel(C ch) throws Exception;
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
|
// Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
|
|
// the handler.
|
|
if (initChannel(ctx)) {
|
|
// we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
|
|
// miss an event.
|
|
ctx.pipeline().fireChannelRegistered();
|
|
|
|
// We are done with init the Channel, removing all the state for the Channel now.
|
|
removeState(ctx);
|
|
} else {
|
|
// Called initChannel(...) before which is the expected behavior, so just forward the event.
|
|
ctx.fireChannelRegistered();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
|
|
*/
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
if (logger.isWarnEnabled()) {
|
|
logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause);
|
|
}
|
|
ctx.close();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc} If override this method ensure you call super!
|
|
*/
|
|
@Override
|
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
if (ctx.channel().isRegistered()) {
|
|
// This should always be true with our current DefaultChannelPipeline implementation.
|
|
// The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering
|
|
// surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers
|
|
// will be added in the expected order.
|
|
if (initChannel(ctx)) {
|
|
|
|
// We are done with init the Channel, removing the initializer now.
|
|
removeState(ctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
initMap.remove(ctx);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
|
|
if (initMap.add(ctx)) { // Guard against re-entrance.
|
|
try {
|
|
initChannel((C) ctx.channel());
|
|
} catch (Throwable cause) {
|
|
// Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
|
|
// We do so to prevent multiple calls to initChannel(...).
|
|
exceptionCaught(ctx, cause);
|
|
} finally {
|
|
ChannelPipeline pipeline = ctx.pipeline();
|
|
if (pipeline.context(this) != null) {
|
|
pipeline.remove(this);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void removeState(final ChannelHandlerContext ctx) {
|
|
// The removal may happen in an async fashion if the EventExecutor we use does something funky.
|
|
if (ctx.isRemoved()) {
|
|
initMap.remove(ctx);
|
|
} else {
|
|
// The context is not removed yet which is most likely the case because a custom EventExecutor is used.
|
|
// Let's schedule it on the EventExecutor to give it some more time to be completed in case it is offloaded.
|
|
ctx.executor().execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
initMap.remove(ctx);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|