/* * Copyright (c) 2015 Hudson. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Hudson - initial API and implementation and/or initial documentation */ package hudson.util; import java.lang.ref.Reference; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Set; import org.eclipse.hudson.init.InitialSetup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.context.SecurityContextImpl; /** * * @author Bob Foster */ public class ThreadLocalUtils { private static final Logger LOGGER = LoggerFactory.getLogger(ThreadLocalUtils.class); private static final boolean VERBOSE = false; /** * Remove thread locals created by instance class loaders. * * @param verbose true if log each time threadlocals found or removed; otherwise silent */ public static void removeThreadLocals() { Set<ThreadLocal> threadLocals = getThreadLocals(getLoaders()); for (ThreadLocal threadLocal : threadLocals) { if (VERBOSE) { Object value = threadLocal.get(); LOGGER.info("Removing thread local with key ["+threadLocal+"] and value ["+value+"]"); } threadLocal.remove(); } } private static ClassLoader[] getLoaders() { ClassLoader[] loaders = new ClassLoader[2]; loaders[0] = ThreadLocalUtils.class.getClassLoader(); loaders[1] = InitialSetup.getHudsonContextClassLoader(); return loaders; } private static Set<ThreadLocal> getThreadLocals(ClassLoader[] loaders) { Set<ThreadLocal> threadLocals = new HashSet<ThreadLocal>(); try { Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); threadLocalsField.setAccessible(true); Object threadLocalTable = threadLocalsField.get(Thread.currentThread()); Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); Field tableField = threadLocalMapClass.getDeclaredField("table"); tableField.setAccessible(true); Object table = tableField.get(threadLocalTable); if (table == null) { return threadLocals; } Field referentField = Reference.class.getDeclaredField("referent"); referentField.setAccessible(true); for (int i=0; i < Array.getLength(table); i++) { Object entry = Array.get(table, i); if (entry != null) { ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry); if (threadLocal != null) { Object value = threadLocal.get(); if (value instanceof SecurityContextImpl) { // This may risk removing threadlocals added by other apps, // but perhaps we are doing them a favor. threadLocals.add(threadLocal); } else { ClassLoader tlClassLoader = threadLocal.getClass().getClassLoader(); for (ClassLoader cl : loaders) { if (tlClassLoader == cl) { threadLocals.add(threadLocal); break; } } } } } } } catch(Exception e) { LOGGER.warn("Exception getting ThreadLocals in thread "+Thread.currentThread().getName(), e); } return threadLocals; } }