From a86407a0b223fb90e454cbc27189fd3b71d587d6 Mon Sep 17 00:00:00 2001 From: "Daniel D. Daugherty" Date: Wed, 1 Jun 2011 17:11:23 -0700 Subject: [PATCH] 7045594: 4/4 fix for 6977677 introduced a ResourceBundle race Fix Logger.getLogger() ResourceBundle name race. Reviewed-by: dholmes, mchung --- .../classes/java/util/logging/Logger.java | 35 +- .../logging/LoggerResourceBundleRace.java | 203 ++++++ .../java/util/logging/RacingThreadsTest.java | 687 ++++++++++++++++++ 3 files changed, 915 insertions(+), 10 deletions(-) create mode 100644 jdk/test/java/util/logging/LoggerResourceBundleRace.java create mode 100644 jdk/test/java/util/logging/RacingThreadsTest.java diff --git a/jdk/src/share/classes/java/util/logging/Logger.java b/jdk/src/share/classes/java/util/logging/Logger.java index e3ce6bdac60..aebf63ed5e8 100644 --- a/jdk/src/share/classes/java/util/logging/Logger.java +++ b/jdk/src/share/classes/java/util/logging/Logger.java @@ -251,7 +251,10 @@ public class Logger { protected Logger(String name, String resourceBundleName) { this.manager = LogManager.getLogManager(); if (resourceBundleName != null) { - // Note: we may get a MissingResourceException here. + // MissingResourceException or IllegalArgumentException can + // be thrown by setupResourceInfo(). Since this is the Logger + // constructor, the resourceBundleName field is null so + // IllegalArgumentException cannot happen here. setupResourceInfo(resourceBundleName); } this.name = name; @@ -374,13 +377,10 @@ public class Logger { public static Logger getLogger(String name, String resourceBundleName) { LogManager manager = LogManager.getLogManager(); Logger result = manager.demandLogger(name); - if (result.resourceBundleName == null) { - // Note: we may get a MissingResourceException here. - result.setupResourceInfo(resourceBundleName); - } else if (!result.resourceBundleName.equals(resourceBundleName)) { - throw new IllegalArgumentException(result.resourceBundleName + - " != " + resourceBundleName); - } + + // MissingResourceException or IllegalArgumentException can be + // thrown by setupResourceInfo(). + result.setupResourceInfo(resourceBundleName); return result; } @@ -1353,14 +1353,29 @@ public class Logger { } // Private utility method to initialize our one entry - // resource bundle cache. + // resource bundle name cache. // Note: for consistency reasons, we are careful to check // that a suitable ResourceBundle exists before setting the - // ResourceBundleName. + // resourceBundleName field. + // Synchronized to prevent races in setting the field. private synchronized void setupResourceInfo(String name) { if (name == null) { return; } + + if (resourceBundleName != null) { + // this Logger already has a ResourceBundle + + if (resourceBundleName.equals(name)) { + // the names match so there is nothing more to do + return; + } + + // cannot change ResourceBundles once they are set + throw new IllegalArgumentException( + resourceBundleName + " != " + name); + } + ResourceBundle rb = findResourceBundle(name); if (rb == null) { // We've failed to find an expected ResourceBundle. diff --git a/jdk/test/java/util/logging/LoggerResourceBundleRace.java b/jdk/test/java/util/logging/LoggerResourceBundleRace.java new file mode 100644 index 00000000000..c7976d7cced --- /dev/null +++ b/jdk/test/java/util/logging/LoggerResourceBundleRace.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 7045594 + * @summary ResourceBundle setting race in Logger.getLogger(name, rbName) + * @author Daniel D. Daugherty + * @build RacingThreadsTest LoggerResourceBundleRace + * @run main LoggerResourceBundleRace + */ + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.ListResourceBundle; +import java.util.logging.Logger; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + + +public class LoggerResourceBundleRace extends RacingThreadsTest { + private final static int N_LOOPS = 500000; // # of race loops + private final static int N_SECS = 15; // # of secs to run test + // # of parallel threads; must match number of MyResources inner classes + private final static int N_THREADS = 3; + + private final static String LOGGER_PREFIX = "myLogger-"; + private final static String RESOURCE_PREFIX + = "LoggerResourceBundleRace$MyResources"; + // these counters are AtomicInteger since any worker thread can increment + private final static AtomicInteger iaeCnt = new AtomicInteger(); + private final static AtomicInteger worksCnt = new AtomicInteger(); + + Logger dummy; // dummy Logger + + LoggerResourceBundleRace(String name, int n_threads, int n_loops, + int n_secs) { + super(name, n_threads, n_loops, n_secs); + } + + + // Main test driver + // + public static void main(String[] args) { + LoggerResourceBundleRace test + = new LoggerResourceBundleRace("LoggerResourceBundleRace", + N_THREADS, N_LOOPS, N_SECS); + test.setVerbose( + Boolean.getBoolean("LoggerResourceBundleRace.verbose")); + + DriverThread driver = new DriverThread(test); + MyWorkerThread[] workers = new MyWorkerThread[N_THREADS]; + for (int i = 0; i < workers.length; i++) { + workers[i] = new MyWorkerThread(i, test); + } + test.runTest(driver, workers); + } + + public void oneTimeDriverInit(DriverThread dt) { + super.oneTimeDriverInit(dt); + dummy = null; + } + + public void perRaceDriverInit(DriverThread dt) { + super.perRaceDriverInit(dt); + + // - allocate a new dummy Logger without a ResourceBundle; + // this gives the racing threads less to do + // - reset the counters + dummy = Logger.getLogger(LOGGER_PREFIX + getLoopCnt()); + iaeCnt.set(0); + worksCnt.set(0); + } + + public void executeRace(WorkerThread wt) { + super.executeRace(wt); + + Logger myLogger = null; + try { + MyWorkerThread mwt = (MyWorkerThread) wt; // short hand + + // Here is the race: + // - the target Logger object has already been created by + // the DriverThread without a ResourceBundle name + // - in parallel, each WorkerThread calls Logger.getLogger() + // with a different ResourceBundle name + // - Logger.getLogger() should only successfully set the + // ResourceBundle name for one WorkerThread; all other + // WorkerThread calls to Logger.getLogger() should throw + // IllegalArgumentException + myLogger = Logger.getLogger(LOGGER_PREFIX + getLoopCnt(), + mwt.rbName); + if (myLogger.getResourceBundleName().equals(mwt.rbName)) { + // no exception and the ResourceBundle names match + worksCnt.incrementAndGet(); // ignore return + } else { + System.err.println(wt.getName() + + ": ERROR: expected ResourceBundleName '" + + mwt.rbName + "' does not match actual '" + + myLogger.getResourceBundleName() + "'"); + incAndGetFailCnt(); // ignore return + } + } catch (IllegalArgumentException iae) { + iaeCnt.incrementAndGet(); // ignore return + } catch (MissingResourceException mre) { + // This exception happens when N_THREADS above does not + // match the number of MyResources inner classes below. + // We exit since this is a coding error. + unexpectedException(wt, mre); + System.exit(2); + } + } + + public void checkRaceResults(DriverThread dt) { + super.checkRaceResults(dt); + + if (worksCnt.get() != 1) { + System.err.println(dt.getName() + ": ERROR: worksCnt should be 1" + + ": loopCnt=" + getLoopCnt() + ", worksCnt=" + worksCnt.get()); + incAndGetFailCnt(); // ignore return + } else if (iaeCnt.get() != N_THREADS - 1) { + System.err.println(dt.getName() + ": ERROR: iaeCnt should be " + + (N_THREADS - 1) + ": loopCnt=" + getLoopCnt() + + ", iaeCnt=" + iaeCnt.get()); + incAndGetFailCnt(); // ignore return + } + } + + public void oneTimeDriverEpilog(DriverThread dt) { + super.oneTimeDriverEpilog(dt); + + // Use the dummy Logger after the testing loop to make sure that + // dummy doesn't get optimized away in the testing loop. + dummy.info("This is a test message."); + } + + // N_THREADS above must match number of MyResources inner classes + // + public static class MyResources0 extends ListResourceBundle { + final static Object[][] contents = { + {"sample1", "translation #1 for sample1"}, + {"sample2", "translation #1 for sample2"}, + }; + + public Object[][] getContents() { + return contents; + } + } + + public static class MyResources1 extends ListResourceBundle { + final static Object[][] contents = { + {"sample1", "translation #2 for sample1"}, + {"sample2", "translation #2 for sample2"}, + }; + + public Object[][] getContents() { + return contents; + } + } + + public static class MyResources2 extends ListResourceBundle { + final static Object[][] contents = { + {"sample1", "translation #3 for sample1"}, + {"sample2", "translation #3 for sample2"}, + }; + + public Object[][] getContents() { + return contents; + } + } + + + // WorkerThread with a thread specific ResourceBundle name + // + public static class MyWorkerThread extends WorkerThread { + public final String rbName; // ResourceBundle name + + MyWorkerThread(int workerNum, RacingThreadsTest test) { + super(workerNum, test); + + rbName = RESOURCE_PREFIX + workerNum; + } + } +} diff --git a/jdk/test/java/util/logging/RacingThreadsTest.java b/jdk/test/java/util/logging/RacingThreadsTest.java new file mode 100644 index 00000000000..88dc8e5c9e3 --- /dev/null +++ b/jdk/test/java/util/logging/RacingThreadsTest.java @@ -0,0 +1,687 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + + +/** + * RacingThreadsTest is a support class for creating a test + * where multiple threads are needed to exercise a code path. + * The RacingThreadsTest class is typically used as follows: + * + * + * The RacingThreadsTest class provides many methods that can be + * overridden in order to provide test specific semantics at each + * identified test execution point. At a minimum, your test's + * subclass needs to override the + * "void executeRace(WorkerThread)" + * method in order to exercise your race condition and it needs to + * override the + * "void checkRaceResults(DriverThread)" + * method in order to check the results of the race. Your + * checkRaceResults() method should call the + * "int incAndGetFailCnt()" + * method when it detects a failure. It can also call the + * "void unexpectedException(Thread, Exception)" + * method if it detects an unexpected exception; this will cause + * an error message to be output and the failure count to be + * incremented. When the RacingThreadsTest.runTest() method is + * done running the races, if there is a non-zero failure count, + * then a RuntimeException will be thrown. + *

+ * The RacingThreadsTest class uses three internal barriers to + * coordinate actions between the DriverThread and the WorkerThreads. + * These barriers should not be managed or used by your test's + * subclass and are only mentioned here to provide clarity about + * interactions between the DriverThread and the WorkerThreads. + * The following transaction diagram shows when the different + * RacingThreadsTest methods are called relative to the different + * barriers: + * + *

+ * DriverThread           WorkerThread-0         WorkerThread-N-1
+ * ---------------------  ---------------------  ---------------------
+ * run(workers)
+ * oneTimeDriverInit()
+ * <start WorkerThreads>  run()                  run()
+ * <top of race loop>     :                      :
+ * perRaceDriverInit()    oneTimeWorkerInit()    oneTimeWorkerInit()
+ * :                      <top of race loop>     <top of race loop>
+ * :                      perRaceWorkerInit()    perRaceWorkerInit()
+ * startBarrier           startBarrier           startBarrier
+ * :                      executeRace()          executeRace()
+ * finishBarrier          finishBarrier          finishBarrier
+ * checkRaceResults()     :                      :
+ * resetBarrier           resetBarrier           resetBarrier
+ * perRaceDriverEpilog()  perRaceWorkerEpilog()  perRaceWorkerEpilog()
+ * <repeat race or done>  <repeat race or done>  <repeat race or done>
+ * :                      oneTimeWorkerEpilog()  oneTimeWorkerEpilog()
+ * <join WorkerThreads>   <WorkerThread ends>    <WorkerThread ends>
+ * oneTimeDriverEpilog()
+ * <DriverThread ends>
+ * 
+ * + * Just to be clear about the parallel parts of this infrastructure: + * + * + * Once the DriverThread has ended, the RacingThreadsTest.runTest() + * method checks the failure count. If there were no failures, then + * a "Test PASSed" message is printed. Otherwise, the failure count + * is printed, a "Test FAILed" message is printed and a RuntimeException + * is thrown. + */ +public class RacingThreadsTest { + /** + * name of the test + */ + public final String TEST_NAME; + /** + * maximum number of test iterations (race loops) + */ + public final int N_LOOPS; + /** + * the maximum number of seconds to execute the test loop + */ + public final int N_SECS; + /** + * number of WorkerThreads + */ + public final int N_THREADS; + + /** + * Creates a test with the specified name and the specified number + * of WorkerThreads that execute the test code in parallel up to + * n_loops iterations or n_secs seconds. The RacingThreadsTest + * class is extended in order to provide the test specific variables + * and/or code. + * @param name the name of the test + * @param n_threads the number of WorkerThreads + * @param n_loops the maximum number of test iterations + * @param n_secs the maximum number of seconds to execute the test loop + */ + RacingThreadsTest(String name, int n_threads, int n_loops, int n_secs) { + TEST_NAME = name; + N_THREADS = n_threads; + N_LOOPS = n_loops; + N_SECS = n_secs; + + finishBarrier = new CyclicBarrier(N_THREADS + 1); + resetBarrier = new CyclicBarrier(N_THREADS + 1); + startBarrier = new CyclicBarrier(N_THREADS + 1); + } + + + /** + * Entry point for exercising the RacingThreadsTest class. + */ + public static void main(String[] args) { + // a dummy test: + // - 2 threads + // - 3 loops + // - 2 seconds + // - standard DriverThread + // - standard WorkerThread + RacingThreadsTest test = new RacingThreadsTest("dummy", 2, 3, 2); + DriverThread driver = new DriverThread(test); + WorkerThread[] workers = new WorkerThread[2]; + for (int i = 0; i < workers.length; i++) { + workers[i] = new WorkerThread(i, test); + } + test.runTest(driver, workers); + } + + private static volatile boolean done = false; // test done flag + + // # of fails; AtomicInteger since any WorkerThread can increment + private static final AtomicInteger failCnt = new AtomicInteger(); + // # of loops; volatile is OK since only DriverThread increments + // but using AtomicInteger for consistency + private static final AtomicInteger loopCnt = new AtomicInteger(); + private static boolean verbose + = Boolean.getBoolean("RacingThreadsTest.verbose"); + + // barriers for starting, finishing and resetting the race + private final CyclicBarrier finishBarrier; + private final CyclicBarrier resetBarrier; + private final CyclicBarrier startBarrier; + + + /** + * Get current done flag value. + * @return the current done flag value + */ + public boolean getDone() { + return done; + } + + /** + * Set done flag to specified value. + * @param v the new done flag value + */ + public void setDone(boolean v) { + done = v; + } + + /** + * Get current failure counter value. + * @return the current failure count + */ + public int getFailCnt() { + return failCnt.get(); + } + + /** + * Increment and get current failure counter value. + * @return the current failure count after incrementing + */ + public int incAndGetFailCnt() { + return failCnt.incrementAndGet(); + } + + /** + * Get current loop counter value. + * @return the current loop count + */ + public int getLoopCnt() { + return loopCnt.get(); + } + + /** + * Increment and get current loop counter value. + * @return the current loop count after incrementing + */ + public int incAndGetLoopCnt() { + return loopCnt.incrementAndGet(); + } + + /** + * Get current verbose flag value. + * @return the current verbose flag value + */ + public boolean getVerbose() { + return verbose; + } + + /** + * Set verbose flag to specified value. + * @param v the new verbose flag value + */ + public void setVerbose(boolean v) { + verbose = v; + } + + /** + * Run the test with the specified DriverThread and the + * specified WorkerThreads. + * @param driver the DriverThread for running the test + * @param workers the WorkerThreads for executing the race + * @exception RuntimeException the test has failed + */ + public void runTest(DriverThread driver, WorkerThread[] workers) { + driver.run(workers); + + try { + driver.join(); + } catch (InterruptedException ie) { + unexpectedException(Thread.currentThread(), ie); + // fall through to test failed below + } + + if (failCnt.get() == 0) { + System.out.println(TEST_NAME + ": Test PASSed."); + } else { + System.out.println(TEST_NAME + ": failCnt=" + failCnt.get()); + System.out.println(TEST_NAME + ": Test FAILed."); + throw new RuntimeException("Test Failed"); + } + } + + /** + * Helper method for reporting an unexpected Exception and + * calling incAndGetFailCnt(); + * @param t the Thread that caught the exception + * @param e the Exception that was caught + */ + public void unexpectedException(Thread t, Exception e) { + System.err.println(t.getName() + ": ERROR: unexpected exception: " + e); + incAndGetFailCnt(); // ignore return + } + + + // The following methods are typically overridden by the subclass + // of RacingThreadsTest to provide test specific semantics at each + // identified test execution point: + + /** + * Initialize 1-time items for the DriverThread. + * Called by the DriverThread before WorkerThreads are started. + * @param dt the DriverThread + */ + public void oneTimeDriverInit(DriverThread dt) { + if (verbose) + System.out.println(dt.getName() + ": oneTimeDriverInit() called"); + } + + /** + * Initialize 1-time items for a WorkerThread. Called by a + * WorkerThread after oneTimeDriverInit() and before the + * WorkerThread checks in with startBarrier. May execute in + * parallel with perRaceDriverInit() or with another + * WorkerThread's oneTimeWorkerInit() call or another + * WorkerThread's perRaceWorkerInit() call. + * @param wt the WorkerThread + */ + public void oneTimeWorkerInit(WorkerThread wt) { + if (verbose) + System.out.println(wt.getName() + ": oneTimeWorkerInit() called"); + } + + /** + * Initialize per-race items for the DriverThread. Called by the + * DriverThread before it checks in with startBarrier. May execute + * in parallel with oneTimeWorkerInit() and perRaceWorkerInit() + * calls. After any race except for the last race, this method may + * execute in parallel with perRaceWorkerEpilog(). + * @param dt the DriverThread + */ + public void perRaceDriverInit(DriverThread dt) { + if (verbose) + System.out.println(dt.getName() + ": perRaceDriverInit() called"); + } + + /** + * Initialize per-race items for a WorkerThread. Called by each + * WorkerThread before it checks in with startBarrier. On the first + * call, this method may execute in parallel with another + * WorkerThread's oneTimeWorkerInit() call. On any call, this method + * may execute in parallel with perRaceDriverInit() or another + * WorkerThread's perRaceWorkerInit() call. After any race except + * for the last race, this method may execute in parallel with + * perRaceDriverEpilog() or another WorkerThread's + * perRaceWorkerEpilog() call. + * @param wt the WorkerThread + */ + public void perRaceWorkerInit(WorkerThread wt) { + if (verbose) + System.out.println(wt.getName() + ": perRaceWorkerInit() called"); + } + + /** + * Execute the race in a WorkerThread. Called by each WorkerThread + * after it has been released from startBarrier. + * @param wt the WorkerThread + */ + public void executeRace(WorkerThread wt) { + if (verbose) + System.out.println(wt.getName() + ": executeRace() called"); + } + + /** + * Check race results in the DriverThread. Called by the DriverThread + * after it has been released from finishBarrier and before the + * DriverThread checks in with resetBarrier. + * @param dt the DriverThread + */ + public void checkRaceResults(DriverThread dt) { + if (verbose) + System.out.println(dt.getName() + ": checkRaceResults() called"); + } + + /** + * Handle end-of-race items for the DriverThread. Called by the + * DriverThread after it has been released from resetBarrier and + * before the DriverThread checks in again with startBarrier. Can + * execute in parallel with perRaceWorkerEpilog(). If this is not + * the last race, can execute in parallel with perRaceWorkerInit(). + * If this is the last race, can execute in parallel with + * oneTimeWorkerEpilog(). + * @param dt the DriverThread + */ + public void perRaceDriverEpilog(DriverThread dt) { + if (verbose) + System.out.println(dt.getName() + ": perRaceDriverEpilog() called"); + } + + /** + * Handle end-of-race items for a WorkerThread. Called by each + * WorkerThread after it has been released from resetBarrier and + * before the WorkerThread checks in again with startBarrier. + * Can execute in parallel with perRaceDriverEpilog() or another + * WorkerThread's perRaceWorkerEpilog() call. If this is not the + * last race, can execute in parallel with perRaceDriverInit(), + * or another WorkerThread's perRaceWorkerInit() call. If this + * is the last race, can execute in parallel with another + * WorkerThread's oneTimeWorkerEpilog() call. + * @param wt the WorkerThread + */ + public void perRaceWorkerEpilog(WorkerThread wt) { + if (verbose) + System.out.println(wt.getName() + ": perRaceWorkerEpilog() called"); + } + + /** + * Handle end-of-test items for a WorkerThread. Called by each + * WorkerThread after it has detected that all races are done and + * before oneTimeDriverEpilog() is called. Can execute in parallel + * with perRaceDriverEpilog(), with another WorkerThread's + * perRaceWorkerEpilog() call or with another WorkerThread's + * oneTimeWorkerEpilog() call. + * @param wt the WorkerThread + */ + public void oneTimeWorkerEpilog(WorkerThread wt) { + if (verbose) + System.out.println(wt.getName() + ": oneTimeWorkerEpilog() called"); + } + + /** + * Handle end-of-test items for the DriverThread. Called by the + * DriverThread after all the WorkerThreads have called + * oneTimeWorkerEpilog(). + * @param dt the DriverThread + */ + public void oneTimeDriverEpilog(DriverThread dt) { + if (verbose) + System.out.println(dt.getName() + ": oneTimeDriverEpilog() called"); + } + + + /** + * DriverThread for executing the test. + */ + public static class DriverThread extends Thread { + private final RacingThreadsTest test; + + /** + * Create the test DriverThread that manages all the WorkerThreads. + * The DriverThread class can be extended to provide test specific + * variables and/or code. However, that is typically done in the + * subclass of RacingThreadsTest. + * @parameter test the RacingThreadsTest being run + */ + DriverThread(RacingThreadsTest test) { + super("DriverThread"); + this.test = test; + } + + private void run(WorkerThread[] workers) { + System.out.println(getName() + ": is starting."); + System.out.println(getName() + ": # WorkerThreads: " + test.N_THREADS); + System.out.println(getName() + ": max # loops: " + test.N_LOOPS); + System.out.println(getName() + ": max # secs: " + test.N_SECS); + + // initialize 1-time items for the DriverThread + test.oneTimeDriverInit(this); + + // start all the threads + for (int i = 0; i < workers.length; i++) { + workers[i].start(); + } + + // All WorkerThreads call oneTimeWorkerInit() and + // perRaceWorkerInit() on the way to startBarrier. + + long endTime = System.currentTimeMillis() + test.N_SECS * 1000; + + for (; !test.getDone() && test.getLoopCnt() < test.N_LOOPS; + test.incAndGetLoopCnt()) { + + if (test.getVerbose() && (test.N_LOOPS < 10 || + (test.getLoopCnt() % (test.N_LOOPS / 10)) == 0)) { + System.out.println(getName() + ": race loop #" + + test.getLoopCnt()); + } + + // initialize per-race items for the DriverThread + test.perRaceDriverInit(this); + + try { + // we've setup the race so start it when all + // WorkerThreads get to the startBarrier + test.startBarrier.await(); + } catch (BrokenBarrierException bbe) { + test.unexpectedException(this, bbe); + return; + } catch (InterruptedException ie) { + test.unexpectedException(this, ie); + return; + } + + // All WorkerThreads are racing via executeRace() + // at this point + + // wait for all threads to finish the race + try { + test.finishBarrier.await(); + } catch (BrokenBarrierException bbe) { + test.unexpectedException(this, bbe); + return; + } catch (InterruptedException ie) { + test.unexpectedException(this, ie); + return; + } + // All WorkerThreads are heading to resetBarrier at this + // point so we can check the race results before we reset + // for another race (or bail because we are done). + + test.checkRaceResults(this); + + if (test.getLoopCnt() + 1 >= test.N_LOOPS || + System.currentTimeMillis() >= endTime) { + // This is the last loop or we're out of time. + // Let test threads know we are done before we release + // them from resetBarrier + test.setDone(true); + } + + // release the WorkerThreads from resetBarrier + try { + test.resetBarrier.await(); + } catch (BrokenBarrierException bbe) { + test.unexpectedException(this, bbe); + return; + } catch (InterruptedException ie) { + test.unexpectedException(this, ie); + return; + } + + // All WorkerThreads call perRaceWorkerEpilog(). If + // this is not the last loop, then all WorkerThreads + // will also call perRaceWorkerInit() on the way to + // startBarrier. If this is the last loop, then all + // WorkerThreads will call oneTimeWorkerEpilog() on + // their way to ending. + + // handle end-of-race items for the DriverThread + test.perRaceDriverEpilog(this); + } + + System.out.println(getName() + ": completed " + test.getLoopCnt() + + " race loops."); + if (test.getLoopCnt() < test.N_LOOPS) { + System.out.println(getName() + ": race stopped @ " + test.N_SECS + + " seconds."); + } + + for (int i = 0; i < workers.length; i++) { + try { + workers[i].join(); + } catch (InterruptedException ie) { + test.unexpectedException(this, ie); + return; + } + } + + // handle end-of-test items for the DriverThread + test.oneTimeDriverEpilog(this); + + System.out.println(getName() + ": is done."); + } + } + + + /** + * WorkerThread for executing the race. + */ + public static class WorkerThread extends Thread { + private final RacingThreadsTest test; + private final int workerNum; + + /** + * Creates WorkerThread-N that executes the test code. The + * WorkerThread class can be extended to provide test thread + * specific variables and/or code. + * @param workerNum the number for the new WorkerThread + * @parameter test the RacingThreadsTest being run + */ + WorkerThread(int workerNum, RacingThreadsTest test) { + super("WorkerThread-" + workerNum); + this.test = test; + this.workerNum = workerNum; + } + + /** + * get the WorkerThread's number + * @return the WorkerThread's number + */ + public int getWorkerNum() { + return workerNum; + } + + /** + * Run the race in a WorkerThread. + */ + public void run() { + System.out.println(getName() + ": is running."); + + // initialize 1-time items for the WorkerThread + test.oneTimeWorkerInit(this); + + while (!test.getDone()) { + // initialize per-race items for the WorkerThread + test.perRaceWorkerInit(this); + + try { + test.startBarrier.await(); // wait for race to start + } catch (BrokenBarrierException bbe) { + test.unexpectedException(this, bbe); + return; + } catch (InterruptedException ie) { + test.unexpectedException(this, ie); + return; + } + + // execute the race for the WorkerThread + test.executeRace(this); + + try { + test.finishBarrier.await(); // this thread is done + } catch (BrokenBarrierException bbe) { + test.unexpectedException(this, bbe); + return; + } catch (InterruptedException ie) { + test.unexpectedException(this, ie); + return; + } + + try { + test.resetBarrier.await(); // wait for race to reset + } catch (BrokenBarrierException bbe) { + test.unexpectedException(this, bbe); + return; + } catch (InterruptedException ie) { + test.unexpectedException(this, ie); + return; + } + + // handle end-of-race items for the WorkerThread + test.perRaceWorkerEpilog(this); + } + + // handle end-of-test items for the WorkerThread + test.oneTimeWorkerEpilog(this); + + System.out.println(getName() + ": is ending."); + } + } +}