/* * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, BELLSOFT. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * LoadLibraryUnload class calls ClassLoader.loadedLibrary from multiple threads */ /* * The driver for this test is LoadLibraryUnloadTest.java. * * @bug 8266310 * @summary Loads a native library from multiple class loaders and multiple * threads. This creates a race for loading the library. The winner * loads the library in two threads. All threads except two would fail * with UnsatisfiedLinkError when the class being loaded is already * loaded in a different class loader that won the race. The test * checks that the loaded class is GC'ed, that means the class loader * is GC'ed and the native library is unloaded. */ import jdk.test.lib.Asserts; import jdk.test.lib.Utils; import java.lang.*; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.reflect.*; import java.lang.ref.WeakReference; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import jdk.test.lib.util.ForceGC; import p.Class1; public class LoadLibraryUnload { private static class TestLoader extends URLClassLoader { public TestLoader() throws Exception { super(new URL[] { Path.of(System.getProperty("test.classes")).toUri().toURL() }); } @Override public Class loadClass(String name) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class clazz = findLoadedClass(name); if (clazz == null) { try { clazz = findClass(name); } catch (ClassNotFoundException ignore) { } if (clazz == null) { clazz = super.loadClass(name); } } return clazz; } } } private static class LoadLibraryFromClass implements Runnable { Object object; Method method; Object canary; public LoadLibraryFromClass(Class fromClass, Object canary) { try { this.object = fromClass.newInstance(); this.method = fromClass.getDeclaredMethod("loadLibrary", Object.class); this.canary = canary; } catch (ReflectiveOperationException roe) { throw new RuntimeException(roe); } } @Override public void run() { try { method.invoke(object, canary); } catch (ReflectiveOperationException roe) { throw new RuntimeException(roe); } } } public static void main(String[] args) throws Exception { int LOADER_COUNT = 5; List threads = new ArrayList<>(); Object[] canary = new Object[LOADER_COUNT]; final WeakReference wCanary[] = new WeakReference[LOADER_COUNT]; for (int i = 0 ; i < LOADER_COUNT ; i++) { // LOADER_COUNT loaders and 2X threads in total. // winner loads the library in 2 threads canary[i] = new Object(); wCanary[i] = new WeakReference<>(canary[i], null); Class clazz = new TestLoader().loadClass("p.Class1"); threads.add(new Thread(new LoadLibraryFromClass(clazz, canary[i]))); threads.add(new Thread(new LoadLibraryFromClass(clazz, canary[i]))); } final Set exceptions = ConcurrentHashMap.newKeySet(); threads.forEach( t -> { t.setUncaughtExceptionHandler((th, ex) -> { // collect the root cause of each failure Throwable rootCause = ex; while((ex = ex.getCause()) != null) { rootCause = ex; } exceptions.add(rootCause); }); t.start(); }); // wait for all threads to finish for (Thread t : threads) { t.join(); } // expect all errors to be UnsatisfiedLinkError boolean allAreUnsatisfiedLinkError = exceptions .stream() .map(e -> e instanceof UnsatisfiedLinkError) .reduce(true, (i, a) -> i && a); // expect exactly 8 errors int expectedErrorCount = (LOADER_COUNT - 1) * 2; Asserts.assertTrue(exceptions.size() == expectedErrorCount, "Expected to see " + expectedErrorCount + " failing threads"); Asserts.assertTrue(allAreUnsatisfiedLinkError, "All errors have to be UnsatisfiedLinkError"); // release strong refs threads = null; canary = null; exceptions.clear(); // Wait for the canary for each of the libraries to be GC'd (cleared) boolean allClear = ForceGC.wait(() -> { for (int i = 0; i < wCanary.length; i++) { if (!wCanary[i].refersTo(null)) { return false; } } return true; }); Asserts.assertTrue(allClear, "Not all WeakReferences cleared"); // Ensure the WeakReferences are strongly referenced until they can be dequeued Reference.reachabilityFence(wCanary); } }