New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ChannelInitializer may be invoked multiple times when used with custo… #8620
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,11 +18,12 @@ | |
import io.netty.bootstrap.Bootstrap; | ||
import io.netty.bootstrap.ServerBootstrap; | ||
import io.netty.channel.ChannelHandler.Sharable; | ||
import io.netty.util.internal.PlatformDependent; | ||
import io.netty.util.internal.logging.InternalLogger; | ||
import io.netty.util.internal.logging.InternalLoggerFactory; | ||
|
||
import java.util.concurrent.ConcurrentMap; | ||
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 | ||
|
@@ -53,9 +54,10 @@ | |
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter { | ||
|
||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class); | ||
// We use a ConcurrentMap as a ChannelInitializer is usually shared between all Channels in a Bootstrap / | ||
// 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 ConcurrentMap<ChannelHandlerContext, Boolean> initMap = PlatformDependent.newConcurrentHashMap(); | ||
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 | ||
|
@@ -108,9 +110,14 @@ public void handlerAdded(ChannelHandlerContext ctx) throws Exception { | |
} | ||
} | ||
|
||
@Override | ||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { | ||
initMap.remove(ctx); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private boolean initChannel(ChannelHandlerContext ctx) throws Exception { | ||
if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance. | ||
if (initMap.add(ctx)) { // Guard against re-entrance. | ||
try { | ||
initChannel((C) ctx.channel()); | ||
} catch (Throwable cause) { | ||
|
@@ -125,14 +132,25 @@ private boolean initChannel(ChannelHandlerContext ctx) throws Exception { | |
return false; | ||
} | ||
|
||
private void remove(ChannelHandlerContext ctx) { | ||
private void remove(final ChannelHandlerContext ctx) { | ||
try { | ||
ChannelPipeline pipeline = ctx.pipeline(); | ||
if (pipeline.context(this) != null) { | ||
pipeline.remove(this); | ||
} | ||
} finally { | ||
initMap.remove(ctx); | ||
// The removal may happen in an async fashion if the EventExecutor we use does something funky. | ||
if (ctx.isRemoved()) { | ||
initMap.remove(ctx); | ||
} else { | ||
// Ensure we always remove from the Map in all cases to not produce a memory leak. | ||
ctx.channel().closeFuture().addListener(new ChannelFutureListener() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will retain Is this seriously the only way we can clean up properly? Is the problem that the methods can be overridden? If so, then maybe we should fix that in Netty 5? Or maybe we could avoid the check-for-double-register problem by having an alternative approach to 26aa348 in Netty 5? It is possible to cancel the future to clean up, but the amount of effort necessary to do something simple makes me question if this is more of systemic problem that should be addressed in Netty 5. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ejona86 yeah I think its the best we can do for netty 4 to ensure we not leak if the user overrides There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we could check handlerRemoved() was overridden at instantiation time using reflection and take the fast path? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @trustin we could ... that said I think it does not really worth it as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. |
||
@Override | ||
public void operationComplete(ChannelFuture future) { | ||
initMap.remove(ctx); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the point of this line? Wouldn't we only get here if
handlerRemoved()
was called? Is the concern that someone could overridehandlerRemoved()
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes :/