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