1 /* 2 * Copyright (c) 2013, 2018, 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.access.JavaAWTAccess; 39 import jdk.internal.access.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.access 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 Object logger; 145 public void run() { 146 sem3.release(); 147 try { 148 // Wait until Thread1 is in FakeAppContext constructor 149 sem2.acquire(); 150 System.out.println("Sem2 acquired: Thread #1 will be waiting to acquire Sem"); 151 } catch (InterruptedException ie) { 152 Thread.interrupted(); 153 } 154 System.out.println("Logger.getLogger(name).info(name)"); 155 // stick the logger in an instance variable to prevent it 156 // from being garbage collected before the main thread 157 // calls LogManager.getLogger() below. 158 logger = Logger.getLogger(test.name());//.info(name); 159 System.out.println("Done: Logger.getLogger(name).info(name)"); 160 } 161 }; 162 t2.setDaemon(true); 163 t2.start(); 164 System.out.println("Should exit now..."); 165 Thread detector = new DeadlockDetector(); 166 detector.start(); 167 168 // Wait for the 3 threads to start 169 sem3.acquire(); 170 171 // Now wait for t1 & t2 to finish, or for a deadlock to be detected. 172 while (goOn && (t1.isAlive() || t2.isAlive())) { 173 if (t2.isAlive()) t2.join(1000); 174 if (test == TestCase.UNSECURE && System.getSecurityManager() == null) { 175 // if there's no security manager, AppContext.getAppContext() is 176 // not called - so Thread t2 will not end up calling 177 // sem.release(). In that case we must release the semaphore here 178 // so that t1 can proceed. 179 if (LogManager.getLogManager().getLogger(TestCase.UNSECURE.name()) != null) { 180 // means Thread t2 has created the logger 181 sem.release(); 182 } 183 } 184 if (t1.isAlive()) t1.join(1000); 185 } 186 if (thrown != null) { 187 throw thrown; 188 } 189 } 190 191 // Thrown by the deadlock detector 192 static final class DeadlockException extends RuntimeException { 193 public DeadlockException(String message) { 194 super(message); 195 } 196 @Override 197 public void printStackTrace() { 198 } 199 } 200 201 public static void main(String[] args) throws Exception { 202 203 if (args.length == 0) { 204 args = new String[] { "SECURE" }; 205 } 206 207 // If we don't initialize LogManager here, there will be 208 // a deadlock. 209 // See <https://bugs.openjdk.java.net/browse/JDK-8065709?focusedCommentId=13582038&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13582038> 210 // for more details. 211 Logger.getLogger("main").info("starting..."); 212 try { 213 TestCase.valueOf(args[0]).run(); 214 System.out.println("Test "+args[0]+" Passed"); 215 } catch(Throwable t) { 216 System.err.println("Test " + args[0] +" failed: " + t); 217 t.printStackTrace(); 218 } 219 } 220 221 // Called by the deadlock detector when a deadlock is found. 222 static void fail(Exception x) { 223 x.printStackTrace(); 224 if (thrown == null) { 225 thrown = x; 226 } 227 goOn = false; 228 } 229 230 // A thread that detect deadlocks. 231 static final class DeadlockDetector extends Thread { 232 233 public DeadlockDetector() { 234 this.setDaemon(true); 235 } 236 237 @Override 238 public void run() { 239 sem3.release(); 240 Configure.doPrivileged(this::loop); 241 } 242 public void loop() { 243 while(goOn) { 244 try { 245 long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads(); 246 ids = ids == null ? new long[0] : ids; 247 if (ids.length == 1) { 248 throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]); 249 } else if (ids.length > 0) { 250 ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(ids, Integer.MAX_VALUE); 251 System.err.println("Found "+ids.length+" deadlocked threads: "); 252 for (ThreadInfo inf : infos) { 253 System.err.println(inf); 254 } 255 throw new DeadlockException("Found "+ids.length+" deadlocked threads"); 256 } 257 Thread.sleep(100); 258 } catch(InterruptedException | RuntimeException x) { 259 fail(x); 260 } 261 } 262 } 263 264 } 265 266 // A helper class to configure the security manager for the test, 267 // and bypass it when needed. 268 static class Configure { 269 static Policy policy = null; 270 static final ThreadLocal<AtomicBoolean> allowAll = new ThreadLocal<AtomicBoolean>() { 271 @Override 272 protected AtomicBoolean initialValue() { 273 return new AtomicBoolean(false); 274 } 275 }; 276 static void setUp(TestCase test) { 277 switch (test) { 278 case SECURE: 279 if (policy == null && System.getSecurityManager() != null) { 280 throw new IllegalStateException("SecurityManager already set"); 281 } else if (policy == null) { 282 policy = new SimplePolicy(TestCase.SECURE, allowAll); 283 Policy.setPolicy(policy); 284 System.setSecurityManager(new SecurityManager()); 285 } 286 if (System.getSecurityManager() == null) { 287 throw new IllegalStateException("No SecurityManager."); 288 } 289 if (policy == null) { 290 throw new IllegalStateException("policy not configured"); 291 } 292 break; 293 case UNSECURE: 294 if (System.getSecurityManager() != null) { 295 throw new IllegalStateException("SecurityManager already set"); 296 } 297 break; 298 default: 299 new InternalError("No such testcase: " + test); 300 } 301 } 302 static void doPrivileged(Runnable run) { 303 allowAll.get().set(true); 304 try { 305 run.run(); 306 } finally { 307 allowAll.get().set(false); 308 } 309 } 310 } 311 312 // A Helper class to build a set of permissions. 313 static final class PermissionsBuilder { 314 final Permissions perms; 315 public PermissionsBuilder() { 316 this(new Permissions()); 317 } 318 public PermissionsBuilder(Permissions perms) { 319 this.perms = perms; 320 } 321 public PermissionsBuilder add(Permission p) { 322 perms.add(p); 323 return this; 324 } 325 public PermissionsBuilder addAll(PermissionCollection col) { 326 if (col != null) { 327 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) { 328 perms.add(e.nextElement()); 329 } 330 } 331 return this; 332 } 333 public Permissions toPermissions() { 334 final PermissionsBuilder builder = new PermissionsBuilder(); 335 builder.addAll(perms); 336 return builder.perms; 337 } 338 } 339 340 // Policy for the test... 341 public static class SimplePolicy extends Policy { 342 343 final Permissions permissions; 344 final Permissions allPermissions; 345 final ThreadLocal<AtomicBoolean> allowAll; // actually: this should be in a thread locale 346 public SimplePolicy(TestCase test, ThreadLocal<AtomicBoolean> allowAll) { 347 this.allowAll = allowAll; 348 // we don't actually need any permission to create our 349 // FileHandlers because we're passing invalid parameters 350 // which will make the creation fail... 351 permissions = new Permissions(); 352 permissions.add(new RuntimePermission("accessClassInPackage.jdk.internal.access")); 353 354 // these are used for configuring the test itself... 355 allPermissions = new Permissions(); 356 allPermissions.add(new java.security.AllPermission()); 357 358 } 359 360 @Override 361 public boolean implies(ProtectionDomain domain, Permission permission) { 362 if (allowAll.get().get()) return allPermissions.implies(permission); 363 return permissions.implies(permission); 364 } 365 366 @Override 367 public PermissionCollection getPermissions(CodeSource codesource) { 368 return new PermissionsBuilder().addAll(allowAll.get().get() 369 ? allPermissions : permissions).toPermissions(); 370 } 371 372 @Override 373 public PermissionCollection getPermissions(ProtectionDomain domain) { 374 return new PermissionsBuilder().addAll(allowAll.get().get() 375 ? allPermissions : permissions).toPermissions(); 376 } 377 } 378 379 }