| /* | |
| * Copyright (c) 2013, 2022, Oracle and/or its affiliates. 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. | |
| */ | |
| import java.lang.management.ManagementFactory; | |
| import java.lang.management.ThreadInfo; | |
| import java.security.CodeSource; | |
| import java.security.Permission; | |
| import java.security.PermissionCollection; | |
| import java.security.Permissions; | |
| import java.security.Policy; | |
| import java.security.ProtectionDomain; | |
| import java.util.Enumeration; | |
| import java.util.concurrent.Semaphore; | |
| import java.util.concurrent.atomic.AtomicBoolean; | |
| import java.util.concurrent.atomic.AtomicInteger; | |
| import java.util.logging.LogManager; | |
| import java.util.logging.Logger; | |
| import jdk.internal.access.JavaAWTAccess; | |
| import jdk.internal.access.SharedSecrets; | |
| /** | |
| * @test | |
| * @bug 8065991 | |
| * @summary check that when LogManager is initialized, a deadlock similar | |
| * to that described in 8065709 will not occur. | |
| * @modules java.base/jdk.internal.access | |
| * java.logging | |
| * java.management | |
| * @run main/othervm LogManagerAppContextDeadlock UNSECURE | |
| * @run main/othervm LogManagerAppContextDeadlock SECURE | |
| * | |
| * @author danielfuchs | |
| */ | |
| public class LogManagerAppContextDeadlock { | |
| public static final Semaphore sem = new Semaphore(0); | |
| public static final Semaphore sem2 = new Semaphore(0); | |
| public static final Semaphore sem3 = new Semaphore(-2); | |
| public static volatile boolean goOn = true; | |
| public static volatile Exception thrown; | |
| // Emulate EventQueue | |
| static class FakeEventQueue { | |
| static final Logger logger = Logger.getLogger("foo"); | |
| } | |
| // Emulate AppContext | |
| static class FakeAppContext { | |
| static final AtomicInteger numAppContexts = new AtomicInteger(0); | |
| static final class FakeAppContextLock {} | |
| static final FakeAppContextLock lock = new FakeAppContextLock(); | |
| static volatile FakeAppContext appContext; | |
| final FakeEventQueue queue; | |
| FakeAppContext() { | |
| appContext = this; | |
| numAppContexts.incrementAndGet(); | |
| // release sem2 to let Thread t2 call Logger.getLogger(). | |
| sem2.release(); | |
| try { | |
| // Wait until we JavaAWTAccess is called by LogManager. | |
| // Thread 2 will call Logger.getLogger() which will | |
| // trigger a call to JavaAWTAccess - which will release | |
| // sem, thus ensuring that Thread #2 is where we want it. | |
| sem.acquire(); | |
| System.out.println("Sem acquired: Thread #2 has called JavaAWTAccess"); | |
| } catch(InterruptedException x) { | |
| Thread.interrupted(); | |
| } | |
| queue = new FakeEventQueue(); | |
| } | |
| static FakeAppContext getAppContext() { | |
| synchronized (lock) { | |
| if (numAppContexts.get() == 0) { | |
| return new FakeAppContext(); | |
| } | |
| return appContext; | |
| } | |
| } | |
| static { | |
| SharedSecrets.setJavaAWTAccess(new JavaAWTAccess() { | |
| @Override | |
| public Object getAppletContext() { | |
| if (numAppContexts.get() == 0) return null; | |
| // We are in JavaAWTAccess, we can release sem and let | |
| // FakeAppContext constructor proceeed. | |
| System.out.println("Releasing Sem"); | |
| sem.release(); | |
| return getAppContext(); | |
| } | |
| }); | |
| } | |
| } | |
| // Test with or without a security manager | |
| public static enum TestCase { | |
| UNSECURE, SECURE; | |
| public void run() throws Exception { | |
| System.out.println("Running test case: " + name()); | |
| Configure.setUp(this); | |
| test(this); | |
| } | |
| } | |
| public static void test(TestCase test) throws Exception { | |
| Thread t1 = new Thread() { | |
| @Override | |
| public void run() { | |
| sem3.release(); | |
| System.out.println("FakeAppContext.getAppContext()"); | |
| FakeAppContext.getAppContext(); | |
| System.out.println("Done: FakeAppContext.getAppContext()"); | |
| } | |
| }; | |
| t1.setDaemon(true); | |
| t1.start(); | |
| Thread t2 = new Thread() { | |
| public Object logger; | |
| public void run() { | |
| sem3.release(); | |
| try { | |
| // Wait until Thread1 is in FakeAppContext constructor | |
| sem2.acquire(); | |
| System.out.println("Sem2 acquired: Thread #1 will be waiting to acquire Sem"); | |
| } catch (InterruptedException ie) { | |
| Thread.interrupted(); | |
| } | |
| System.out.println("Logger.getLogger(name).info(name)"); | |
| // stick the logger in an instance variable to prevent it | |
| // from being garbage collected before the main thread | |
| // calls LogManager.getLogger() below. | |
| logger = Logger.getLogger(test.name());//.info(name); | |
| System.out.println("Done: Logger.getLogger(name).info(name)"); | |
| } | |
| }; | |
| t2.setDaemon(true); | |
| t2.start(); | |
| System.out.println("Should exit now..."); | |
| Thread detector = new DeadlockDetector(); | |
| detector.start(); | |
| // Wait for the 3 threads to start | |
| sem3.acquire(); | |
| // Now wait for t1 & t2 to finish, or for a deadlock to be detected. | |
| while (goOn && (t1.isAlive() || t2.isAlive())) { | |
| if (t2.isAlive()) t2.join(1000); | |
| if (test == TestCase.UNSECURE && System.getSecurityManager() == null) { | |
| // if there's no security manager, AppContext.getAppContext() is | |
| // not called - so Thread t2 will not end up calling | |
| // sem.release(). In that case we must release the semaphore here | |
| // so that t1 can proceed. | |
| if (LogManager.getLogManager().getLogger(TestCase.UNSECURE.name()) != null) { | |
| // means Thread t2 has created the logger | |
| sem.release(); | |
| } | |
| } | |
| if (t1.isAlive()) t1.join(1000); | |
| } | |
| if (thrown != null) { | |
| throw thrown; | |
| } | |
| } | |
| // Thrown by the deadlock detector | |
| static final class DeadlockException extends RuntimeException { | |
| public DeadlockException(String message) { | |
| super(message); | |
| } | |
| @Override | |
| public void printStackTrace() { | |
| } | |
| } | |
| public static void main(String[] args) throws Exception { | |
| if (args.length == 0) { | |
| args = new String[] { "SECURE" }; | |
| } | |
| // If we don't initialize LogManager here, there will be | |
| // a deadlock. | |
| // See <https://bugs.openjdk.org/browse/JDK-8065709?focusedCommentId=13582038&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13582038> | |
| // for more details. | |
| Logger.getLogger("main").info("starting..."); | |
| try { | |
| TestCase.valueOf(args[0]).run(); | |
| System.out.println("Test "+args[0]+" Passed"); | |
| } catch(Throwable t) { | |
| System.err.println("Test " + args[0] +" failed: " + t); | |
| t.printStackTrace(); | |
| } | |
| } | |
| // Called by the deadlock detector when a deadlock is found. | |
| static void fail(Exception x) { | |
| x.printStackTrace(); | |
| if (thrown == null) { | |
| thrown = x; | |
| } | |
| goOn = false; | |
| } | |
| // A thread that detect deadlocks. | |
| static final class DeadlockDetector extends Thread { | |
| public DeadlockDetector() { | |
| this.setDaemon(true); | |
| } | |
| @Override | |
| public void run() { | |
| sem3.release(); | |
| Configure.doPrivileged(this::loop); | |
| } | |
| public void loop() { | |
| while(goOn) { | |
| try { | |
| long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads(); | |
| ids = ids == null ? new long[0] : ids; | |
| if (ids.length == 1) { | |
| throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]); | |
| } else if (ids.length > 0) { | |
| ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(ids, Integer.MAX_VALUE); | |
| System.err.println("Found "+ids.length+" deadlocked threads: "); | |
| for (ThreadInfo inf : infos) { | |
| System.err.println(inf); | |
| } | |
| throw new DeadlockException("Found "+ids.length+" deadlocked threads"); | |
| } | |
| Thread.sleep(100); | |
| } catch(InterruptedException | RuntimeException x) { | |
| fail(x); | |
| } | |
| } | |
| } | |
| } | |
| // A helper class to configure the security manager for the test, | |
| // and bypass it when needed. | |
| static class Configure { | |
| static Policy policy = null; | |
| static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() { | |
| @Override | |
| protected AtomicBoolean initialValue() { | |
| return new AtomicBoolean(false); | |
| } | |
| }; | |
| static void setUp(TestCase test) { | |
| switch (test) { | |
| case SECURE: | |
| if (policy == null && System.getSecurityManager() != null) { | |
| throw new IllegalStateException("SecurityManager already set"); | |
| } else if (policy == null) { | |
| policy = new SimplePolicy(TestCase.SECURE, allowAll); | |
| Policy.setPolicy(policy); | |
| System.setSecurityManager(new SecurityManager()); | |
| } | |
| if (System.getSecurityManager() == null) { | |
| throw new IllegalStateException("No SecurityManager."); | |
| } | |
| if (policy == null) { | |
| throw new IllegalStateException("policy not configured"); | |
| } | |
| break; | |
| case UNSECURE: | |
| if (System.getSecurityManager() != null) { | |
| throw new IllegalStateException("SecurityManager already set"); | |
| } | |
| break; | |
| default: | |
| new InternalError("No such testcase: " + test); | |
| } | |
| } | |
| static void doPrivileged(Runnable run) { | |
| allowAll.get().set(true); | |
| try { | |
| run.run(); | |
| } finally { | |
| allowAll.get().set(false); | |
| } | |
| } | |
| } | |
| // A Helper class to build a set of permissions. | |
| static final class PermissionsBuilder { | |
| final Permissions perms; | |
| public PermissionsBuilder() { | |
| this(new Permissions()); | |
| } | |
| public PermissionsBuilder(Permissions perms) { | |
| this.perms = perms; | |
| } | |
| public PermissionsBuilder add(Permission p) { | |
| perms.add(p); | |
| return this; | |
| } | |
| public PermissionsBuilder addAll(PermissionCollection col) { | |
| if (col != null) { | |
| for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) { | |
| perms.add(e.nextElement()); | |
| } | |
| } | |
| return this; | |
| } | |
| public Permissions toPermissions() { | |
| final PermissionsBuilder builder = new PermissionsBuilder(); | |
| builder.addAll(perms); | |
| return builder.perms; | |
| } | |
| } | |
| // Policy for the test... | |
| public static class SimplePolicy extends Policy { | |
| static final Policy DEFAULT_POLICY = Policy.getPolicy(); | |
| final Permissions permissions; | |
| final Permissions allPermissions; | |
| final ThreadLocal<AtomicBoolean> allowAll; // actually: this should be in a thread locale | |
| public SimplePolicy(TestCase test, ThreadLocal<AtomicBoolean> allowAll) { | |
| this.allowAll = allowAll; | |
| // we don't actually need any permission to create our | |
| // FileHandlers because we're passing invalid parameters | |
| // which will make the creation fail... | |
| permissions = new Permissions(); | |
| permissions.add(new RuntimePermission("accessClassInPackage.jdk.internal.access")); | |
| // these are used for configuring the test itself... | |
| allPermissions = new Permissions(); | |
| allPermissions.add(new java.security.AllPermission()); | |
| } | |
| @Override | |
| public boolean implies(ProtectionDomain domain, Permission permission) { | |
| if (allowAll.get().get()) return allPermissions.implies(permission); | |
| return permissions.implies(permission) || DEFAULT_POLICY.implies(domain, permission); | |
| } | |
| @Override | |
| public PermissionCollection getPermissions(CodeSource codesource) { | |
| return new PermissionsBuilder().addAll(allowAll.get().get() | |
| ? allPermissions : permissions).toPermissions(); | |
| } | |
| @Override | |
| public PermissionCollection getPermissions(ProtectionDomain domain) { | |
| return new PermissionsBuilder().addAll(allowAll.get().get() | |
| ? allPermissions : permissions).toPermissions(); | |
| } | |
| } | |
| } |