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