blob: 48933c9adcad62c28303030b1381f03f38f7aefc [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
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;
dfuchs43ee5a22013-12-19 14:53:10 +0100193 volatile int count = 0;
dfuchse3587d92013-12-04 01:58:37 +0100194 @Override
195 public synchronized void publish(LogRecord record) {
dfuchs43ee5a22013-12-19 14:53:10 +0100196 count++;
dfuchse3587d92013-12-04 01:58:37 +0100197 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;
dfuchs43ee5a22013-12-19 14:53:10 +0100232 if (handler.getLevel() != Level.FINEST) {
233 throw new RuntimeException("Handler level is not finest: "
234 + handler.getLevel());
235 }
236 final int countBefore = handler.count;
dfuchse3587d92013-12-04 01:58:37 +0100237 ll.setLevel(Level.FINEST);
238 ll.addHandler(handler);
239 ll.fine("dummy");
240 ll.removeHandler(handler);
dfuchs43ee5a22013-12-19 14:53:10 +0100241 final int countAfter = handler.count;
242 if (countBefore == countAfter) {
243 throw new RuntimeException("Handler not called for "
244 + ll.getName() + "("+ countAfter +")");
245 }
dfuchse3587d92013-12-04 01:58:37 +0100246 hrb = handler.rb;
247 hrbName = handler.rbName;
248 if (name != null) {
dfuchs43ee5a22013-12-19 14:53:10 +0100249 // 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
dfuchse3587d92013-12-04 01:58:37 +0100256 if (!name.equals(hrbName)) {
257 throw new RuntimeException("Unexpected bundle name: "
dfuchs43ee5a22013-12-19 14:53:10 +0100258 +hrbName);
dfuchse3587d92013-12-04 01:58:37 +0100259 }
dfuchs43ee5a22013-12-19 14:53:10 +0100260 // here we know that hrbName is not null so hrb
261 // should not be null either.
dfuchse3587d92013-12-04 01:58:37 +0100262 if (!name.equals(hrb.getBaseBundleName())) {
263 throw new RuntimeException("Unexpected bundle name: "
264 +hrb.getBaseBundleName());
265 }
266 }
267
dfuchs43ee5a22013-12-19 14:53:10 +0100268 // Make sure to refer to 'l' explicitly 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
dfuchse3587d92013-12-04 01:58:37 +0100277 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}