From 9b45e9d015e12db923df39458b36fe3720bdbacd Mon Sep 17 00:00:00 2001 From: Matteo Merli Date: Fri, 28 Aug 2015 15:05:52 -0700 Subject: [PATCH] Additional configuration for leak detection Motivation: Leak detector, when it detects a leak, will print the last 5 stack traces that touched the ByteBuf. In some cases that might not be enough to identify the root cause of the leak. Also, sometimes users might not be interested in tracing all the operations on the buffer, but just the ones that are affecting the reference count. Modifications: Added command line properties to override default values: * Allow to configure max number of stack traces to collect * Allow to only record retain/release operation on buffers Result: Users can increase the number of stack traces to debug buffer leaks with lot of retain/release operations. --- .../buffer/AdvancedLeakAwareByteBuf.java | 231 ++++++++++-------- .../io/netty/util/ResourceLeakDetector.java | 21 +- 2 files changed, 139 insertions(+), 113 deletions(-) diff --git a/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareByteBuf.java b/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareByteBuf.java index ef683596ca..62b2b774b3 100644 --- a/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/AdvancedLeakAwareByteBuf.java @@ -18,6 +18,7 @@ package io.netty.buffer; import io.netty.util.ByteProcessor; import io.netty.util.ResourceLeak; +import io.netty.util.internal.SystemPropertyUtil; import java.io.IOException; import java.io.InputStream; @@ -30,6 +31,13 @@ import java.nio.charset.Charset; final class AdvancedLeakAwareByteBuf extends WrappedByteBuf { + private static final String PROP_ACQUIRE_AND_RELEASE_ONLY = "io.netty.leakDetection.acquireAndReleaseOnly"; + private static final boolean ACQUIRE_AND_RELEASE_ONLY; + + static { + ACQUIRE_AND_RELEASE_ONLY = SystemPropertyUtil.getBoolean(PROP_ACQUIRE_AND_RELEASE_ONLY, false); + } + private final ResourceLeak leak; AdvancedLeakAwareByteBuf(ByteBuf buf, ResourceLeak leak) { @@ -37,9 +45,15 @@ final class AdvancedLeakAwareByteBuf extends WrappedByteBuf { this.leak = leak; } + private void recordLeakNonRefCountingOperation() { + if (!ACQUIRE_AND_RELEASE_ONLY) { + leak.record(); + } + } + @Override public ByteBuf order(ByteOrder endianness) { - leak.record(); + recordLeakNonRefCountingOperation(); if (order() == endianness) { return this; } else { @@ -49,643 +63,644 @@ final class AdvancedLeakAwareByteBuf extends WrappedByteBuf { @Override public ByteBuf slice() { - leak.record(); + recordLeakNonRefCountingOperation(); return new AdvancedLeakAwareByteBuf(super.slice(), leak); } @Override public ByteBuf slice(int index, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return new AdvancedLeakAwareByteBuf(super.slice(index, length), leak); } @Override public ByteBuf duplicate() { - leak.record(); + recordLeakNonRefCountingOperation(); return new AdvancedLeakAwareByteBuf(super.duplicate(), leak); } @Override public ByteBuf readSlice(int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return new AdvancedLeakAwareByteBuf(super.readSlice(length), leak); } @Override public ByteBuf discardReadBytes() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.discardReadBytes(); } @Override public ByteBuf discardSomeReadBytes() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.discardSomeReadBytes(); } @Override public ByteBuf ensureWritable(int minWritableBytes) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.ensureWritable(minWritableBytes); } @Override public int ensureWritable(int minWritableBytes, boolean force) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.ensureWritable(minWritableBytes, force); } @Override public boolean getBoolean(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBoolean(index); } @Override public byte getByte(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getByte(index); } @Override public short getUnsignedByte(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getUnsignedByte(index); } @Override public short getShort(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getShort(index); } @Override public int getUnsignedShort(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getUnsignedShort(index); } @Override public int getMedium(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getMedium(index); } @Override public int getUnsignedMedium(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getUnsignedMedium(index); } @Override public int getInt(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getInt(index); } @Override public long getUnsignedInt(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getUnsignedInt(index); } @Override public long getLong(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getLong(index); } @Override public char getChar(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getChar(index); } @Override public float getFloat(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getFloat(index); } @Override public double getDouble(int index) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getDouble(index); } @Override public ByteBuf getBytes(int index, ByteBuf dst) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBytes(index, dst); } @Override public ByteBuf getBytes(int index, ByteBuf dst, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBytes(index, dst, length); } @Override public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBytes(index, dst, dstIndex, length); } @Override public ByteBuf getBytes(int index, byte[] dst) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBytes(index, dst); } @Override public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBytes(index, dst, dstIndex, length); } @Override public ByteBuf getBytes(int index, ByteBuffer dst) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBytes(index, dst); } @Override public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBytes(index, out, length); } @Override public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { - leak.record(); + recordLeakNonRefCountingOperation(); return super.getBytes(index, out, length); } @Override public ByteBuf setBoolean(int index, boolean value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBoolean(index, value); } @Override public ByteBuf setByte(int index, int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setByte(index, value); } @Override public ByteBuf setShort(int index, int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setShort(index, value); } @Override public ByteBuf setMedium(int index, int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setMedium(index, value); } @Override public ByteBuf setInt(int index, int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setInt(index, value); } @Override public ByteBuf setLong(int index, long value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setLong(index, value); } @Override public ByteBuf setChar(int index, int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setChar(index, value); } @Override public ByteBuf setFloat(int index, float value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setFloat(index, value); } @Override public ByteBuf setDouble(int index, double value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setDouble(index, value); } @Override public ByteBuf setBytes(int index, ByteBuf src) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBytes(index, src); } @Override public ByteBuf setBytes(int index, ByteBuf src, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBytes(index, src, length); } @Override public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBytes(index, src, srcIndex, length); } @Override public ByteBuf setBytes(int index, byte[] src) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBytes(index, src); } @Override public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBytes(index, src, srcIndex, length); } @Override public ByteBuf setBytes(int index, ByteBuffer src) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBytes(index, src); } @Override public int setBytes(int index, InputStream in, int length) throws IOException { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBytes(index, in, length); } @Override public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setBytes(index, in, length); } @Override public ByteBuf setZero(int index, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.setZero(index, length); } @Override public boolean readBoolean() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBoolean(); } @Override public byte readByte() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readByte(); } @Override public short readUnsignedByte() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readUnsignedByte(); } @Override public short readShort() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readShort(); } @Override public int readUnsignedShort() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readUnsignedShort(); } @Override public int readMedium() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readMedium(); } @Override public int readUnsignedMedium() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readUnsignedMedium(); } @Override public int readInt() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readInt(); } @Override public long readUnsignedInt() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readUnsignedInt(); } @Override public long readLong() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readLong(); } @Override public char readChar() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readChar(); } @Override public float readFloat() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readFloat(); } @Override public double readDouble() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readDouble(); } @Override public ByteBuf readBytes(int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(length); } @Override public ByteBuf readBytes(ByteBuf dst) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(dst); } @Override public ByteBuf readBytes(ByteBuf dst, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(dst, length); } @Override public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(dst, dstIndex, length); } @Override public ByteBuf readBytes(byte[] dst) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(dst); } @Override public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(dst, dstIndex, length); } @Override public ByteBuf readBytes(ByteBuffer dst) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(dst); } @Override public ByteBuf readBytes(OutputStream out, int length) throws IOException { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(out, length); } @Override public int readBytes(GatheringByteChannel out, int length) throws IOException { - leak.record(); + recordLeakNonRefCountingOperation(); return super.readBytes(out, length); } @Override public ByteBuf skipBytes(int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.skipBytes(length); } @Override public ByteBuf writeBoolean(boolean value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBoolean(value); } @Override public ByteBuf writeByte(int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeByte(value); } @Override public ByteBuf writeShort(int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeShort(value); } @Override public ByteBuf writeMedium(int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeMedium(value); } @Override public ByteBuf writeInt(int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeInt(value); } @Override public ByteBuf writeLong(long value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeLong(value); } @Override public ByteBuf writeChar(int value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeChar(value); } @Override public ByteBuf writeFloat(float value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeFloat(value); } @Override public ByteBuf writeDouble(double value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeDouble(value); } @Override public ByteBuf writeBytes(ByteBuf src) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBytes(src); } @Override public ByteBuf writeBytes(ByteBuf src, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBytes(src, length); } @Override public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBytes(src, srcIndex, length); } @Override public ByteBuf writeBytes(byte[] src) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBytes(src); } @Override public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBytes(src, srcIndex, length); } @Override public ByteBuf writeBytes(ByteBuffer src) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBytes(src); } @Override public int writeBytes(InputStream in, int length) throws IOException { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBytes(in, length); } @Override public int writeBytes(ScatteringByteChannel in, int length) throws IOException { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeBytes(in, length); } @Override public ByteBuf writeZero(int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.writeZero(length); } @Override public int indexOf(int fromIndex, int toIndex, byte value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.indexOf(fromIndex, toIndex, value); } @Override public int bytesBefore(byte value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.bytesBefore(value); } @Override public int bytesBefore(int length, byte value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.bytesBefore(length, value); } @Override public int bytesBefore(int index, int length, byte value) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.bytesBefore(index, length, value); } @Override public int forEachByte(ByteProcessor processor) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.forEachByte(processor); } @Override public int forEachByte(int index, int length, ByteProcessor processor) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.forEachByte(index, length, processor); } @Override public int forEachByteDesc(ByteProcessor processor) { - leak.record(); + recordLeakNonRefCountingOperation(); + recordLeakNonRefCountingOperation(); return super.forEachByteDesc(processor); } @Override public int forEachByteDesc(int index, int length, ByteProcessor processor) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.forEachByteDesc(index, length, processor); } @Override public ByteBuf copy() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.copy(); } @Override public ByteBuf copy(int index, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.copy(index, length); } @Override public int nioBufferCount() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.nioBufferCount(); } @Override public ByteBuffer nioBuffer() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.nioBuffer(); } @Override public ByteBuffer nioBuffer(int index, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.nioBuffer(index, length); } @Override public ByteBuffer[] nioBuffers() { - leak.record(); + recordLeakNonRefCountingOperation(); return super.nioBuffers(); } @Override public ByteBuffer[] nioBuffers(int index, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.nioBuffers(index, length); } @Override public ByteBuffer internalNioBuffer(int index, int length) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.internalNioBuffer(index, length); } @Override public String toString(Charset charset) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.toString(charset); } @Override public String toString(int index, int length, Charset charset) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.toString(index, length, charset); } @Override public ByteBuf capacity(int newCapacity) { - leak.record(); + recordLeakNonRefCountingOperation(); return super.capacity(newCapacity); } diff --git a/common/src/main/java/io/netty/util/ResourceLeakDetector.java b/common/src/main/java/io/netty/util/ResourceLeakDetector.java index d1dbfdd352..15098607fd 100644 --- a/common/src/main/java/io/netty/util/ResourceLeakDetector.java +++ b/common/src/main/java/io/netty/util/ResourceLeakDetector.java @@ -33,9 +33,14 @@ import static io.netty.util.internal.StringUtil.*; public final class ResourceLeakDetector { - private static final String PROP_LEVEL = "io.netty.leakDetectionLevel"; + private static final String PROP_LEVEL_OLD = "io.netty.leakDetectionLevel"; + private static final String PROP_LEVEL = "io.netty.leakDetection.level"; private static final Level DEFAULT_LEVEL = Level.SIMPLE; + private static final String PROP_MAX_RECORDS = "io.netty.leakDetection.maxRecords"; + private static final int DEFAULT_MAX_RECORDS = 4; + private static final int MAX_RECORDS; + /** * Represents the level of resource leak detection. */ @@ -78,7 +83,12 @@ public final class ResourceLeakDetector { } Level defaultLevel = disabled? Level.DISABLED : DEFAULT_LEVEL; - String levelStr = SystemPropertyUtil.get(PROP_LEVEL, defaultLevel.name()).trim().toUpperCase(); + + // First read old property name + String levelStr = SystemPropertyUtil.get(PROP_LEVEL_OLD, defaultLevel.name()).trim().toUpperCase(); + + // If new property name is present, use it + levelStr = SystemPropertyUtil.get(PROP_LEVEL, levelStr).trim().toUpperCase(); Level level = DEFAULT_LEVEL; for (Level l: EnumSet.allOf(Level.class)) { if (levelStr.equals(l.name()) || levelStr.equals(String.valueOf(l.ordinal()))) { @@ -86,9 +96,12 @@ public final class ResourceLeakDetector { } } + MAX_RECORDS = SystemPropertyUtil.getInt(PROP_MAX_RECORDS, DEFAULT_MAX_RECORDS); + ResourceLeakDetector.level = level; if (logger.isDebugEnabled()) { logger.debug("-D{}: {}", PROP_LEVEL, level.name().toLowerCase()); + logger.debug("-D{}: {}", PROP_MAX_RECORDS, MAX_RECORDS); } } @@ -252,9 +265,6 @@ public final class ResourceLeakDetector { } private final class DefaultResourceLeak extends PhantomReference implements ResourceLeak { - - private static final int MAX_RECORDS = 4; - private final String creationRecord; private final Deque lastRecords = new ArrayDeque(); private final AtomicBoolean freed; @@ -368,6 +378,7 @@ public final class ResourceLeakDetector { "io.netty.util.ReferenceCountUtil.touch(", "io.netty.buffer.AdvancedLeakAwareByteBuf.touch(", "io.netty.buffer.AbstractByteBufAllocator.toLeakAwareBuffer(", + "io.netty.buffer.AdvancedLeakAwareByteBuf.recordLeakNonRefCountingOperation(" }; static String newRecord(Object hint, int recordsToSkip) {