/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package classloading; import com.hazelcast.test.HazelcastParametersRunnerFactory; import com.hazelcast.test.annotation.NightlyTest; import com.hazelcast.util.FilteringClassLoader; import org.junit.After; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import org.junit.runners.Parameterized.UseParametersRunnerFactory; import java.lang.reflect.Method; import java.util.Collections; import static classloading.ThreadLocalLeakTestUtils.checkThreadLocalsForLeaks; import static java.util.Arrays.asList; import static org.junit.Assert.assertTrue; /** * Checks Hazelcast for {@link ThreadLocal} leaks after the shutdown. */ @RunWith(Parameterized.class) @UseParametersRunnerFactory(HazelcastParametersRunnerFactory.class) @Category(NightlyTest.class) public class ThreadLocalLeakTest { private enum ClassLoaderType { FILTERING, OWN, } @Parameter public ClassLoaderType classLoaderType; private Class<?> applicationClazz; @Parameters(name = "classLoaderType:{0}") public static Iterable<Object[]> parameters() { return asList(new Object[][]{ {ClassLoaderType.FILTERING}, //{ClassLoaderType.OWN}, }); } @After public void tearDown() { if (applicationClazz != null) { cleanupLeakingApplication(applicationClazz); } } /** * Checks the detection code with an example application which uses {@link ThreadLocal} with a proper cleanup. */ @Test public void testLeakingApplication_withThreadLocalCleanup() throws Exception { ClassLoader cl = getClassLoader(LeakingApplication.class.getPackage().getName()); applicationClazz = startLeakingApplication(cl, true); checkThreadLocalsForLeaks(cl); } /** * Checks the detection code with an example application which leaks {@link ThreadLocal} instances. */ @Test(expected = AssertionError.class) public void testLeakingApplication_withoutThreadLocalCleanup() throws Exception { ClassLoader cl = getClassLoader(LeakingApplication.class.getPackage().getName()); applicationClazz = startLeakingApplication(cl, false); checkThreadLocalsForLeaks(cl); } /** * Tests Hazelcast for {@link ThreadLocal} leakages. */ @Test public void testHazelcast() throws Exception { ClassLoader cl = getClassLoader("com.hazelcast"); Object isolatedNode = startIsolatedNode(cl); assertTrue(getClusterTime(isolatedNode) > 0); shutdownIsolatedNode(isolatedNode); checkThreadLocalsForLeaks(cl); } private ClassLoader getClassLoader(String packageName) { switch (classLoaderType) { case FILTERING: return new FilteringClassLoader(Collections.<String>emptyList(), packageName); case OWN: return getClass().getClassLoader(); default: throw new AssertionError("Unknown classLoaderType: " + classLoaderType); } } private static Class<?> startLeakingApplication(ClassLoader cl, boolean doCleanup) { Thread thread = Thread.currentThread(); ClassLoader tccl = thread.getContextClassLoader(); try { thread.setContextClassLoader(cl); Class<?> applicationClazz = cl.loadClass(LeakingApplication.class.getCanonicalName()); Method init = applicationClazz.getDeclaredMethod("init", Boolean.class); init.invoke(applicationClazz, doCleanup); return applicationClazz; } catch (Exception e) { throw new RuntimeException("Could not start LeakingApplication", e); } finally { thread.setContextClassLoader(tccl); } } private static void cleanupLeakingApplication(Class<?> applicationClazz) { try { Method cleanup = applicationClazz.getDeclaredMethod("cleanup"); cleanup.invoke(applicationClazz); } catch (Exception e) { throw new RuntimeException("Could not cleanup LeakingApplication", e); } } private static Object startIsolatedNode(ClassLoader cl) { Object isolatedNode; Thread thread = Thread.currentThread(); ClassLoader tccl = thread.getContextClassLoader(); try { thread.setContextClassLoader(cl); Class<?> configClazz = cl.loadClass("com.hazelcast.config.Config"); Object config = configClazz.newInstance(); Method setClassLoader = configClazz.getDeclaredMethod("setClassLoader", ClassLoader.class); setClassLoader.invoke(config, cl); Class<?> hazelcastClazz = cl.loadClass("com.hazelcast.core.Hazelcast"); Method newHazelcastInstance = hazelcastClazz.getDeclaredMethod("newHazelcastInstance", configClazz); isolatedNode = newHazelcastInstance.invoke(hazelcastClazz, config); } catch (Exception e) { throw new RuntimeException("Could not start isolated Hazelcast instance", e); } finally { thread.setContextClassLoader(tccl); } return isolatedNode; } private static void shutdownIsolatedNode(Object isolatedNode) { try { Class<?> instanceClass = isolatedNode.getClass(); Method method = instanceClass.getMethod("shutdown"); method.invoke(isolatedNode); } catch (Exception e) { throw new RuntimeException("Could not start shutdown Hazelcast instance", e); } } private static long getClusterTime(Object isolatedNode) { try { Method getCluster = isolatedNode.getClass().getMethod("getCluster"); Object cluster = getCluster.invoke(isolatedNode); Method getClusterTime = cluster.getClass().getMethod("getClusterTime"); return ((Number) getClusterTime.invoke(cluster)).longValue(); } catch (Exception e) { throw new RuntimeException("Could not get cluster time from Hazelcast instance", e); } } }