1 /*
   2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.lang.management.ManagementFactory;
  25 import java.lang.management.ThreadInfo;
  26 import java.security.CodeSource;
  27 import java.security.Permission;
  28 import java.security.PermissionCollection;
  29 import java.security.Permissions;
  30 import java.security.Policy;
  31 import java.security.ProtectionDomain;
  32 import java.util.Enumeration;
  33 import java.util.concurrent.Semaphore;
  34 import java.util.concurrent.atomic.AtomicBoolean;
  35 import java.util.concurrent.atomic.AtomicInteger;
  36 import java.util.logging.LogManager;
  37 import java.util.logging.Logger;
  38 
  39 /**
  40  * @test
  41  * @bug 8065991
  42  * @summary check that when LogManager is initialized, a deadlock similar
  43  *          to that described in 8065709 will not occur.
  44  * @run main/othervm LogManagerAppContextDeadlock UNSECURE
  45  * @run main/othervm LogManagerAppContextDeadlock SECURE
  46  *
  47  * @author danielfuchs
  48  */
  49 public class LogManagerAppContextDeadlock {
  50 
  51     public static final Semaphore sem = new Semaphore(0);
  52     public static final Semaphore sem2 = new Semaphore(0);
  53     public static final Semaphore sem3 = new Semaphore(-2);
  54     public static volatile boolean goOn = true;
  55     public static volatile Exception thrown;
  56 
  57     // Emulate EventQueue
  58     static class FakeEventQueue {
  59         static final Logger logger = Logger.getLogger("foo");
  60     }
  61 
  62     // Emulate AppContext
  63     static class FakeAppContext {
  64 
  65         final static AtomicInteger numAppContexts = new AtomicInteger(0);
  66         static final class FakeAppContextLock {}
  67         static final FakeAppContextLock lock = new FakeAppContextLock();
  68         static volatile FakeAppContext appContext;
  69 
  70         final FakeEventQueue queue;
  71         FakeAppContext() {
  72             appContext = this;
  73             numAppContexts.incrementAndGet();
  74             // release sem2 to let Thread t2 call Logger.getLogger().
  75             sem2.release();
  76             try {
  77                 // Wait until we JavaAWTAccess is called by LogManager.
  78                 // Thread 2 will call Logger.getLogger() which will
  79                 // trigger a call to JavaAWTAccess - which will release
  80                 // sem, thus ensuring that Thread #2 is where we want it.
  81                 sem.acquire();
  82                 System.out.println("Sem acquired: Thread #2 has called JavaAWTAccess");
  83             } catch(InterruptedException x) {
  84                 Thread.interrupted();
  85             }
  86             queue = new FakeEventQueue();
  87         }
  88 
  89         static FakeAppContext getAppContext() {
  90             synchronized (lock) {
  91                 if (numAppContexts.get() == 0) {
  92                     return new FakeAppContext();
  93                 }
  94                 return appContext;
  95             }
  96         }
  97 
  98         static {
  99             sun.misc.SharedSecrets.setJavaAWTAccess(new sun.misc.JavaAWTAccess() {
 100                 @Override
 101                 public Object getAppletContext() {
 102                     if (numAppContexts.get() == 0) return null;
 103                     // We are in JavaAWTAccess, we can release sem and let
 104                     // FakeAppContext constructor proceeed.
 105                     System.out.println("Releasing Sem");
 106                     sem.release();
 107                     return getAppContext();
 108                 }
 109 
 110             });
 111         }
 112 
 113     }
 114 
 115 
 116     // Test with or without a security manager
 117     public static enum TestCase {
 118         UNSECURE, SECURE;
 119         public void run() throws Exception {
 120             System.out.println("Running test case: " + name());
 121             Configure.setUp(this);
 122             test(this);
 123         }
 124     }
 125 
 126     public static void test(TestCase test) throws Exception {
 127         Thread t1 = new Thread() {
 128             @Override
 129             public void run() {
 130                 sem3.release();
 131                 System.out.println("FakeAppContext.getAppContext()");
 132                 FakeAppContext.getAppContext();
 133                 System.out.println("Done: FakeAppContext.getAppContext()");
 134             }
 135         };
 136         t1.setDaemon(true);
 137         t1.start();
 138         Thread t2 = new Thread() {
 139             public void run() {
 140                 sem3.release();
 141                 try {
 142                     // Wait until Thread1 is in FakeAppContext constructor
 143                     sem2.acquire();
 144                     System.out.println("Sem2 acquired: Thread #1 will be waiting to acquire Sem");
 145                 } catch (InterruptedException ie) {
 146                     Thread.interrupted();
 147                 }
 148                 System.out.println("Logger.getLogger(name).info(name)");
 149                 Logger.getLogger(test.name());//.info(name);
 150                 System.out.println("Done: Logger.getLogger(name).info(name)");
 151             }
 152         };
 153         t2.setDaemon(true);
 154         t2.start();
 155         System.out.println("Should exit now...");
 156         Thread detector = new DeadlockDetector();
 157         detector.start();
 158 
 159         // Wait for the 3 threads to start
 160         sem3.acquire();
 161 
 162         // Now wait for t1 & t2 to finish, or for a deadlock to be detected.
 163         while (goOn && (t1.isAlive() || t2.isAlive())) {
 164             if (t2.isAlive()) t2.join(1000);
 165             if (test == TestCase.UNSECURE && System.getSecurityManager() == null) {
 166                 // if there's no security manager, AppContext.getAppContext() is
 167                 // not called -  so Thread t2 will not end up calling
 168                 // sem.release(). In that case we must release the semaphore here
 169                 // so that t1 can proceed.
 170                 if (LogManager.getLogManager().getLogger(TestCase.UNSECURE.name()) != null) {
 171                     // means Thread t2 has created the logger
 172                     sem.release();
 173                 }
 174             }
 175             if (t1.isAlive()) t1.join(1000);
 176         }
 177         if (thrown != null) {
 178             throw thrown;
 179         }
 180     }
 181 
 182     // Thrown by the deadlock detector
 183     static final class DeadlockException extends RuntimeException {
 184         public DeadlockException(String message) {
 185             super(message);
 186         }
 187         @Override
 188         public void printStackTrace() {
 189         }
 190     }
 191 
 192     public static void main(String[] args) throws Exception {
 193 
 194         if (args.length == 0) {
 195             args = new String[] { "SECURE" };
 196         }
 197 
 198         // If we don't initialize LogManager here, there will be
 199         // a deadlock.
 200         // See <https://bugs.openjdk.java.net/browse/JDK-8065709?focusedCommentId=13582038&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13582038>
 201         // for more details.
 202         Logger.getLogger("main").info("starting...");
 203         try {
 204             TestCase.valueOf(args[0]).run();
 205             System.out.println("Test "+args[0]+" Passed");
 206         } catch(Throwable t) {
 207             System.err.println("Test " + args[0] +" failed: " + t);
 208             t.printStackTrace();
 209         }
 210     }
 211 
 212     // Called by the deadlock detector when a deadlock is found.
 213     static void fail(Exception x) {
 214         x.printStackTrace();
 215         if (thrown == null) {
 216             thrown = x;
 217         }
 218         goOn = false;
 219     }
 220 
 221     // A thread that detect deadlocks.
 222     final static class DeadlockDetector extends Thread {
 223 
 224         public DeadlockDetector() {
 225             this.setDaemon(true);
 226         }
 227 
 228         @Override
 229         public void run() {
 230             sem3.release();
 231             Configure.doPrivileged(this::loop);
 232         }
 233         public void loop() {
 234             while(goOn) {
 235                 try {
 236                     long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
 237                     ids = ids == null ? new long[0] : ids;
 238                     if (ids.length == 1) {
 239                         throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]);
 240                     } else if (ids.length > 0) {
 241                         ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(ids, Integer.MAX_VALUE);
 242                         System.err.println("Found "+ids.length+" deadlocked threads: ");
 243                         for (ThreadInfo inf : infos) {
 244                             System.err.println(inf);
 245                         }
 246                         throw new DeadlockException("Found "+ids.length+" deadlocked threads");
 247                     }
 248                     Thread.sleep(100);
 249                 } catch(InterruptedException | RuntimeException x) {
 250                     fail(x);
 251                 }
 252             }
 253         }
 254 
 255     }
 256 
 257     // A helper class to configure the security manager for the test,
 258     // and bypass it when needed.
 259     static class Configure {
 260         static Policy policy = null;
 261         static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() {
 262             @Override
 263             protected AtomicBoolean initialValue() {
 264                 return  new AtomicBoolean(false);
 265             }
 266         };
 267         static void setUp(TestCase test) {
 268             switch (test) {
 269                 case SECURE:
 270                     if (policy == null && System.getSecurityManager() != null) {
 271                         throw new IllegalStateException("SecurityManager already set");
 272                     } else if (policy == null) {
 273                         policy = new SimplePolicy(TestCase.SECURE, allowAll);
 274                         Policy.setPolicy(policy);
 275                         System.setSecurityManager(new SecurityManager());
 276                     }
 277                     if (System.getSecurityManager() == null) {
 278                         throw new IllegalStateException("No SecurityManager.");
 279                     }
 280                     if (policy == null) {
 281                         throw new IllegalStateException("policy not configured");
 282                     }
 283                     break;
 284                 case UNSECURE:
 285                     if (System.getSecurityManager() != null) {
 286                         throw new IllegalStateException("SecurityManager already set");
 287                     }
 288                     break;
 289                 default:
 290                     new InternalError("No such testcase: " + test);
 291             }
 292         }
 293         static void doPrivileged(Runnable run) {
 294             allowAll.get().set(true);
 295             try {
 296                 run.run();
 297             } finally {
 298                 allowAll.get().set(false);
 299             }
 300         }
 301     }
 302 
 303     // A Helper class to build a set of permissions.
 304     final static class PermissionsBuilder {
 305         final Permissions perms;
 306         public PermissionsBuilder() {
 307             this(new Permissions());
 308         }
 309         public PermissionsBuilder(Permissions perms) {
 310             this.perms = perms;
 311         }
 312         public PermissionsBuilder add(Permission p) {
 313             perms.add(p);
 314             return this;
 315         }
 316         public PermissionsBuilder addAll(PermissionCollection col) {
 317             if (col != null) {
 318                 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
 319                     perms.add(e.nextElement());
 320                 }
 321             }
 322             return this;
 323         }
 324         public Permissions toPermissions() {
 325             final PermissionsBuilder builder = new PermissionsBuilder();
 326             builder.addAll(perms);
 327             return builder.perms;
 328         }
 329     }
 330 
 331     // Policy for the test...
 332     public static class SimplePolicy extends Policy {
 333 
 334         final Permissions permissions;
 335         final Permissions allPermissions;
 336         final ThreadLocal<AtomicBoolean> allowAll; // actually: this should be in a thread locale
 337         public SimplePolicy(TestCase test, ThreadLocal<AtomicBoolean> allowAll) {
 338             this.allowAll = allowAll;
 339             // we don't actually need any permission to create our
 340             // FileHandlers because we're passing invalid parameters
 341             // which will make the creation fail...
 342             permissions = new Permissions();
 343             permissions.add(new RuntimePermission("accessClassInPackage.sun.misc"));
 344 
 345             // these are used for configuring the test itself...
 346             allPermissions = new Permissions();
 347             allPermissions.add(new java.security.AllPermission());
 348 
 349         }
 350 
 351         @Override
 352         public boolean implies(ProtectionDomain domain, Permission permission) {
 353             if (allowAll.get().get()) return allPermissions.implies(permission);
 354             return permissions.implies(permission);
 355         }
 356 
 357         @Override
 358         public PermissionCollection getPermissions(CodeSource codesource) {
 359             return new PermissionsBuilder().addAll(allowAll.get().get()
 360                     ? allPermissions : permissions).toPermissions();
 361         }
 362 
 363         @Override
 364         public PermissionCollection getPermissions(ProtectionDomain domain) {
 365             return new PermissionsBuilder().addAll(allowAll.get().get()
 366                     ? allPermissions : permissions).toPermissions();
 367         }
 368     }
 369 
 370 }