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 @Override
194 public synchronized void publish(LogRecord record) {
195 rb = record.getResourceBundle();
196 rbName = record.getResourceBundleName();
197 }
198
199 @Override
200 public void flush() {
201 }
202
203 @Override
204 public void close() throws SecurityException {
205 }
206 };
207 final MyHandler handler = new MyHandler();
208 @Override
209 public void run() {
210 try {
211 handler.setLevel(Level.FINEST);
212 while (goOn) {
213 Logger l;
214 Logger foo = Logger.getLogger("foo");
215 Logger bar = Logger.getLogger("foo.bar");
216 for (long i=0; i < nextLong.longValue() + 100 ; i++) {
217 if (!goOn) break;
218 l = Logger.getLogger("foo.bar.l"+i);
219 final ResourceBundle b = l.getResourceBundle();
220 final String name = l.getResourceBundleName();
221 if (b != null) {
222 if (!name.equals(b.getBaseBundleName())) {
223 throw new RuntimeException("Unexpected bundle name: "
224 +b.getBaseBundleName());
225 }
226 }
227 Logger ll = Logger.getLogger(l.getName()+".bie.bye");
228 ResourceBundle hrb;
229 String hrbName;
230 ll.setLevel(Level.FINEST);
231 ll.addHandler(handler);
232 ll.fine("dummy");
233 ll.removeHandler(handler);
234 hrb = handler.rb;
235 hrbName = handler.rbName;
236 if (name != null) {
237 if (!name.equals(hrbName)) {
238 throw new RuntimeException("Unexpected bundle name: "
239 +hrb.getBaseBundleName());
240 }
241 if (!name.equals(hrb.getBaseBundleName())) {
242 throw new RuntimeException("Unexpected bundle name: "
243 +hrb.getBaseBundleName());
244 }
245 }
246
247 getRBcount.incrementAndGet();
248 if (!goOn) break;
249 Thread.sleep(1);
250 }
251 }
252 } catch (Exception x) {
253 fail(x);
254 }
255 }
256 }
257
258 final static class SetRB extends Thread {
259 final Class<? extends ResourceBundle> type;
260 final static ExecutorService executor = Executors.newSingleThreadExecutor();
261 final static class CheckRBTask implements Callable<Exception> {
262 final Logger logger;
263 volatile String rbName;
264 volatile ResourceBundle rb;
265
266 public CheckRBTask(Logger logger) {
267 this.logger = logger;
268 }
269
270 @Override
271 public Exception call() throws Exception {
272 try {
273 final String name = logger.getResourceBundleName();
274 if (!Objects.equals(name, rbName)) {
275 throw new RuntimeException("Unexpected rbname for "
276 + logger.getName() + ": " + name);
277 }
278 final ResourceBundle b = logger.getResourceBundle();
279 if (b != rb) {
280 throw new RuntimeException("Unexpected rb for "
281 + logger.getName() + ": " + b);
282 }
283 } catch(Exception x) {
284 return x;
285 }
286 return null;
287 }
288
289 public void check() throws Exception {
290 final FutureTask<Exception> futureTask = new FutureTask<>(this);
291 executor.submit(futureTask);
292 Exception x = futureTask.get();
293 if ( x != null) {
294 throw new RuntimeException("Check failed: "+x,x);
295 }
296 }
297 }
298 SetRB(Class<? extends ResourceBundle> type) {
299 super("SetRB["+type.getSimpleName()+"]");
300 this.type = type;
301 }
302 @Override
303 public void run() {
304 try {
305 while (goOn) {
306 Logger l;
307 Logger foo = Logger.getLogger("foo");
308 Logger bar = Logger.getLogger("foo.bar");
309 l = Logger.getLogger("foo.bar.l"+nextLong.incrementAndGet());
310 final CheckRBTask checkTask = new CheckRBTask(l);
311 checkTask.check();
312
313 for (int i=0; i < LCOUNT ; i++) {
314 if (!goOn) break;
315
316 ResourceBundle b = ResourceBundle.getBundle(type.getName());
317 try {
318 l.setResourceBundle(b);
319 checkTask.rb = b;
320 checkTask.rbName = type.getName();
321 checkTask.check();
322 if (!goOn) break;
323
324 String name = l.getResourceBundleName();
325 ResourceBundle bb = l.getResourceBundle();
326 if (!type.getName().equals(name)) {
327 throw new RuntimeException(this.getName()
328 + ": Unexpected name: "+name);
329 }
330 if (!b.getBaseBundleName().equals(name)) {
331 throw new RuntimeException(this.getName()
332 + ": Unexpected base name: " +
333 b.getBaseBundleName());
334 }
335 if (b != bb) {
336 throw new RuntimeException(this.getName()
337 + ": Unexpected bundle: "+bb);
338 }
339 setRBcount.incrementAndGet();
340 } catch (IllegalArgumentException x) {
341 final String name = l.getResourceBundleName();
342 if (!name.startsWith(MyBundle.class.getName())) {
343 throw new RuntimeException(this.getName()
344 + ": Unexpected name: "+name, x);
345 } else if (type.getName().equals(name)) {
346 throw new RuntimeException(this.getName()
347 + ": Unexpected exception for "+name, x);
348 }
349 throw x;
350 }
351 l.fine("I'm fine");
352 if (!goOn) break;
353 Thread.sleep(1);
354 }
355 }
356 } catch (Exception x) {
357 fail(x);
358 }
359 }
360 }
361
362 final static class SetRBName extends Thread {
363 int nexti = 0;
364 final Class<? extends ResourceBundle> type;
365 final static ExecutorService executor = Executors.newSingleThreadExecutor();
366 final static class CheckRBNameTask implements Callable<Exception> {
367 final Logger logger;
368 volatile String rbName;
369
370 public CheckRBNameTask(Logger logger) {
371 this.logger = logger;
372 }
373
374 @Override
375 public Exception call() throws Exception {
376 try {
377 final String name = logger.getResourceBundleName();
378 if (!Objects.equals(name, rbName)) {
379 throw new RuntimeException("Unexpected rbname for "
380 + logger.getName() + ": " + name);
381 }
382 final ResourceBundle b = logger.getResourceBundle();
383 if (!Objects.equals(b == null ? null : b.getBaseBundleName(), rbName)) {
384 throw new RuntimeException("Unexpected base name for "
385 + logger.getName() + ": " + b.getBaseBundleName());
386 }
387 } catch(Exception x) {
388 return x;
389 }
390 return null;
391 }
392
393 public void check() throws Exception {
394 final FutureTask<Exception> futureTask = new FutureTask<>(this);
395 executor.submit(futureTask);
396 Exception x = futureTask.get();
397 if ( x != null) {
398 throw new RuntimeException("Check failed: "+x,x);
399 }
400 }
401
402 }
403 SetRBName(Class<? extends ResourceBundle> type) {
404 super("SetRB["+type.getSimpleName()+"]");
405 this.type = type;
406 }
407 @Override
408 public void run() {
409 try {
410 while (goOn) {
411 Logger foo = Logger.getLogger("foo");
412 Logger bar = Logger.getLogger("foo.bar");
413 Logger l = Logger.getLogger("foo.bar.l"+nextLong.incrementAndGet());
414 final CheckRBNameTask checkTask = new CheckRBNameTask(l);
415 checkTask.check();
416
417 for (int i=0; i < LCOUNT ; i++) {
418 if (!goOn) break;
419
420 try {
421 Logger l2 = Logger.getLogger(l.getName(), type.getName());
422 if (l2 != l) {
423 System.err.println("**** ERROR WITH "+l.getName());
424 throw new RuntimeException("l2 != l ["
425 + l2 + "(" + l2.getName() + ") != "
426 + l + "(" + l.getName() + ")]");
427 }
428 checkTask.rbName = type.getName();
429 checkTask.check();
430 if (!goOn) break;
431
432 String name = l.getResourceBundleName();
433 ResourceBundle bb = l.getResourceBundle();
434 if (!type.getName().equals(name)) {
435 throw new RuntimeException(this.getName()
436 + ": Unexpected name: "+name);
437 }
438 if (!bb.getBaseBundleName().equals(name)) {
439 throw new RuntimeException(this.getName()
440 + ": Unexpected base name: "
441 + bb.getBaseBundleName());
442 }
443 setRBNameCount.incrementAndGet();
444 } catch (IllegalArgumentException x) {
445 final String name = l.getResourceBundleName();
446 if (!name.startsWith(MyBundle.class.getName())) {
447 throw new RuntimeException(this.getName()
448 + ": Unexpected name: "+name, x);
449 } else if (type.getName().equals(name)) {
450 throw new RuntimeException(this.getName()
451 + ": Unexpected exception for "+name, x);
452 }
453 throw x;
454 }
455 l.fine("I'm fine");
456 if (!goOn) break;
457 Thread.sleep(1);
458 }
459 }
460 } catch (Exception x) {
461 fail(x);
462 }
463 }
464 }
465
466 final static class DeadlockDetector extends Thread {
467
468 @Override
469 public void run() {
470 while(goOn) {
471 try {
472 long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
473 checkCount.incrementAndGet();
474 ids = ids == null ? new long[0] : ids;
475 if (ids.length == 1) {
476 throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]);
477 } else if (ids.length > 0) {
478 ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(ids);
479 System.err.println("Found "+ids.length+" deadlocked threads: ");
480 for (ThreadInfo inf : infos) {
481 System.err.println(inf.toString());
482 }
483 throw new RuntimeException("Found "+ids.length+" deadlocked threads");
484 }
485 Thread.sleep(100);
486 } catch(InterruptedException | RuntimeException x) {
487 fail(x);
488 }
489 }
490 }
491
492 }
493
494 static final class Stopper extends Thread {
495 long start;
496 long time;
497
498 Stopper(long time) {
499 start = System.currentTimeMillis();
500 this.time = time;
501 }
502
503 @Override
504 public void run() {
505 try {
506 long rest, previous;
507 previous = time;
508 while (goOn && (rest = start - System.currentTimeMillis() + time) > 0) {
509 if (previous == time || previous - rest >= STEP) {
510 Logger.getLogger("remaining").info(String.valueOf(rest)+"ms remaining...");
511 previous = rest == time ? rest -1 : rest;
512 System.gc();
513 }
514 if (goOn == false) break;
515 Thread.sleep(Math.min(rest, 100));
516 }
517 System.out.println(System.currentTimeMillis() - start
518 + " ms elapsed ("+time+ " requested)");
519 goOn = false;
520 } catch(InterruptedException | RuntimeException x) {
521 fail(x);
522 }
523 }
524
525 }
526
527 static void fail(Exception x) {
528 x.printStackTrace();
529 if (thrown == null) {
530 thrown = x;
531 }
532 goOn = false;
533 }
534 }
--- EOF ---