package org.joget.apps.app.web;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Map;
import org.joget.commons.util.LogUtil;
public class ThreadCleaner {
Boolean debug;
public ThreadCleaner() {
debug = false;
}
public ThreadCleaner(Boolean debug) {
this.debug = debug;
}
public Integer cleanThreadLocals() {
int count = 0;
try {
final Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
final Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals");
inheritableThreadLocalsField.setAccessible(true);
for (final Thread thread : Thread.getAllStackTraces().keySet()) {
count += clear(threadLocalsField.get(thread));
count += clear(inheritableThreadLocalsField.get(thread));
}
LogUtil.info(getClass().getName(), "cleaned " + count + " values in ThreadLocals");
} catch (Exception e) {
throw new Error("ThreadLocalCleaner.cleanThreadLocals()", e);
}
return count;
}
public Integer cleanThreads() {
int count = 0;
try {
final Field field = Class.forName("java.lang.ApplicationShutdownHooks").getDeclaredField("hooks");
field.setAccessible(true);
Map<Thread, Thread> shutdownHooks = (Map<Thread, Thread>) field.get(null);
// Iterate copy to avoid ConcurrentModificationException
for (Thread t : new ArrayList<Thread>(shutdownHooks.keySet())) {
if (true || t.getClass().getName().indexOf("DODSPersistentManager") > 0) { // TODO: Set name
// Make sure it's from this web app instance
if (t.getClass().getClassLoader() != null && t.getClass().getClassLoader().equals(this.getClass().getClassLoader())) {
Runtime.getRuntime().removeShutdownHook(t); // Remove hook to avoid PermGen leak
LogUtil.info(getClass().getName(), "Cleaning thread " + t);
count++;
t.start(); // Wait up to 1 minute for thread to run
t.join(60 * 1000); // Wait up to 1 minute for thread to run
}
}
}
} catch (Exception e) {
throw new Error("ThreadLocalCleaner.cleanThreads()", e);
}
return count;
}
private int clear(final Object threadLocalMap) throws Exception {
if (threadLocalMap == null)
return 0;
int count = 0;
final Field tableField = threadLocalMap.getClass().getDeclaredField("table");
tableField.setAccessible(true);
final Object table = tableField.get(threadLocalMap);
for (int i = 0, length = Array.getLength(table); i < length; ++i) {
final Object entry = Array.get(table, i);
if (entry != null) {
final Object threadLocal = ((WeakReference)entry).get();
if (threadLocal != null) {
log(i, threadLocal);
Array.set(table, i, null);
++count;
}
}
}
return count;
}
private void log(int i, final Object threadLocal) {
if (!debug) {
return;
}
if (threadLocal.getClass() != null &&
threadLocal.getClass().getEnclosingClass() != null &&
threadLocal.getClass().getEnclosingClass().getName() != null) {
LogUtil.info(getClass().getName(), "threadLocalMap(" + i + "): " + threadLocal.getClass().getEnclosingClass().getName());
}
else if (threadLocal.getClass() != null &&
threadLocal.getClass().getName() != null) {
LogUtil.info(getClass().getName(), "threadLocalMap(" + i + "): " + threadLocal.getClass().getName());
}
else {
LogUtil.info(getClass().getName(), "threadLocalMap(" + i + "): cannot identify threadlocal class name");
}
}
}