7045594: 4/4 fix for 6977677 introduced a ResourceBundle race
Fix Logger.getLogger() ResourceBundle name race. Reviewed-by: dholmes, mchung
This commit is contained in:
parent
0d21eb970f
commit
a86407a0b2
@ -251,7 +251,10 @@ public class Logger {
|
|||||||
protected Logger(String name, String resourceBundleName) {
|
protected Logger(String name, String resourceBundleName) {
|
||||||
this.manager = LogManager.getLogManager();
|
this.manager = LogManager.getLogManager();
|
||||||
if (resourceBundleName != null) {
|
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);
|
setupResourceInfo(resourceBundleName);
|
||||||
}
|
}
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -374,13 +377,10 @@ public class Logger {
|
|||||||
public static Logger getLogger(String name, String resourceBundleName) {
|
public static Logger getLogger(String name, String resourceBundleName) {
|
||||||
LogManager manager = LogManager.getLogManager();
|
LogManager manager = LogManager.getLogManager();
|
||||||
Logger result = manager.demandLogger(name);
|
Logger result = manager.demandLogger(name);
|
||||||
if (result.resourceBundleName == null) {
|
|
||||||
// Note: we may get a MissingResourceException here.
|
// MissingResourceException or IllegalArgumentException can be
|
||||||
result.setupResourceInfo(resourceBundleName);
|
// thrown by setupResourceInfo().
|
||||||
} else if (!result.resourceBundleName.equals(resourceBundleName)) {
|
result.setupResourceInfo(resourceBundleName);
|
||||||
throw new IllegalArgumentException(result.resourceBundleName +
|
|
||||||
" != " + resourceBundleName);
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1353,14 +1353,29 @@ public class Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private utility method to initialize our one entry
|
// Private utility method to initialize our one entry
|
||||||
// resource bundle cache.
|
// resource bundle name cache.
|
||||||
// Note: for consistency reasons, we are careful to check
|
// Note: for consistency reasons, we are careful to check
|
||||||
// that a suitable ResourceBundle exists before setting the
|
// 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) {
|
private synchronized void setupResourceInfo(String name) {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
return;
|
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);
|
ResourceBundle rb = findResourceBundle(name);
|
||||||
if (rb == null) {
|
if (rb == null) {
|
||||||
// We've failed to find an expected ResourceBundle.
|
// We've failed to find an expected ResourceBundle.
|
||||||
|
203
jdk/test/java/util/logging/LoggerResourceBundleRace.java
Normal file
203
jdk/test/java/util/logging/LoggerResourceBundleRace.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
687
jdk/test/java/util/logging/RacingThreadsTest.java
Normal file
687
jdk/test/java/util/logging/RacingThreadsTest.java
Normal file
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user