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 }