TestLoggerBundleSync.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.Permission; 27import java.security.Policy; 28import java.security.ProtectionDomain; 29import java.util.ArrayList; 30import java.util.List; 31import java.util.ListResourceBundle; 32import java.util.Objects; 33import java.util.ResourceBundle; 34import java.util.concurrent.Callable; 35import java.util.concurrent.ExecutorService; 36import java.util.concurrent.Executors; 37import java.util.concurrent.FutureTask; 38import java.util.concurrent.atomic.AtomicLong; 39import java.util.logging.Handler; 40import java.util.logging.Level; 41import java.util.logging.LogRecord; 42import java.util.logging.Logger; 43 44/** 45 * @test 46 * @bug 8029281 8028763 47 * @summary Attempts to detect synchronization issues with getResourceBundle() 48 * and getResourceBundleName(). It might also detect issues in the way 49 * that the logger tree is cleaned up after a logger has been garbage 50 * collected. This test helped find the root cause of 8029092, so if 51 * this test fails one might also expect failures in 52 * java/util/logging/Logger/logrb/TestLogrbResourceBundle.java and 53 * java/util/logging/Logger/setResourceBundle/TestSetResourceBundle.java. 54 * Note that this is a best effort test. Running it in a loop to 55 * reproduce intermittent issues can be a good idea. 56 * @run main/othervm TestLoggerBundleSync 57 * @author danielfuchs 58 */ 59public class TestLoggerBundleSync { 60 61 static final boolean VERBOSE = false; 62 static volatile Exception thrown = null; 63 static volatile boolean goOn = true; 64 65 static final int READERS = 3; 66 static final long TIME = 4 * 1000; // 4 sec. 67 static final long STEP = 1 * 1000; // message every 1 sec. 68 static final int LCOUNT = 50; // change bundle 50 times... 69 static final AtomicLong ignoreLogCount = new AtomicLong(0); 70 static final AtomicLong setRBcount = new AtomicLong(0); 71 static final AtomicLong setRBNameCount = new AtomicLong(0); 72 static final AtomicLong getRBcount = new AtomicLong(0); 73 static final AtomicLong checkCount = new AtomicLong(0); 74 static final AtomicLong nextLong = new AtomicLong(0); 75 76 public static class MyBundle extends ListResourceBundle { 77 @Override 78 protected Object[][] getContents() { 79 return new Object[][] { 80 {"dummy", "foo"} 81 }; 82 } 83 } 84 85 public static final class MyBundle1 extends MyBundle { }; 86 public static final class MyBundle2 extends MyBundle { }; 87 public static final class MyBundle3 extends MyBundle { }; 88 89 90 public static final class LoggerRB { 91 public final String resourceBundleName; 92 public final ResourceBundle userBundle; 93 public LoggerRB(String name, ResourceBundle bundle) { 94 resourceBundleName = name; 95 userBundle = bundle; 96 } 97 } 98 99 static final List<Class<? extends ResourceBundle>> classes = new ArrayList<>(); 100 static { 101 classes.add(MyBundle1.class); 102 classes.add(MyBundle2.class); 103 classes.add(MyBundle3.class); 104 } 105 106 107 /** 108 * This test will run both with and without a security manager. 109 * 110 * The test starts a number of threads that will attempt to concurrently 111 * set resource bundles on Logger, and verifies the consistency of the 112 * obtained results. 113 * 114 * This is a best effort test. 115 * 116 * @param args the command line arguments 117 */ 118 public static void main(String[] args) throws Exception { 119 120 try { 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 } finally { 138 SetRB.executor.shutdownNow(); 139 SetRBName.executor.shutdownNow(); 140 } 141 } 142 143 /** 144 * Starts all threads, wait 15secs, then stops all threads. 145 * @throws Exception if a deadlock was detected or an error occurred. 146 */ 147 public static void test() throws Exception { 148 goOn = true; 149 thrown = null; 150 long sGetRBCount = getRBcount.get(); 151 long sSetRBCount = setRBcount.get(); 152 long sSetRBNameCount = setRBNameCount.get(); 153 long sCheckCount = checkCount.get(); 154 long sNextLong = nextLong.get(); 155 long sIgnoreLogCount = ignoreLogCount.get(); 156 List<Thread> threads = new ArrayList<>(); 157 for (Class<? extends ResourceBundle> type : classes) { 158 threads.add(new SetRB(type)); 159 threads.add(new SetRBName(type)); 160 } 161 for (int i =0 ; i < READERS ; i++) { 162 threads.add(new GetRB()); 163 } 164 threads.add(new DeadlockDetector()); 165 threads.add(0, new Stopper(TIME)); 166 for (Thread t : threads) { 167 t.start(); 168 } 169 for (Thread t : threads) { 170 try { 171 t.join(); 172 } catch (Exception x) { 173 fail(x); 174 } 175 } 176 if (thrown != null) { 177 throw thrown; 178 } 179 System.out.println("Passed: " + (nextLong.longValue() - sNextLong) 180 + " unique loggers created"); 181 System.out.println("\t " +(getRBcount.get() - sGetRBCount) 182 + " loggers tested by " + READERS + " Thread(s),"); 183 System.out.println("\t " + (setRBcount.get() - sSetRBCount) 184 + " resource bundles set by " + classes.size() + " Thread(s),"); 185 System.out.println("\t " + (setRBNameCount.get() - sSetRBNameCount) 186 + " resource bundle names set by " + classes.size() + " Thread(s),"); 187 System.out.println("\t " + (ignoreLogCount.get() - sIgnoreLogCount) 188 + " log messages emitted by other GetRB threads were ignored" 189 + " to ensure MT test consistency,"); 190 System.out.println("\t ThreadMXBean.findDeadlockedThreads called " 191 + (checkCount.get() -sCheckCount) + " times by 1 Thread."); 192 193 } 194 195 static final class GetRB extends Thread { 196 final class MyHandler extends Handler { 197 volatile ResourceBundle rb; 198 volatile String rbName; 199 volatile int count = 0; 200 @Override 201 public synchronized void publish(LogRecord record) { 202 Object[] params = record.getParameters(); 203 // Each GetRB thread has its own handler, but since they 204 // log into the same logger, each handler may receive 205 // messages emitted by other threads. 206 // This means that GetRB#2.handler may receive a message 207 // emitted by GetRB#1 at a time where the resource bundle 208 // was still null. 209 // To avoid falling into this trap, the GetRB thread passes 210 // 'this' as argument to the messages it logs - which does 211 // allow us here to ignore messages that where not emitted 212 // by our own GetRB.this thread... 213 if (params.length == 1) { 214 if (params[0] == GetRB.this) { 215 // The message was emitted by our thread. 216 count++; 217 rb = record.getResourceBundle(); 218 rbName = record.getResourceBundleName(); 219 } else { 220 // The message was emitted by another thread: just 221 // ignore it, as it may have been emitted at a time 222 // where the resource bundle was still null, and 223 // processing it may overwrite the 'rb' and 'rbName' 224 // recorded from the message emitted by our own thread. 225 if (VERBOSE) { 226 System.out.println("Ignoring message logged by " + params[0]); 227 } 228 ignoreLogCount.incrementAndGet(); 229 } 230 } else { 231 ignoreLogCount.incrementAndGet(); 232 System.err.println("Unexpected message received"); 233 } 234 } 235 236 void reset() { 237 rbName = null; 238 rb = null; 239 } 240 241 @Override 242 public void flush() { 243 } 244 245 @Override 246 public void close() throws SecurityException { 247 } 248 }; 249 final MyHandler handler = new MyHandler(); 250 251 @Override 252 public void run() { 253 try { 254 handler.setLevel(Level.FINEST); 255 while (goOn) { 256 Logger l; 257 Logger foo = Logger.getLogger("foo"); 258 Logger bar = Logger.getLogger("foo.bar"); 259 for (long i=0; i < nextLong.longValue() + 100 ; i++) { 260 if (!goOn) break; 261 l = Logger.getLogger("foo.bar.l"+i); 262 final ResourceBundle b = l.getResourceBundle(); 263 final String name = l.getResourceBundleName(); 264 if (b != null) { 265 if (!name.equals(b.getBaseBundleName())) { 266 throw new RuntimeException("Unexpected bundle name: " 267 +b.getBaseBundleName()); 268 } 269 } 270 Logger ll = Logger.getLogger(l.getName()+".bie.bye"); 271 ResourceBundle hrb; 272 String hrbName; 273 if (handler.getLevel() != Level.FINEST) { 274 throw new RuntimeException("Handler level is not finest: " 275 + handler.getLevel()); 276 } 277 final int countBefore = handler.count; 278 handler.reset(); 279 ll.setLevel(Level.FINEST); 280 ll.addHandler(handler); 281 ll.log(Level.FINE, "dummy {0}", this); 282 ll.removeHandler(handler); 283 final int countAfter = handler.count; 284 if (countBefore == countAfter) { 285 throw new RuntimeException("Handler not called for " 286 + ll.getName() + "("+ countAfter +")"); 287 } 288 hrb = handler.rb; 289 hrbName = handler.rbName; 290 if (name != null) { 291 // if name is not null, then it implies that it 292 // won't change, since setResourceBundle() cannot 293 // replace a non null name. 294 // Since we never set the resource bundle on 'll', 295 // then ll must inherit its resource bundle [name] 296 // from l - and therefor we should find it in 297 // handler.rb/handler.rbName 298 if (!name.equals(hrbName)) { 299 throw new RuntimeException("Unexpected bundle name: " 300 +hrbName); 301 } 302 // here we know that hrbName is not null so hrb 303 // should not be null either. 304 if (!name.equals(hrb.getBaseBundleName())) { 305 throw new RuntimeException("Unexpected bundle name: " 306 +hrb.getBaseBundleName()); 307 } 308 } 309 310 // Make sure to refer to 'l' explicitly in order to 311 // prevent eager garbage collecting before the end of 312 // the test (JDK-8030192) 313 if (!ll.getName().startsWith(l.getName())) { 314 throw new RuntimeException("Logger " + ll.getName() 315 + "does not start with expected prefix " 316 + l.getName()); 317 } 318 319 getRBcount.incrementAndGet(); 320 if (!goOn) break; 321 Thread.sleep(1); 322 } 323 } 324 } catch (Exception x) { 325 fail(x); 326 } 327 } 328 } 329 330 static final class SetRB extends Thread { 331 final Class<? extends ResourceBundle> type; 332 static final ExecutorService executor = Executors.newSingleThreadExecutor(); 333 static final class CheckRBTask implements Callable<Exception> { 334 final Logger logger; 335 volatile String rbName; 336 volatile ResourceBundle rb; 337 338 public CheckRBTask(Logger logger) { 339 this.logger = logger; 340 } 341 342 @Override 343 public Exception call() throws Exception { 344 try { 345 final String name = logger.getResourceBundleName(); 346 if (!Objects.equals(name, rbName)) { 347 throw new RuntimeException("Unexpected rbname for " 348 + logger.getName() + ": " + name); 349 } 350 final ResourceBundle b = logger.getResourceBundle(); 351 if (b != rb) { 352 throw new RuntimeException("Unexpected rb for " 353 + logger.getName() + ": " + b); 354 } 355 } catch(Exception x) { 356 return x; 357 } 358 return null; 359 } 360 361 public void check() throws Exception { 362 final FutureTask<Exception> futureTask = new FutureTask<>(this); 363 executor.submit(futureTask); 364 Exception x = futureTask.get(); 365 if ( x != null) { 366 throw new RuntimeException("Check failed: "+x,x); 367 } 368 } 369 } 370 SetRB(Class<? extends ResourceBundle> type) { 371 super("SetRB["+type.getSimpleName()+"]"); 372 this.type = type; 373 } 374 @Override 375 public void run() { 376 try { 377 while (goOn) { 378 Logger l; 379 Logger foo = Logger.getLogger("foo"); 380 Logger bar = Logger.getLogger("foo.bar"); 381 l = Logger.getLogger("foo.bar.l"+nextLong.incrementAndGet()); 382 final CheckRBTask checkTask = new CheckRBTask(l); 383 checkTask.check(); 384 385 for (int i=0; i < LCOUNT ; i++) { 386 if (!goOn) break; 387 388 ResourceBundle b = ResourceBundle.getBundle(type.getName()); 389 try { 390 l.setResourceBundle(b); 391 checkTask.rb = b; 392 checkTask.rbName = type.getName(); 393 checkTask.check(); 394 if (!goOn) break; 395 396 String name = l.getResourceBundleName(); 397 ResourceBundle bb = l.getResourceBundle(); 398 if (!type.getName().equals(name)) { 399 throw new RuntimeException(this.getName() 400 + ": Unexpected name: "+name); 401 } 402 if (!b.getBaseBundleName().equals(name)) { 403 throw new RuntimeException(this.getName() 404 + ": Unexpected base name: " + 405 b.getBaseBundleName()); 406 } 407 if (b != bb) { 408 throw new RuntimeException(this.getName() 409 + ": Unexpected bundle: "+bb); 410 } 411 setRBcount.incrementAndGet(); 412 } catch (IllegalArgumentException x) { 413 final String name = l.getResourceBundleName(); 414 if (!name.startsWith(MyBundle.class.getName())) { 415 throw new RuntimeException(this.getName() 416 + ": Unexpected name: "+name, x); 417 } else if (type.getName().equals(name)) { 418 throw new RuntimeException(this.getName() 419 + ": Unexpected exception for "+name, x); 420 } 421 throw x; 422 } 423 l.fine("I'm fine"); 424 if (!goOn) break; 425 Thread.sleep(1); 426 } 427 } 428 } catch (Exception x) { 429 fail(x); 430 } 431 } 432 } 433 434 static final class SetRBName extends Thread { 435 int nexti = 0; 436 final Class<? extends ResourceBundle> type; 437 static final ExecutorService executor = Executors.newSingleThreadExecutor(); 438 static final class CheckRBNameTask implements Callable<Exception> { 439 final Logger logger; 440 volatile String rbName; 441 442 public CheckRBNameTask(Logger logger) { 443 this.logger = logger; 444 } 445 446 @Override 447 public Exception call() throws Exception { 448 try { 449 final String name = logger.getResourceBundleName(); 450 if (!Objects.equals(name, rbName)) { 451 throw new RuntimeException("Unexpected rbname for " 452 + logger.getName() + ": " + name); 453 } 454 final ResourceBundle b = logger.getResourceBundle(); 455 if (!Objects.equals(b == null ? null : b.getBaseBundleName(), rbName)) { 456 throw new RuntimeException("Unexpected base name for " 457 + logger.getName() + ": " + b.getBaseBundleName()); 458 } 459 } catch(Exception x) { 460 return x; 461 } 462 return null; 463 } 464 465 public void check() throws Exception { 466 final FutureTask<Exception> futureTask = new FutureTask<>(this); 467 executor.submit(futureTask); 468 Exception x = futureTask.get(); 469 if ( x != null) { 470 throw new RuntimeException("Check failed: "+x,x); 471 } 472 } 473 474 } 475 SetRBName(Class<? extends ResourceBundle> type) { 476 super("SetRB["+type.getSimpleName()+"]"); 477 this.type = type; 478 } 479 @Override 480 public void run() { 481 try { 482 while (goOn) { 483 Logger foo = Logger.getLogger("foo"); 484 Logger bar = Logger.getLogger("foo.bar"); 485 Logger l = Logger.getLogger("foo.bar.l"+nextLong.incrementAndGet()); 486 final CheckRBNameTask checkTask = new CheckRBNameTask(l); 487 checkTask.check(); 488 489 for (int i=0; i < LCOUNT ; i++) { 490 if (!goOn) break; 491 492 try { 493 Logger l2 = Logger.getLogger(l.getName(), type.getName()); 494 if (l2 != l) { 495 System.err.println("**** ERROR WITH "+l.getName()); 496 throw new RuntimeException("l2 != l [" 497 + l2 + "(" + l2.getName() + ") != " 498 + l + "(" + l.getName() + ")]"); 499 } 500 checkTask.rbName = type.getName(); 501 checkTask.check(); 502 if (!goOn) break; 503 504 String name = l.getResourceBundleName(); 505 ResourceBundle bb = l.getResourceBundle(); 506 if (!type.getName().equals(name)) { 507 throw new RuntimeException(this.getName() 508 + ": Unexpected name: "+name); 509 } 510 if (!bb.getBaseBundleName().equals(name)) { 511 throw new RuntimeException(this.getName() 512 + ": Unexpected base name: " 513 + bb.getBaseBundleName()); 514 } 515 setRBNameCount.incrementAndGet(); 516 } catch (IllegalArgumentException x) { 517 final String name = l.getResourceBundleName(); 518 if (!name.startsWith(MyBundle.class.getName())) { 519 throw new RuntimeException(this.getName() 520 + ": Unexpected name: "+name, x); 521 } else if (type.getName().equals(name)) { 522 throw new RuntimeException(this.getName() 523 + ": Unexpected exception for "+name, x); 524 } 525 throw x; 526 } 527 l.fine("I'm fine"); 528 if (!goOn) break; 529 Thread.sleep(1); 530 } 531 } 532 } catch (Exception x) { 533 fail(x); 534 } 535 } 536 } 537 538 static final class DeadlockDetector extends Thread { 539 540 @Override 541 public void run() { 542 while(goOn) { 543 try { 544 long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads(); 545 checkCount.incrementAndGet(); 546 ids = ids == null ? new long[0] : ids; 547 if (ids.length == 1) { 548 throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]); 549 } else if (ids.length > 0) { 550 ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(ids); 551 System.err.println("Found "+ids.length+" deadlocked threads: "); 552 for (ThreadInfo inf : infos) { 553 System.err.println(inf.toString()); 554 } 555 throw new RuntimeException("Found "+ids.length+" deadlocked threads"); 556 } 557 Thread.sleep(100); 558 } catch(InterruptedException | RuntimeException x) { 559 fail(x); 560 } 561 } 562 } 563 564 } 565 566 static final class Stopper extends Thread { 567 long start; 568 long time; 569 570 Stopper(long time) { 571 start = System.currentTimeMillis(); 572 this.time = time; 573 } 574 575 @Override 576 public void run() { 577 try { 578 long rest, previous; 579 previous = time; 580 while (goOn && (rest = start - System.currentTimeMillis() + time) > 0) { 581 if (previous == time || previous - rest >= STEP) { 582 Logger.getLogger("remaining").info(String.valueOf(rest)+"ms remaining..."); 583 previous = rest == time ? rest -1 : rest; 584 System.gc(); 585 } 586 if (goOn == false) break; 587 Thread.sleep(Math.min(rest, 100)); 588 } 589 System.out.println(System.currentTimeMillis() - start 590 + " ms elapsed ("+time+ " requested)"); 591 goOn = false; 592 } catch(InterruptedException | RuntimeException x) { 593 fail(x); 594 } 595 } 596 597 } 598 599 static void fail(Exception x) { 600 x.printStackTrace(); 601 if (thrown == null) { 602 thrown = x; 603 } 604 goOn = false; 605 } 606} 607