Remove HashMap for lookup name / ctx from DefaultChannelPipeline to reduce memory footprint
Motivation: If you start to have 1M+ concurrent connections memory footprint can be come a big issue. We should try to reduce it as much as possible in the core of netty. Modifications: - Remove HashMap that was used to store name to ctx mapping. This was only used for validation and access a handler by name. As a pipeline is not expected to be very long (like 100+ handlers) we can just walk the linked list structure to find the ctx with a given name. Result: Less memory footprint of the DefaultChannelPipeline.
This commit is contained in:
parent
63c2ad4b7d
commit
d6c23d0af9
@ -28,7 +28,6 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -60,9 +59,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
final AbstractChannelHandlerContext head;
|
||||
final AbstractChannelHandlerContext tail;
|
||||
|
||||
private final Map<String, AbstractChannelHandlerContext> name2ctx =
|
||||
new HashMap<String, AbstractChannelHandlerContext>(4);
|
||||
|
||||
final Map<EventExecutorGroup, EventExecutor> childExecutors =
|
||||
new IdentityHashMap<EventExecutorGroup, EventExecutor>();
|
||||
|
||||
@ -94,13 +90,13 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
synchronized (this) {
|
||||
checkDuplicateName(name);
|
||||
AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
|
||||
addFirst0(name, newCtx);
|
||||
addFirst0(newCtx);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addFirst0(String name, AbstractChannelHandlerContext newCtx) {
|
||||
private void addFirst0(AbstractChannelHandlerContext newCtx) {
|
||||
checkMultiplicity(newCtx);
|
||||
|
||||
AbstractChannelHandlerContext nextCtx = head.next;
|
||||
@ -109,8 +105,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
head.next = newCtx;
|
||||
nextCtx.prev = newCtx;
|
||||
|
||||
name2ctx.put(name, newCtx);
|
||||
|
||||
callHandlerAdded(newCtx);
|
||||
}
|
||||
|
||||
@ -125,13 +119,13 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
checkDuplicateName(name);
|
||||
|
||||
AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
|
||||
addLast0(name, newCtx);
|
||||
addLast0(newCtx);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addLast0(final String name, AbstractChannelHandlerContext newCtx) {
|
||||
private void addLast0(AbstractChannelHandlerContext newCtx) {
|
||||
checkMultiplicity(newCtx);
|
||||
|
||||
AbstractChannelHandlerContext prev = tail.prev;
|
||||
@ -140,8 +134,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
prev.next = newCtx;
|
||||
tail.prev = newCtx;
|
||||
|
||||
name2ctx.put(name, newCtx);
|
||||
|
||||
callHandlerAdded(newCtx);
|
||||
}
|
||||
|
||||
@ -157,13 +149,12 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
AbstractChannelHandlerContext ctx = getContextOrDie(baseName);
|
||||
checkDuplicateName(name);
|
||||
AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
|
||||
addBefore0(name, ctx, newCtx);
|
||||
addBefore0(ctx, newCtx);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addBefore0(
|
||||
final String name, AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
|
||||
private void addBefore0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
|
||||
checkMultiplicity(newCtx);
|
||||
|
||||
newCtx.prev = ctx.prev;
|
||||
@ -171,8 +162,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
ctx.prev.next = newCtx;
|
||||
ctx.prev = newCtx;
|
||||
|
||||
name2ctx.put(name, newCtx);
|
||||
|
||||
callHandlerAdded(newCtx);
|
||||
}
|
||||
|
||||
@ -189,14 +178,13 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
checkDuplicateName(name);
|
||||
AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
|
||||
|
||||
addAfter0(name, ctx, newCtx);
|
||||
addAfter0(ctx, newCtx);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addAfter0(final String name, AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
|
||||
checkDuplicateName(name);
|
||||
private void addAfter0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
|
||||
checkMultiplicity(newCtx);
|
||||
|
||||
newCtx.prev = ctx;
|
||||
@ -204,8 +192,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
ctx.next.prev = newCtx;
|
||||
ctx.next = newCtx;
|
||||
|
||||
name2ctx.put(name, newCtx);
|
||||
|
||||
callHandlerAdded(newCtx);
|
||||
}
|
||||
|
||||
@ -271,11 +257,11 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
synchronized (this) {
|
||||
// It's not very likely for a user to put more than one handler of the same type, but make sure to avoid
|
||||
// any name conflicts. Note that we don't cache the names generated here.
|
||||
if (name2ctx.containsKey(name)) {
|
||||
if (context0(name) != null) {
|
||||
String baseName = name.substring(0, name.length() - 1); // Strip the trailing '0'.
|
||||
for (int i = 1;; i ++) {
|
||||
String newName = baseName + i;
|
||||
if (!name2ctx.containsKey(newName)) {
|
||||
if (context0(newName) == null) {
|
||||
name = newName;
|
||||
break;
|
||||
}
|
||||
@ -343,7 +329,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
AbstractChannelHandlerContext next = ctx.next;
|
||||
prev.next = next;
|
||||
next.prev = prev;
|
||||
name2ctx.remove(ctx.name());
|
||||
callHandlerRemoved(ctx);
|
||||
}
|
||||
|
||||
@ -398,14 +383,14 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
new DefaultChannelHandlerContext(this, ctx.executor, newName, newHandler);
|
||||
|
||||
if (!newCtx.channel().isRegistered() || newCtx.executor().inEventLoop()) {
|
||||
replace0(ctx, newName, newCtx);
|
||||
replace0(ctx, newCtx);
|
||||
return ctx.handler();
|
||||
} else {
|
||||
future = newCtx.executor().submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DefaultChannelPipeline.this) {
|
||||
replace0(ctx, newName, newCtx);
|
||||
replace0(ctx, newCtx);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -420,8 +405,7 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
return ctx.handler();
|
||||
}
|
||||
|
||||
private void replace0(AbstractChannelHandlerContext oldCtx, String newName,
|
||||
AbstractChannelHandlerContext newCtx) {
|
||||
private void replace0(AbstractChannelHandlerContext oldCtx, AbstractChannelHandlerContext newCtx) {
|
||||
checkMultiplicity(newCtx);
|
||||
|
||||
AbstractChannelHandlerContext prev = oldCtx.prev;
|
||||
@ -436,11 +420,6 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
prev.next = newCtx;
|
||||
next.prev = newCtx;
|
||||
|
||||
if (!oldCtx.name().equals(newName)) {
|
||||
name2ctx.remove(oldCtx.name());
|
||||
}
|
||||
name2ctx.put(newName, newCtx);
|
||||
|
||||
// update the reference to the replacement so forward of buffered content will work correctly
|
||||
oldCtx.prev = newCtx;
|
||||
oldCtx.next = newCtx;
|
||||
@ -618,9 +597,7 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
return name2ctx.get(name);
|
||||
}
|
||||
return context0(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -745,8 +722,8 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
* Removes all handlers from the pipeline one by one from tail (exclusive) to head (exclusive) to trigger
|
||||
* handlerRemoved().
|
||||
*
|
||||
* Note that we traverse up the pipeline ({@link #destroyUp(AbstractChannelHandlerContext)})
|
||||
* before traversing down ({@link #destroyDown(Thread, AbstractChannelHandlerContext)}) so that
|
||||
* Note that we traverse up the pipeline ({@link #destroyUp(AbstractChannelHandlerContext, boolean)})
|
||||
* before traversing down ({@link #destroyDown(Thread, AbstractChannelHandlerContext, boolean)}) so that
|
||||
* the handlers are removed after all events are handled.
|
||||
*
|
||||
* See: https://github.com/netty/netty/issues/3156
|
||||
@ -953,11 +930,22 @@ final class DefaultChannelPipeline implements ChannelPipeline {
|
||||
}
|
||||
|
||||
private void checkDuplicateName(String name) {
|
||||
if (name2ctx.containsKey(name)) {
|
||||
if (context0(name) != null) {
|
||||
throw new IllegalArgumentException("Duplicate handler name: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
private AbstractChannelHandlerContext context0(String name) {
|
||||
AbstractChannelHandlerContext context = head.next;
|
||||
while (context != tail) {
|
||||
if (context.name().equals(name)) {
|
||||
return context;
|
||||
}
|
||||
context = context.next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private AbstractChannelHandlerContext getContextOrDie(String name) {
|
||||
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name);
|
||||
if (ctx == null) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user