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