| /* |
| * 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."); |
| } |
| } |
| } |