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