From 892f21f9d9a9d78eeec9774706b39fa036a0ca9b Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 20 Sep 2013 15:50:35 +0000
Subject: [PATCH] Fix OPENDJ-1152: Provide the ability to debug leaked pooled connections
---
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionFactoryTestCase.java | 2
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CachedConnectionPool.java | 54 ++++++++++++++++++++++++---
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionPoolTestCase.java | 1
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/StaticUtils.java | 60 ++++++++++++++++++++++++++++++
4 files changed, 110 insertions(+), 7 deletions(-)
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/StaticUtils.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/StaticUtils.java
index bbfd09e..3a4326f 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/StaticUtils.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/StaticUtils.java
@@ -72,6 +72,21 @@
public static final Logger DEBUG_LOG = Logger.getLogger("org.forgerock.opendj.ldap");
/**
+ * Indicates whether the SDK is being used in debug mode. In debug mode
+ * components may enable certain instrumentation in order to help debug
+ * applications.
+ */
+ public static final boolean DEBUG_ENABLED =
+ System.getProperty("org.forgerock.opendj.debug") != null;
+
+ private static final boolean DEBUG_TO_STDERR = System
+ .getProperty("org.forgerock.opendj.debug.stderr") != null;
+
+ static {
+ logIfDebugEnabled("debugging enabled", null);
+ }
+
+ /**
* The end-of-line character for this platform.
*/
public static final String EOL = System.getProperty("line.separator");
@@ -2162,6 +2177,51 @@
}
/**
+ * Returns the stack trace for the calling method, but only if SDK debugging
+ * is enabled.
+ *
+ * @return The stack trace for the calling method, but only if SDK debugging
+ * is enabled, otherwise {@code null}..
+ */
+ public static StackTraceElement[] getStackTraceIfDebugEnabled() {
+ if (!DEBUG_ENABLED) {
+ return null;
+ } else {
+ final StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+ return Arrays.copyOfRange(stack, 2, stack.length);
+ }
+ }
+
+ /**
+ * Logs the provided message and stack trace if SDK debugging is enabled to
+ * either stderr or the debug logger.
+ *
+ * @param msg
+ * The message to be logged.
+ * @param stackTrace
+ * The stack trace, which may be {@code null}.
+ */
+ public static void logIfDebugEnabled(final String msg, final StackTraceElement[] stackTrace) {
+ if (DEBUG_ENABLED) {
+ final StringBuilder builder = new StringBuilder("OPENDJ SDK: ");
+ builder.append(msg);
+ if (stackTrace != null) {
+ builder.append(System.lineSeparator());
+ for (StackTraceElement e : stackTrace) {
+ builder.append("\tat ");
+ builder.append(String.valueOf(e));
+ builder.append(System.lineSeparator());
+ }
+ }
+ if (DEBUG_TO_STDERR) {
+ System.err.println(builder.toString());
+ } else if (DEBUG_LOG.isLoggable(Level.SEVERE)) {
+ DEBUG_LOG.severe(builder.toString());
+ }
+ }
+ }
+
+ /**
* Retrieves the printable ASCII representation of the provided byte.
*
* @param b
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CachedConnectionPool.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CachedConnectionPool.java
index c2176e9..86d41c1 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CachedConnectionPool.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CachedConnectionPool.java
@@ -27,8 +27,11 @@
package org.forgerock.opendj.ldap;
+import static com.forgerock.opendj.util.StaticUtils.DEBUG_ENABLED;
import static com.forgerock.opendj.util.StaticUtils.DEBUG_LOG;
import static com.forgerock.opendj.util.StaticUtils.DEFAULT_SCHEDULER;
+import static com.forgerock.opendj.util.StaticUtils.getStackTraceIfDebugEnabled;
+import static com.forgerock.opendj.util.StaticUtils.logIfDebugEnabled;
import static org.forgerock.opendj.ldap.CoreMessages.ERR_CONNECTION_POOL_CLOSING;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
@@ -124,7 +127,7 @@
* the client application closes this connection. More specifically, pooled
* connections are not actually stored in the internal queue.
*/
- private final class PooledConnection implements Connection, ConnectionEventListener {
+ class PooledConnection implements Connection, ConnectionEventListener {
private final Connection connection;
private ErrorResultException error = null;
private final AtomicBoolean isClosed = new AtomicBoolean(false);
@@ -132,7 +135,7 @@
private List<ConnectionEventListener> listeners = null;
private final Object stateLock = new Object();
- private PooledConnection(final Connection connection) {
+ PooledConnection(final Connection connection) {
this.connection = connection;
}
@@ -582,6 +585,24 @@
}
}
+ private final class DebugEnabledPooledConnection extends PooledConnection {
+ private final StackTraceElement[] stackTrace;
+
+ private DebugEnabledPooledConnection(final Connection connection,
+ final StackTraceElement[] stackTrace) {
+ super(connection);
+ this.stackTrace = stackTrace;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (!isClosed()) {
+ logIfDebugEnabled("CONNECTION POOL: connection leaked! It was allocated here: ",
+ stackTrace);
+ }
+ }
+ }
+
/**
* A queue element is either a pending connection request future awaiting an
* {@code Connection} or it is an unused {@code Connection} awaiting a
@@ -590,17 +611,21 @@
private static final class QueueElement {
private final long timestampMillis;
private final Object value;
+ private final StackTraceElement[] stack;
QueueElement(final Connection connection, final long timestampMillis) {
this.value = connection;
this.timestampMillis = timestampMillis;
+ this.stack = null;
}
- QueueElement(final ResultHandler<? super Connection> handler, final long timestampMillis) {
+ QueueElement(final ResultHandler<? super Connection> handler, final long timestampMillis,
+ final StackTraceElement[] stack) {
this.value =
new AsynchronousFutureResult<Connection, ResultHandler<? super Connection>>(
handler);
this.timestampMillis = timestampMillis;
+ this.stack = stack;
}
@Override
@@ -608,6 +633,10 @@
return String.valueOf(value);
}
+ StackTraceElement[] getStackTrace() {
+ return stack;
+ }
+
Connection getWaitingConnection() {
if (value instanceof Connection) {
return (Connection) value;
@@ -740,7 +769,9 @@
} else if (hasWaitingConnections()) {
holder = queue.removeFirst();
} else {
- holder = new QueueElement(handler, timeSource.currentTimeMillis());
+ holder =
+ new QueueElement(handler, timeSource.currentTimeMillis(),
+ getStackTraceIfDebugEnabled());
queue.add(holder);
}
}
@@ -749,7 +780,8 @@
// There was a completed connection attempt.
final Connection connection = holder.getWaitingConnection();
if (connection.isValid()) {
- final PooledConnection pooledConnection = new PooledConnection(connection);
+ final PooledConnection pooledConnection =
+ newPooledConnection(connection, getStackTraceIfDebugEnabled());
if (handler != null) {
handler.handleResult(pooledConnection);
}
@@ -854,7 +886,17 @@
}
}
} else {
- holder.getWaitingFuture().handleResult(new PooledConnection(connection));
+ holder.getWaitingFuture().handleResult(
+ newPooledConnection(connection, holder.getStackTrace()));
+ }
+ }
+
+ private PooledConnection newPooledConnection(final Connection connection,
+ final StackTraceElement[] stack) {
+ if (!DEBUG_ENABLED) {
+ return new PooledConnection(connection);
+ } else {
+ return new DebugEnabledPooledConnection(connection, stack);
}
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionFactoryTestCase.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionFactoryTestCase.java
index 8ceb48f..9c2ec9a 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionFactoryTestCase.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionFactoryTestCase.java
@@ -95,7 +95,7 @@
}
public void handleResult(final Connection con) {
- //
+ con.close();
latch.countDown();
}
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionPoolTestCase.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionPoolTestCase.java
index 0dd7596..bd19355 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionPoolTestCase.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionPoolTestCase.java
@@ -264,6 +264,7 @@
pc1.close();
pc2.close();
+ pc3.close();
pool.close();
}
--
Gitblit v1.10.0