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