blob: 7f5e51470eb140d73288200a8c9916dbfbea204b [file] [log] [blame]
dfuchse3587d92013-12-04 01:58:37 +01001/*
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
dfuchse5bd8f42014-08-14 15:18:11 +020061 static final boolean VERBOSE = false;
dfuchse3587d92013-12-04 01:58:37 +010062 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...
dfuchse5bd8f42014-08-14 15:18:11 +020069 static final AtomicLong ignoreLogCount = new AtomicLong(0);
dfuchse3587d92013-12-04 01:58:37 +010070 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();
dfuchse5bd8f42014-08-14 15:18:11 +0200155 long sIgnoreLogCount = ignoreLogCount.get();
dfuchse3587d92013-12-04 01:58:37 +0100156 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),");
dfuchse5bd8f42014-08-14 15:18:11 +0200187 System.out.println("\t " + (ignoreLogCount.get() - sIgnoreLogCount)
188 + " log messages emitted by other GetRB threads were ignored"
189 + " to ensure MT test consistency,");
dfuchse3587d92013-12-04 01:58:37 +0100190 System.out.println("\t ThreadMXBean.findDeadlockedThreads called "
191 + (checkCount.get() -sCheckCount) + " times by 1 Thread.");
192
193 }
194
195 final static class GetRB extends Thread {
dfuchse5bd8f42014-08-14 15:18:11 +0200196 final class MyHandler extends Handler {
dfuchse3587d92013-12-04 01:58:37 +0100197 volatile ResourceBundle rb;
198 volatile String rbName;
dfuchs43ee5a22013-12-19 14:53:10 +0100199 volatile int count = 0;
dfuchse3587d92013-12-04 01:58:37 +0100200 @Override
201 public synchronized void publish(LogRecord record) {
dfuchse5bd8f42014-08-14 15:18:11 +0200202 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;
dfuchse3587d92013-12-04 01:58:37 +0100239 }
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();
dfuchse5bd8f42014-08-14 15:18:11 +0200250
dfuchse3587d92013-12-04 01:58:37 +0100251 @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;
dfuchs43ee5a22013-12-19 14:53:10 +0100273 if (handler.getLevel() != Level.FINEST) {
274 throw new RuntimeException("Handler level is not finest: "
275 + handler.getLevel());
276 }
277 final int countBefore = handler.count;
dfuchse5bd8f42014-08-14 15:18:11 +0200278 handler.reset();
dfuchse3587d92013-12-04 01:58:37 +0100279 ll.setLevel(Level.FINEST);
280 ll.addHandler(handler);
dfuchse5bd8f42014-08-14 15:18:11 +0200281 ll.log(Level.FINE, "dummy {0}", this);
dfuchse3587d92013-12-04 01:58:37 +0100282 ll.removeHandler(handler);
dfuchs43ee5a22013-12-19 14:53:10 +0100283 final int countAfter = handler.count;
284 if (countBefore == countAfter) {
285 throw new RuntimeException("Handler not called for "
286 + ll.getName() + "("+ countAfter +")");
287 }
dfuchse3587d92013-12-04 01:58:37 +0100288 hrb = handler.rb;
289 hrbName = handler.rbName;
290 if (name != null) {
dfuchs43ee5a22013-12-19 14:53:10 +0100291 // 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
dfuchse3587d92013-12-04 01:58:37 +0100298 if (!name.equals(hrbName)) {
299 throw new RuntimeException("Unexpected bundle name: "
dfuchs43ee5a22013-12-19 14:53:10 +0100300 +hrbName);
dfuchse3587d92013-12-04 01:58:37 +0100301 }
dfuchs43ee5a22013-12-19 14:53:10 +0100302 // here we know that hrbName is not null so hrb
303 // should not be null either.
dfuchse3587d92013-12-04 01:58:37 +0100304 if (!name.equals(hrb.getBaseBundleName())) {
305 throw new RuntimeException("Unexpected bundle name: "
306 +hrb.getBaseBundleName());
307 }
308 }
309
dfuchs43ee5a22013-12-19 14:53:10 +0100310 // 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
dfuchse3587d92013-12-04 01:58:37 +0100319 getRBcount.incrementAndGet();
320 if (!goOn) break;
321 Thread.sleep(1);
322 }
323 }
324 } catch (Exception x) {
325 fail(x);
326 }
327 }
328 }
329
330 final static class SetRB extends Thread {
331 final Class<? extends ResourceBundle> type;
332 final static ExecutorService executor = Executors.newSingleThreadExecutor();
333 final static 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 final static class SetRBName extends Thread {
435 int nexti = 0;
436 final Class<? extends ResourceBundle> type;
437 final static ExecutorService executor = Executors.newSingleThreadExecutor();
438 final static 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 final static 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}