blob: 5ff94485d38e71fc8242001811785b1ecd5470cd [file] [log] [blame]
/*
* Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.gjc.util;
import static java.util.logging.Level.WARNING;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
import org.glassfish.resourcebase.resources.api.PoolInfo;
import com.sun.enterprise.util.i18n.StringManager;
import com.sun.gjc.monitoring.StatementLeakProbeProvider;
import com.sun.logging.LogDomains;
/**
* Statement leak detector that prints the stack trace of the thread when a
* statement object is leaked. Once the leak timeout expires, a statement leak
* is assumed and the caller stack trace is printed. When statement-leak-reclaim
* is set to true, the statement object is reclaimed.
*
* @author Shalini M
*/
public class StatementLeakDetector {
private HashMap<Statement, StackTraceElement[]> statementLeakThreadStackHashMap;
private HashMap<Statement, StatementLeakTask> statementLeakTimerTaskHashMap;
private PoolInfo poolInfo;
private boolean statementLeakTracing;
private long statementLeakTimeoutInMillis;
private boolean statementLeakReclaim;
// Lock on HashMap to trace statement leaks
private final Object statementLeakLock;
private Map<Statement, StatementLeakListener> listeners;
private final static Logger _logger = LogDomains.getLogger(StatementLeakDetector.class, LogDomains.RSR_LOGGER);
private final static StringManager localStrings = StringManager.getManager(StatementLeakDetector.class);
private Timer timer;
private StatementLeakProbeProvider stmtLeakProbeProvider = null;
public StatementLeakDetector(PoolInfo poolInfo, boolean leakTracing, long leakTimeoutInMillis, boolean leakReclaim,
Timer timer) {
this.poolInfo = poolInfo;
statementLeakThreadStackHashMap = new HashMap<Statement, StackTraceElement[]>();
statementLeakTimerTaskHashMap = new HashMap<Statement, StatementLeakTask>();
listeners = new HashMap<Statement, StatementLeakListener>();
statementLeakLock = new Object();
statementLeakTracing = leakTracing;
statementLeakTimeoutInMillis = leakTimeoutInMillis;
statementLeakReclaim = leakReclaim;
this.timer = timer;
stmtLeakProbeProvider = new StatementLeakProbeProvider();
}
public void reset(boolean leakTracing, long leakTimeoutInMillis, boolean leakReclaim) {
if (!statementLeakTracing && leakTracing) {
clearAllStatementLeakTasks();
}
statementLeakTracing = leakTracing;
statementLeakTimeoutInMillis = leakTimeoutInMillis;
statementLeakReclaim = leakReclaim;
}
private void registerListener(Statement stmt, StatementLeakListener listener) {
listeners.put(stmt, listener);
}
private void unRegisterListener(Statement stmt) {
listeners.remove(stmt);
}
/**
* Starts statement leak tracing
*
* @param stmt Statement which needs to be traced
* @param listener Leak Listener
*/
public void startStatementLeakTracing(Statement stmt, StatementLeakListener listener) {
synchronized (statementLeakLock) {
if (!statementLeakThreadStackHashMap.containsKey(stmt)) {
statementLeakThreadStackHashMap.put(stmt, Thread.currentThread().getStackTrace());
StatementLeakTask statementLeakTask = new StatementLeakTask(stmt);
statementLeakTimerTaskHashMap.put(stmt, statementLeakTask);
registerListener(stmt, listener);
if (timer != null) {
timer.schedule(statementLeakTask, statementLeakTimeoutInMillis);
_logger.finest("Scheduled Statement leak tracing timer task");
}
}
}
}
/**
* Stops statement leak tracing
*
* @param stmt Statement which needs to be traced
* @param listener Leak Listener
*/
public void stopStatementLeakTracing(Statement stmt, StatementLeakListener listener) {
synchronized (statementLeakLock) {
if (statementLeakThreadStackHashMap.containsKey(stmt)) {
statementLeakThreadStackHashMap.remove(stmt);
StatementLeakTask statementLeakTask = statementLeakTimerTaskHashMap.remove(stmt);
statementLeakTask.cancel();
timer.purge();
_logger.finest("Stopped Statement leak tracing timer task");
unRegisterListener(stmt);
}
}
}
/**
* Logs the potential statement leaks
*
* @param stmt Statement that is not closed by application
*/
private void potentialStatementLeakFound(Statement stmt) {
synchronized (statementLeakLock) {
if (statementLeakThreadStackHashMap.containsKey(stmt)) {
StackTraceElement[] threadStack = statementLeakThreadStackHashMap.remove(stmt);
StatementLeakListener stmtLeakListener = listeners.get(stmt);
stmtLeakProbeProvider.potentialStatementLeakEvent(poolInfo.getName(), poolInfo.getApplicationName(),
poolInfo.getModuleName());
printStatementLeakTrace(threadStack);
statementLeakTimerTaskHashMap.remove(stmt);
if (statementLeakReclaim) {
try {
stmtLeakListener.reclaimStatement();
} catch (SQLException ex) {
Object[] params = new Object[] { poolInfo, ex };
_logger.log(WARNING, "statement.leak.detector_reclaim_statement_failure", params);
}
}
// Unregister here as the listeners would still be present in the map.
unRegisterListener(stmt);
}
}
}
/**
* Prints the stack trace of thread leaking statement to server logs
*
* @param threadStackTrace Application(caller) thread stack trace
*/
private void printStatementLeakTrace(StackTraceElement[] threadStackTrace) {
StringBuffer stackTrace = new StringBuffer();
String msg = localStrings
.getStringWithDefault(
"potential.statement.leak.msg", "A potential statement leak detected for connection pool "
+ poolInfo + ". The stack trace of the thread is provided below : ",
new Object[] { poolInfo });
stackTrace.append(msg);
stackTrace.append("\n");
for (int i = 2; i < threadStackTrace.length; i++) {
stackTrace.append(threadStackTrace[i].toString());
stackTrace.append("\n");
}
_logger.log(WARNING, stackTrace.toString(), "ConnectionPoolName=" + poolInfo);
}
/**
* Clear all statement leak tracing tasks in case of statement leak tracing
* being turned off
*/
public void clearAllStatementLeakTasks() {
synchronized (statementLeakLock) {
Iterator<Map.Entry<Statement, StatementLeakTask>> entryIterator = statementLeakTimerTaskHashMap.entrySet()
.iterator();
while (entryIterator.hasNext()) {
Map.Entry<Statement, StatementLeakTask> entry = entryIterator.next();
StatementLeakTask statementLeakTask = entry.getValue();
statementLeakTask.cancel();
}
if (timer != null)
timer.purge();
statementLeakThreadStackHashMap.clear();
statementLeakTimerTaskHashMap.clear();
}
}
private class StatementLeakTask extends TimerTask {
private Statement statement;
StatementLeakTask(Statement stmt) {
this.statement = stmt;
}
public void run() {
potentialStatementLeakFound(statement);
}
}
}