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