7045594: 4/4 fix for 6977677 introduced a ResourceBundle race
Summary: Fix Logger.getLogger() ResourceBundle name race.
Reviewed-by: dholmes, mchung
--- a/jdk/src/share/classes/java/util/logging/Logger.java Wed Jun 01 17:10:30 2011 -0700
+++ b/jdk/src/share/classes/java/util/logging/Logger.java Wed Jun 01 17:11:23 2011 -0700
@@ -251,7 +251,10 @@
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 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 @@
}
// 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.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/logging/LoggerResourceBundleRace.java Wed Jun 01 17:11:23 2011 -0700
@@ -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;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/logging/RacingThreadsTest.java Wed Jun 01 17:11:23 2011 -0700
@@ -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:
+ * <ul>
+ * <li>
+ * Extend RacingThreadsTest class in order to provide the test
+ * specific variables and/or code, e.g., <br>
+ * public class MyRacingThreadsTest extends RacingThreadsTest
+ * <li>
+ * Use
+ * "new MyRacingThreadsTest(name, n_threads, n_loops, n_secs)"
+ * to create your 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.
+ * <li>
+ * Use
+ * "new DriverThread(test)"
+ * to create the test DriverThread that manages all the
+ * WorkerThreads. The DriverThread class can be extended to
+ * provide test specific code and/or variables. However, that
+ * is typically done in your test's subclass.
+ * <li>
+ * Use
+ * "new WorkerThread(workerNum, test)"
+ * to create WorkerThread-workerNum that executes the test code.
+ * The WorkerThread class can be extended to provide test thread
+ * specific code and/or variables.
+ * <li>
+ * Use
+ * "RacingThreadsTest.runTest(driver, workers)"
+ * to run the test. If the test fails, then a RuntimeException
+ * is thrown.
+ * </ul>
+ *
+ * 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.
+ * <p>
+ * 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:
+ *
+ * <pre>
+ * 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>
+ * </pre>
+ *
+ * Just to be clear about the parallel parts of this infrastructure:
+ * <ul>
+ * <li>
+ * After the DriverThread starts the WorkerThreads, the DriverThread
+ * and the WorkerThreads are running in parallel until the startBarrier
+ * is reached.
+ * <li>
+ * After the WorkerThreads leave the startBarrier, they are running
+ * the code in executeRace() in parallel which is the whole point
+ * of this class.
+ * <li>
+ * The DriverThread heads straight to the finishBarrier and waits for
+ * the WorkerThreads to get there.
+ * <li>
+ * After the DriverThread leaves the finishBarrier, it checks the
+ * results of the race.
+ * <li>
+ * The WorkerThreads head straight to the resetBarrier and wait for
+ * the DriverThread to get there.
+ * <li>
+ * If this is not the last race, then after the DriverThread and
+ * WorkerThreads leave the resetBarrier, the DriverThread and the
+ * WorkerThreads are running in parallel until the startBarrier
+ * is reached.
+ * <li>
+ * If this is the last race, then after the DriverThread and
+ * WorkerThreads leave the resetBarrier, the DriverThread and the
+ * WorkerThreads are running in parallel as each WorkerThread ends.
+ * <li>
+ * The DriverThread waits for the WorkerThreads to end and
+ * then it ends
+ * </ul>
+ *
+ * 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.");
+ }
+ }
+}