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 import java.io.File; 24 import java.io.IOException; 25 import java.lang.management.LockInfo; 26 import java.lang.management.ManagementFactory; 27 import java.lang.management.MonitorInfo; 28 import java.lang.management.ThreadInfo; 29 import java.security.Permission; 30 import java.security.Policy; 31 import java.security.ProtectionDomain; 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.concurrent.atomic.AtomicLong; 35 import java.util.logging.Level; 36 import java.util.logging.LogManager; 37 import java.util.logging.Logger; 38 39 40 /** 41 * @test 42 * @bug 8077846 43 * @summary Test that using a reentrant configuration lock does not introduce 44 * new synchronization issues in Logger and LogManager. This test 45 * focuses more particularly on potential deadlock in 46 * drainLoggerRefQueueBounded / readConfiguration / reset 47 * todo: add at randomness 48 * @run main/othervm TestConfigurationLock 49 * @author danielfuchs 50 */ 51 // This test is a best effort to try & detect issues. The test itself will run 52 // for 8secs. This might be unsufficient to detect issues. 53 // To get a greater confidence it is recommended to run this test in a loop: 54 // e.g. use something like: 55 // $ while jtreg -jdk:$JDK -verbose:all \ 56 // test/java/util/logging/TestConfigurationLock.java ; \ 57 // do echo Running test again ; done 58 // and let it run for a few hours... 59 // 60 public class TestConfigurationLock { 61 62 static volatile Exception thrown = null; 63 static volatile boolean goOn = true; 64 static volatile boolean deadlock = false; 65 66 static final double CONFSYNCTHRESHOLD = 0.3; 67 static final double LOGSYNCTHRESHOLD = 0.3; 68 static final int RESETERS = 0; 69 static final int READERS = 3; 70 static final int LOGGERS = 4; 71 static final long TIME = 8 * 1000; // 8 sec. 72 static final long STEP = 1 * 1000; // message every 1 sec. 73 static final int LCOUNT = 50; // 50 loggers created in a row... 74 static final AtomicLong nextLogger = new AtomicLong(0); 75 static final AtomicLong resetCount = new AtomicLong(0); 76 static final AtomicLong readCount = new AtomicLong(0); 77 static final AtomicLong checkCount = new AtomicLong(0); 78 79 static final String BLAH = "blah"; 80 81 static Object fakeConfExternalLock() { 82 return LogManager.getLogManager(); 83 } 84 85 static Object fakeLogExternalLock() { 86 return LogManager.getLogManager(); 87 } 88 89 90 /** 91 * This test will run both with and without a security manager. 92 * 93 * The test starts a number of threads that will call 94 * LogManager.reset() concurrently (ResetConf), and a number of threads 95 * that will call readConfiguration() (ReadConf), and then starts a 96 * number of threads that will create new loggers concurrently 97 * (AddLogger), and finally two additional threads: 98 * - one (Stopper) that will stop the test after 4secs (TIME ms), 99 * - and one DeadlockDetector that will attempt to detect deadlocks. 100 * If after 4secs no deadlock was detected and no exception was thrown 101 * then the test is considered a success and passes. 102 * 103 * This procedure is done twice: once without a security manager and once 104 * again with a security manager - which means the test takes ~8secs to 105 * run. 106 * 107 * Note that 8sec may not be enough to detect issues if there are some. 108 * This is a best effort test. 109 * 110 * @param args the command line arguments 111 * @throws java.lang.Exception if the test fails 112 */ 113 public static void main(String[] args) throws Exception { 114 115 File conf = new File(System.getProperty("test.src", "./src"), 116 TestConfigurationLock.class.getSimpleName() + ".properties"); 117 if (!conf.canRead()) { 118 throw new IOException("Can't read config file: " + conf.getAbsolutePath()); 119 } 120 System.setProperty("java.util.logging.config.file", conf.getAbsolutePath()); 121 // test without security 122 System.out.println("No security"); 123 test(); 124 125 // test with security 126 System.out.println("\nWith security"); 127 Policy.setPolicy(new Policy() { 128 @Override 129 public boolean implies(ProtectionDomain domain, Permission permission) { 130 if (super.implies(domain, permission)) return true; 131 // System.out.println("Granting " + permission); 132 return true; // all permissions 133 } 134 }); 135 System.setSecurityManager(new SecurityManager()); 136 test(); 137 } 138 139 140 /** 141 * Starts all threads, wait 4secs, then stops all threads. 142 * @throws Exception if a deadlock was detected or an error occurred. 143 */ 144 public static void test() throws Exception { 145 goOn = true; 146 thrown = null; 147 long sNextLogger = nextLogger.get(); 148 long sUpdateCount = resetCount.get(); 149 long sReadCount = readCount.get(); 150 long sCheckCount = checkCount.get(); 151 List<Thread> threads = new ArrayList<>(); 152 for (int i = 0; i<RESETERS; i++) { 153 threads.add(new ResetConf()); 154 } 155 for (int i = 0; i<READERS; i++) { 156 threads.add(new ReadConf()); 157 } 158 for (int i = 0; i<LOGGERS; i++) { 159 threads.add(new AddLogger()); 160 } 161 threads.add(0, new Stopper(TIME)); 162 threads.stream().forEach(Thread::start); 163 164 Thread deadLockDetector = new DeadlockDetector(); 165 deadLockDetector.start(); 166 deadLockDetector.join(); 167 168 if (!deadlock) { 169 threads.stream().forEach(TestConfigurationLock::join); 170 } else { 171 System.err.println("Deadlock found: exiting forcibly."); 172 Runtime.getRuntime().halt(-1); 173 } 174 175 if (thrown != null) { 176 throw thrown; 177 } 178 System.out.println("Passed: " + (nextLogger.get() - sNextLogger) 179 + " loggers created by " + LOGGERS + " Thread(s),"); 180 System.out.println("\t LogManager.reset() called " 181 + (resetCount.get() - sUpdateCount) + " times by " + RESETERS 182 + " Thread(s)."); 183 System.out.println("\t LogManager.readConfiguration() called " 184 + (readCount.get() - sReadCount) + " times by " + READERS 185 + " Thread(s)."); 186 System.out.println("\t ThreadMXBean.findDeadlockedThreads called " 187 + (checkCount.get() -sCheckCount) + " times by 1 Thread."); 188 189 } 190 191 static void join(Thread t) { 192 try { 193 t.join(); 194 } catch (Exception x) { 195 fail(x); 196 } 197 } 198 199 final static class ResetConf extends Thread { 200 201 public ResetConf() { 202 setDaemon(true); 203 } 204 205 @Override 206 public void run() { 207 while (goOn) { 208 try { 209 if (Math.random() > CONFSYNCTHRESHOLD) { 210 // calling reset while holding a lock can increase 211 // deadlock probability... 212 synchronized(fakeConfExternalLock()) { 213 LogManager.getLogManager().reset(); 214 } 215 } else { 216 LogManager.getLogManager().reset(); 217 } 218 Logger blah = Logger.getLogger(BLAH); 219 blah.setLevel(Level.FINEST); 220 blah.fine(BLAH); 221 resetCount.incrementAndGet(); 222 pause(1); 223 } catch (Exception x) { 224 fail(x); 225 } 226 } 227 } 228 } 229 230 final static class ReadConf extends Thread { 231 232 public ReadConf() { 233 setDaemon(true); 234 } 235 236 @Override 237 public void run() { 238 while (goOn) { 239 try { 240 if (Math.random() > CONFSYNCTHRESHOLD) { 241 // calling readConfiguration while holding a lock can 242 // increase deadlock probability... 243 synchronized(fakeConfExternalLock()) { 244 LogManager.getLogManager().readConfiguration(); 245 } 246 } else { 247 LogManager.getLogManager().readConfiguration(); 248 } 249 Logger blah = Logger.getLogger(BLAH); 250 blah.setLevel(Level.FINEST); 251 blah.fine(BLAH); 252 readCount.incrementAndGet(); 253 pause(1); 254 } catch (Exception x) { 255 fail(x); 256 } 257 } 258 } 259 } 260 261 final static class AddLogger extends Thread { 262 263 public AddLogger() { 264 setDaemon(true); 265 } 266 267 @Override 268 public void run() { 269 try { 270 while (goOn) { 271 Logger l; 272 Logger foo = Logger.getLogger("foo"); 273 Logger bar = Logger.getLogger("foo.bar"); 274 for (int i=0; i < LCOUNT ; i++) { 275 LogManager manager = LogManager.getLogManager(); 276 if (Math.random() > LOGSYNCTHRESHOLD) { 277 synchronized(fakeLogExternalLock()) { 278 l = Logger.getLogger("foo.bar.l"+nextLogger.incrementAndGet()); 279 } 280 } else { 281 l = Logger.getLogger("foo.bar.l"+nextLogger.incrementAndGet()); 282 } 283 l.setLevel(Level.FINEST); 284 l.fine("I'm fine"); 285 if (!goOn) break; 286 pause(1); 287 } 288 } 289 } catch (InterruptedException | RuntimeException x ) { 290 fail(x); 291 } 292 } 293 } 294 295 final static class DeadlockDetector extends Thread { 296 297 @Override 298 public void run() { 299 boolean deadlock = false; 300 while(goOn) { 301 try { 302 long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads(); 303 checkCount.incrementAndGet(); 304 ids = ids == null ? new long[0] : ids; 305 if (ids.length == 1) { 306 throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]); 307 } else if (ids.length > 0) { 308 deadlock = true; 309 ThreadInfo[] infos = ManagementFactory.getThreadMXBean() 310 .getThreadInfo(ids, true, true); 311 System.err.println("Found "+ids.length+" deadlocked threads: "); 312 for (ThreadInfo inf : infos) { 313 System.err.println(asString(inf)); 314 } 315 throw new RuntimeException("Found "+ids.length+" deadlocked threads"); 316 } 317 pause(100); 318 } catch(InterruptedException | RuntimeException x) { 319 if (deadlock) deadlock(x); 320 else fail(x); 321 } 322 } 323 } 324 325 } 326 327 static final class Stopper extends Thread { 328 long start; 329 long time; 330 331 Stopper(long time) { 332 start = System.currentTimeMillis(); 333 this.time = time; 334 setDaemon(true); 335 } 336 337 @Override 338 public void run() { 339 try { 340 long rest, previous; 341 int msgCount = 0; 342 previous = time; 343 Logger logger = Logger.getLogger("remaining"); 344 while (goOn && (rest = start - System.currentTimeMillis() + time) > 0) { 345 if (previous == time || previous - rest >= STEP) { 346 logger.log(Level.INFO, "{0}ms remaining...", String.valueOf(rest)); 347 msgCount++; 348 previous = rest == time ? rest -1 : rest; 349 System.gc(); 350 } 351 if (goOn == false) break; 352 pause(Math.min(rest, 100)); 353 } 354 System.err.println(this + ": " + msgCount + " messages."); 355 System.err.flush(); 356 System.out.println(System.currentTimeMillis() - start 357 + " ms elapsed ("+time+ " requested)"); 358 goOn = false; 359 } catch(InterruptedException | RuntimeException x) { 360 fail(x); 361 } 362 } 363 364 } 365 366 // ThreadInfo.toString() only prints 8 frames... 367 static String asString(ThreadInfo inf) { 368 StringBuilder sb = new StringBuilder(); 369 sb.append("\"").append(inf.getThreadName()).append("\"") 370 .append(inf.isDaemon() ? " daemon" : "") 371 .append(" prio=").append(inf.getPriority()) 372 .append(" Id=").append(inf.getThreadId()) 373 .append(" ").append(inf.getThreadState()); 374 if (inf.getLockName() != null) { 375 sb.append(" on ").append(inf.getLockName()); 376 } 377 if (inf.getLockOwnerName() != null) { 378 sb.append(" owned by \"").append(inf.getLockOwnerName()) 379 .append("\" Id=").append(inf.getLockOwnerId()); 380 } 381 if (inf.isSuspended()) { 382 sb.append(" (suspended)"); 383 } 384 if (inf.isInNative()) { 385 sb.append(" (in native)"); 386 } 387 sb.append('\n'); 388 int i = 0; 389 StackTraceElement[] stackTrace = inf.getStackTrace(); 390 for (; i < stackTrace.length; i++) { 391 StackTraceElement ste = stackTrace[i]; 392 sb.append("\tat ").append(ste.toString()); 393 sb.append('\n'); 394 if (i == 0 && inf.getLockInfo() != null) { 395 Thread.State ts = inf.getThreadState(); 396 switch (ts) { 397 case BLOCKED: 398 sb.append("\t- blocked on ").append(inf.getLockInfo()); 399 sb.append('\n'); 400 break; 401 case WAITING: 402 sb.append("\t- waiting on ").append(inf.getLockInfo()); 403 sb.append('\n'); 404 break; 405 case TIMED_WAITING: 406 sb.append("\t- waiting on ").append(inf.getLockInfo()); 407 sb.append('\n'); 408 break; 409 default: 410 } 411 } 412 413 for (MonitorInfo mi : inf.getLockedMonitors()) { 414 if (mi.getLockedStackDepth() == i) { 415 sb.append("\t- locked ").append(mi); 416 sb.append('\n'); 417 } 418 } 419 } 420 if (i < stackTrace.length) { 421 sb.append("\t..."); 422 sb.append('\n'); 423 } 424 425 LockInfo[] locks = inf.getLockedSynchronizers(); 426 if (locks.length > 0) { 427 sb.append("\n\tNumber of locked synchronizers = ").append(locks.length); 428 sb.append('\n'); 429 for (LockInfo li : locks) { 430 sb.append("\t- ").append(li); 431 sb.append('\n'); 432 } 433 } 434 sb.append('\n'); 435 return sb.toString(); 436 } 437 438 static void pause(long millis) throws InterruptedException { 439 Thread.sleep(millis); 440 } 441 442 static void fail(Exception x) { 443 x.printStackTrace(System.err); 444 if (thrown == null) { 445 thrown = x; 446 } 447 goOn = false; 448 } 449 450 static void deadlock(Exception x) { 451 deadlock = true; 452 System.out.flush(); 453 fail(x); 454 System.err.flush(); 455 } 456 }