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