| /* |
| * Copyright (c) 2014, 2018, 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. |
| */ |
| |
| package vm.mlvm.share; |
| |
| import java.util.concurrent.BrokenBarrierException; |
| import java.util.concurrent.CyclicBarrier; |
| import nsk.share.Log.TraceLevel; |
| import vm.share.options.Option; |
| |
| /** |
| * The test looks for late CPU stores, which is not visible for other CPU. |
| * <p> |
| * In absence of synchronization (such as memory barriers and so on), typical modern CPUs |
| * (Intel x86 32/64, SPARC in TSO mode) put stores into buffer instead of immediately writing them to memory. |
| * |
| * So the following program: |
| * <code> |
| * write A |
| * read B |
| * </code> |
| * can actually be transformed to and seen by other processors as: |
| * <code> |
| * read B |
| * write A |
| * </code> |
| * <p> |
| * DekkerTest runs two threads, A and B, which perform operations on a shared array of Actors concurrently. |
| * Reordering mentioned above is detected by the test and reported as failure |
| * (unless mayFail option is specified in the constructor or command-line option). |
| * |
| * <p> |
| * The tests should subclass Actor with test-specific data and code. |
| * The Actor subclass requirements: |
| * |
| * <ul> |
| * <li>the class should have two data fields: |
| * <code> |
| * A |
| * B |
| * </code> |
| * of the same type, which are able hold one of two values. |
| * Let's call these values TRUE and FALSE (these can be any two values, say 0 and 1). |
| * |
| * <li>* actorA() is called from thread A and should implement the following pseudo-code: |
| * <code> |
| * A = TRUE |
| * optional MEMBAR #StoreLoad |
| * return (B == TRUE) |
| * </code> |
| * |
| * <li>actorB() is called from thread B and should do the following: |
| * <code> |
| * { |
| * B = TRUE |
| * optional MEMBAR #StoreLoad |
| * return (A == TRUE) |
| * } |
| * </code> |
| * |
| * <li>reset() method should do the following: |
| * <code> |
| * { |
| * A = FALSE |
| * B = FALSE |
| * } |
| * </code> |
| * |
| * The use of memory barriers in actorX() methods is up to the implementor -- depending on the goal of testing. |
| * |
| */ |
| public class DekkerTest extends MlvmTest { |
| |
| @Option(name = "actorClass", default_value = "", description = "Name of actor class (see test comments)") |
| private String actorClassNameOpt = ""; |
| |
| @Option(name = "mayFail", default_value = "false", description = "Don't report test failure (use it just a stress test)") |
| private boolean mayFailOpt; |
| |
| @Option(name = "iterations", default_value = "1000000", description = "Number of iterations in each Actor thread (i.e., shared array size)") |
| private int iterationsOpt = 1000000; |
| |
| /** |
| * Actor interface, which should be implemented for using with DekkerTest. |
| * The requirements for Actor implementation are outlined in {@link DekkerTest} class documentation. |
| */ |
| public interface Actor { |
| /** |
| * Sets fields A and B to false |
| */ |
| void reset(); |
| |
| /** |
| * Sets field A to true, and returns field B contents |
| * @return field B contents |
| * @throws Throwable in case of error |
| */ |
| boolean actorA() throws Throwable; |
| |
| /** |
| * Sets field B to true, and returns field A contents |
| * @return field A contents |
| * @throws Throwable in case of error |
| */ |
| boolean actorB() throws Throwable; |
| } |
| |
| private static final class ResultData { |
| public boolean a; |
| public boolean b; |
| |
| public ResultData() { |
| } |
| |
| public void reset() { |
| a = false; |
| b = false; |
| } |
| } |
| |
| private static class RaceStatistics { |
| public int syncErrors; |
| public int runSideBySide; |
| public int A_outruns_B; |
| public int B_outruns_A; |
| |
| public void reset() { |
| syncErrors = 0; |
| runSideBySide = 0; |
| A_outruns_B = 0; |
| B_outruns_A = 0; |
| } |
| } |
| |
| private Class<? extends Actor> actorClass; |
| private final CyclicBarrier startBarrier = new CyclicBarrier(2); // We have two actors |
| private Actor[] testData; |
| private ResultData[] results; |
| private final RaceStatistics raceStatistics = new RaceStatistics(); |
| |
| public DekkerTest() { |
| } |
| |
| /** |
| * Sets actor class. |
| * @param ac Actor class, which implements DekkerTest.Actor interface |
| */ |
| public void setActorClass(Class<? extends Actor> ac) { |
| actorClass = ac; |
| } |
| |
| /** |
| * Sets mayFail option. When set to true, synchronization failure is not reported as test failure (DekkerTest is used as a stress test). |
| * @param mayFail if true, don't report sync failure as test failure, false otherwise |
| */ |
| public void setMayFailOpt(boolean mayFail) { |
| mayFailOpt = mayFail; |
| } |
| |
| /** |
| * Initializes test data, parses command-line options and checks Actor class |
| */ |
| @Override |
| public void initializeTest() { |
| // Override values set by setActorClass() by command-line option, if any |
| try { |
| if (!actorClassNameOpt.isEmpty()) { |
| actorClass = Class.forName(actorClassNameOpt).asSubclass(Actor.class); |
| } else { |
| if (actorClass == null) { |
| throw new RuntimeException("Test error: the actor class should be specified via command-line options or in the constructor"); |
| } |
| } |
| |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException("Actor class '" + actorClassNameOpt + "' not found", e); |
| } catch (ClassCastException e) { |
| throw new RuntimeException("Test error: the actor class " + actorClass.getName() + " should implement " + Actor.class.getName() + " interface", e); |
| } |
| |
| // Generate data |
| int iterations = iterationsOpt * getStressOptions().getIterationsFactor(); |
| if (iterations <= 0) { |
| throw new RuntimeException("Invalid number of iterations specified in -iterations and -stressIterationsFactor options: " + iterations); |
| } |
| |
| results = new ResultData[iterations]; |
| for (int i = 0; i < results.length; ++i) { |
| results[i] = new ResultData(); |
| } |
| |
| testData = new Actor[results.length]; |
| try { |
| for (int i = 0; i < testData.length; ++i) { |
| testData[i] = actorClass.newInstance(); |
| } |
| } catch (ReflectiveOperationException e) { |
| throw new RuntimeException("Test error: can't instantiate class " + actorClass.getName(), e); |
| } |
| } |
| |
| /** |
| * Resets the test data between test runs |
| */ |
| @Override |
| public void resetTest() { |
| for (Actor a : testData) { |
| a.reset(); |
| } |
| for (ResultData d : results) { |
| d.reset(); |
| } |
| } |
| |
| private class RunnerA extends Thread { |
| @Override |
| public void run() { |
| try { |
| startBarrier.await(); |
| for (int i = 0; i < results.length; ++i) { |
| results[i].a = testData[i].actorA(); |
| } |
| } catch (Throwable t) { |
| markTestFailed("Exception in RunnerA", t); |
| } |
| } |
| } |
| |
| private class RunnerB extends Thread { |
| @Override |
| public void run() { |
| try { |
| startBarrier.await(); |
| for (int i = 0; i < results.length; ++i) { |
| results[i].b = testData[i].actorB(); |
| } |
| } catch (Throwable t) { |
| markTestFailed("Exception in RunnerB", t); |
| } |
| } |
| } |
| |
| @Override |
| public boolean run() throws Throwable { |
| RunnerA threadA = new RunnerA(); |
| threadA.start(); |
| |
| RunnerB threadB = new RunnerB(); |
| threadB.start(); |
| |
| threadA.join(); |
| threadB.join(); |
| |
| if (isMarkedFailed()) |
| return false; |
| |
| analyzeResults(); |
| printResults(); |
| |
| if (mayFailOpt) { |
| return true; |
| } |
| |
| return (raceStatistics.syncErrors == 0); |
| } |
| |
| private void analyzeResults() { |
| raceStatistics.reset(); |
| |
| for (int i = 0; i < results.length; ++i) { |
| boolean resultA = results[i].a; |
| boolean resultB = results[i].b; |
| |
| if (resultA && resultB) { |
| |
| ++raceStatistics.runSideBySide; |
| |
| } else if (resultA && !resultB) { |
| |
| ++raceStatistics.A_outruns_B; |
| |
| } else if (!resultA && resultB) { |
| |
| ++raceStatistics.B_outruns_A; |
| |
| } else if (!resultA && !resultB) { |
| |
| ++raceStatistics.syncErrors; |
| |
| } else { |
| |
| throw new RuntimeException("Should not reach here"); |
| } |
| } |
| } |
| |
| private void printResults() { |
| int logLevel = (raceStatistics.syncErrors != 0) ? TraceLevel.TRACE_IMPORTANT : TraceLevel.TRACE_NORMAL; |
| |
| Env.getLog().trace(logLevel, "\n" |
| + "Late stores (sync. errors): " + raceStatistics.syncErrors + "\n" |
| + "B outruns A : " + raceStatistics.B_outruns_A + "\n" |
| + "A outruns B : " + raceStatistics.A_outruns_B + "\n" |
| + "A and B run side by side : " + raceStatistics.runSideBySide); |
| } |
| |
| public static void main(String[] args) { |
| MlvmTest.launch(args); |
| } |
| } |
| |