changeset 245:44ed77c659aa

Runners: rebuild the communication between host and client VM.
author shade
date Fri, 13 May 2016 00:41:21 +0300
parents 0b45beda9932
children 4eaeffc68390
files jcstress-core/src/main/java/org/openjdk/jcstress/ForkedMain.java jcstress-core/src/main/java/org/openjdk/jcstress/JCStress.java jcstress-core/src/main/java/org/openjdk/jcstress/Options.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/collectors/NetworkInputCollector.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/collectors/NetworkOutputCollector.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/collectors/TestResult.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/grading/ExceptionReportPrinter.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/grading/HTMLReportPrinter.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/processors/JCStressTestProcessor.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/Control.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/Runner.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java jcstress-core/src/main/java/org/openjdk/jcstress/link/BinaryLinkClient.java jcstress-core/src/main/java/org/openjdk/jcstress/link/BinaryLinkServer.java jcstress-core/src/main/java/org/openjdk/jcstress/link/FinishingFrame.java jcstress-core/src/main/java/org/openjdk/jcstress/link/JobRequestFrame.java jcstress-core/src/main/java/org/openjdk/jcstress/link/JobResponseFrame.java jcstress-core/src/main/java/org/openjdk/jcstress/link/ResultsFrame.java jcstress-core/src/main/java/org/openjdk/jcstress/util/FileUtils.java
diffstat 19 files changed, 864 insertions(+), 542 deletions(-) [+]
line wrap: on
line diff
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/ForkedMain.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/ForkedMain.java	Fri May 13 00:41:21 2016 +0300
@@ -24,7 +24,8 @@
  */
 package org.openjdk.jcstress;
 
-import org.openjdk.jcstress.infra.collectors.NetworkOutputCollector;
+import org.openjdk.jcstress.infra.runners.TestConfig;
+import org.openjdk.jcstress.link.BinaryLinkClient;
 import org.openjdk.jcstress.vm.WhiteBoxSupport;
 
 /**
@@ -35,20 +36,24 @@
 public class ForkedMain {
 
     public static void main(String[] args) throws Exception {
-        Options opts = new Options(args);
-        if (!opts.parse()) {
-            System.exit(1);
-        }
-
         try {
             WhiteBoxSupport.initSafely();
         } catch (NoClassDefFoundError e) {
             // expected on JDK 7 and lower, parent should have printed the message for user
         }
 
-        NetworkOutputCollector collector = new NetworkOutputCollector(opts.getHostName(), opts.getHostPort());
-        new JCStress().run(opts, true, collector);
-        collector.close();
+        if (args.length < 2) {
+            throw new IllegalStateException("Expected two arguments");
+        }
+
+        String host = args[0];
+        int port = Integer.valueOf(args[1]);
+
+        BinaryLinkClient link = new BinaryLinkClient(host, port);
+        TestConfig config = link.nextJob();
+
+        new JCStress().runEmbedded(config, link);
+        link.close();
     }
 
 }
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/JCStress.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/JCStress.java	Fri May 13 00:41:21 2016 +0300
@@ -27,18 +27,14 @@
 import org.openjdk.jcstress.infra.Scheduler;
 import org.openjdk.jcstress.infra.Status;
 import org.openjdk.jcstress.infra.TestInfo;
-import org.openjdk.jcstress.infra.collectors.DiskReadCollector;
-import org.openjdk.jcstress.infra.collectors.DiskWriteCollector;
-import org.openjdk.jcstress.infra.collectors.InProcessCollector;
-import org.openjdk.jcstress.infra.collectors.MuxCollector;
-import org.openjdk.jcstress.infra.collectors.NetworkInputCollector;
-import org.openjdk.jcstress.infra.collectors.TestResult;
-import org.openjdk.jcstress.infra.collectors.TestResultCollector;
+import org.openjdk.jcstress.infra.collectors.*;
 import org.openjdk.jcstress.infra.grading.ConsoleReportPrinter;
 import org.openjdk.jcstress.infra.grading.ExceptionReportPrinter;
 import org.openjdk.jcstress.infra.grading.HTMLReportPrinter;
 import org.openjdk.jcstress.infra.runners.Runner;
+import org.openjdk.jcstress.infra.runners.TestConfig;
 import org.openjdk.jcstress.infra.runners.TestList;
+import org.openjdk.jcstress.link.BinaryLinkServer;
 import org.openjdk.jcstress.util.InputStreamDrainer;
 import org.openjdk.jcstress.vm.VMSupport;
 
@@ -48,13 +44,7 @@
 import java.io.PrintWriter;
 import java.lang.management.ManagementFactory;
 import java.lang.reflect.Constructor;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.concurrent.ExecutionException;
+import java.util.*;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
@@ -69,8 +59,6 @@
 public class JCStress {
     final ExecutorService pool;
     final PrintStream out;
-    volatile NetworkInputCollector networkCollector;
-    volatile Scheduler scheduler;
 
     public JCStress() {
         this.pool = Executors.newCachedThreadPool(new ThreadFactory() {
@@ -87,32 +75,55 @@
         out = System.out;
     }
 
+    class TestCfgTask implements Scheduler.ScheduledTask {
+        private final TestConfig cfg;
+        private final BinaryLinkServer server;
+        private final TestResultCollector sink;
+
+        public TestCfgTask(TestConfig cfg, BinaryLinkServer server, TestResultCollector sink) {
+            this.cfg = cfg;
+            this.server = server;
+            this.sink = sink;
+        }
+
+        @Override
+        public int getTokens() {
+            return cfg.threads;
+        }
+
+        @Override
+        public void run() {
+            switch (cfg.runMode) {
+                case EMBEDDED:
+                    runEmbedded(cfg, sink);
+                    break;
+                case FORKED:
+                    runForked(cfg, server, sink);
+                    break;
+            }
+        }
+    }
+
     public void run(Options opts) throws Exception {
-        SortedSet<String> tests = getTests(opts.getTestFilter());
-
         if (!opts.shouldParse()) {
             opts.printSettingsOn(out);
 
-            ConsoleReportPrinter printer = new ConsoleReportPrinter(opts, new PrintWriter(out, true), tests.size());
+            List<TestConfig> configs = prepareRunProgram(opts);
+
+            ConsoleReportPrinter printer = new ConsoleReportPrinter(opts, new PrintWriter(out, true), configs.size());
             DiskWriteCollector diskCollector = new DiskWriteCollector(opts.getResultFile());
             TestResultCollector sink = MuxCollector.of(printer, diskCollector);
 
-            networkCollector = new NetworkInputCollector(sink);
+            BinaryLinkServer server = new BinaryLinkServer(sink);
 
-            scheduler = new Scheduler(opts.getUserCPUs());
+            Scheduler scheduler = new Scheduler(opts.getUserCPUs());
+            for (TestConfig cfg : configs) {
+                server.addTask(cfg);
+                scheduler.schedule(new TestCfgTask(cfg, server, sink));
+            }
+            scheduler.waitFinish();
 
-            if (opts.shouldFork()) {
-                for (String test : tests) {
-                    for (int f = 0; f < opts.getForks(); f++) {
-                        runForked(opts, test, sink);
-                    }
-                }
-            } else {
-                run(opts, tests, false, sink);
-            }
-
-            scheduler.waitFinish();
-            networkCollector.terminate();
+            server.terminate();
 
             diskCollector.close();
         }
@@ -137,28 +148,46 @@
         out.println("Done.");
     }
 
-    void runForked(final Options opts, final String test, final TestResultCollector collector) {
-        try {
-            scheduler.schedule(new Scheduler.ScheduledTask() {
-                @Override
-                public int getTokens() {
-                    return TestList.getInfo(test).threads();
+    private List<TestConfig> prepareRunProgram(Options opts) {
+        SortedSet<String> tests = getTests(opts.getTestFilter());
+        List<TestConfig> configs = new ArrayList<>();
+        if (opts.shouldFork()) {
+            for (String test : tests) {
+                for (int f = 0; f < opts.getForks(); f++) {
+                    configs.add(new TestConfig(opts, TestList.getInfo(test), TestConfig.RunMode.FORKED));
                 }
-
-                @Override
-                public void run() {
-                    runForked0(opts, test, collector);
-                }
-            });
-        } catch (InterruptedException e) {
-            throw new IllegalStateException(e);
+            }
+        } else {
+            for (String test : tests) {
+                TestInfo info = TestList.getInfo(test);
+                TestConfig.RunMode mode = info.requiresFork() ? TestConfig.RunMode.FORKED : TestConfig.RunMode.EMBEDDED;
+                configs.add(new TestConfig(opts, info, mode));
+            }
         }
+        return configs;
     }
 
-    void runForked0(Options opts, String test, TestResultCollector collector) {
+    void runForked(TestConfig config, BinaryLinkServer server, TestResultCollector collector) {
         try {
-            List<String> commandString = getSeparateExecutionCommand(opts, test);
-            ProcessBuilder pb = new ProcessBuilder(commandString);
+            List<String> command = new ArrayList<>();
+
+            // basic Java line
+            command.addAll(VMSupport.getJavaInvokeLine());
+
+            // jvm args
+            command.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
+
+            String appendJvmArgs = config.appendJvmArgs;
+            if (appendJvmArgs.length() > 0) {
+                command.addAll(Arrays.asList(appendJvmArgs.split("\\s")));
+            }
+
+            command.add(ForkedMain.class.getName());
+
+            command.add(server.getHost());
+            command.add(String.valueOf(server.getPort()));
+
+            ProcessBuilder pb = new ProcessBuilder(command);
             Process p = pb.start();
 
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -176,83 +205,24 @@
 
             if (ecode != 0) {
                 // Test had failed, record this.
-                TestResult result = new TestResult(test, Status.VM_ERROR);
-                String s = new String(baos.toByteArray()).trim();
-                result.addAuxData(s);
+                TestResult result = new TestResult(config, Status.VM_ERROR);
+                result.addAuxData(new String(baos.toByteArray()).trim());
                 collector.add(result);
             }
-
         } catch (IOException | InterruptedException ex) {
             ex.printStackTrace();
         }
     }
 
-    public void run(Options opts, boolean alreadyForked, TestResultCollector collector) throws Exception {
-        run(opts, getTests(opts.getTestFilter()), alreadyForked, collector);
-    }
-
-    public void async(final Runner runner, final int threads) throws ExecutionException, InterruptedException {
-        if (scheduler == null) {
-            runner.run();
-            return;
+    public void runEmbedded(TestConfig config, TestResultCollector collector) {
+        try {
+            Class<?> aClass = Class.forName(config.generatedRunnerName);
+            Constructor<?> cnstr = aClass.getConstructor(TestConfig.class, TestResultCollector.class, ExecutorService.class);
+            Runner<?> o = (Runner<?>) cnstr.newInstance(config, collector, pool);
+            o.run();
+        } catch (Exception ex) {
+            throw new IllegalStateException("Should have been handled within the Runner");
         }
-
-        scheduler.schedule(new Scheduler.ScheduledTask() {
-            @Override
-            public int getTokens() {
-                return threads;
-            }
-
-            @Override
-            public void run() {
-                runner.run();
-            }
-        });
-    }
-
-    private void run(Options opts, Collection<String> tests, boolean alreadyForked, TestResultCollector collector) throws Exception {
-        for (String test : tests) {
-            TestInfo info = TestList.getInfo(test);
-            if (info.requiresFork() && !alreadyForked && !opts.shouldNeverFork()) {
-                runForked(opts, test, collector);
-            } else {
-                Class<?> aClass = Class.forName(info.generatedRunner());
-                Constructor<?> cnstr = aClass.getConstructor(Options.class, TestResultCollector.class, ExecutorService.class);
-                Runner<?> o = (Runner<?>) cnstr.newInstance(opts, collector, pool);
-                async(o, info.threads());
-            }
-        }
-    }
-
-    public List<String> getSeparateExecutionCommand(Options opts, String test) {
-        List<String> command = new ArrayList<>();
-
-        List<String> o = VMSupport.getJavaInvokeLine();
-        command.addAll(o);
-
-        // jvm args
-        command.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
-
-        String appendJvmArgs = opts.getAppendJvmArgs();
-        if (appendJvmArgs.length() > 0) {
-            command.addAll(Arrays.asList(appendJvmArgs.split("\\s")));
-        }
-
-        command.add(ForkedMain.class.getName());
-
-        // add jcstress options
-        command.addAll(opts.buildForkedCmdLine());
-
-        command.add("-t");
-        command.add(test);
-
-        command.add("--hostName");
-        command.add(networkCollector.getHost());
-
-        command.add("--hostPort");
-        command.add(String.valueOf(networkCollector.getPort()));
-
-        return command;
     }
 
     static SortedSet<String> getTests(final String filter) {
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/Options.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/Options.java	Fri May 13 00:41:21 2016 +0300
@@ -114,7 +114,7 @@
                 .withOptionalArg().ofType(Boolean.class).describedAs("bool");
 
         OptionSpec<Integer> forks = parser.accepts("f", "Should fork each test N times. \"0\" to run in the embedded mode " +
-                "with occasional forking, \"-1\" to never ever fork.")
+                "with occasional forking.")
                 .withOptionalArg().ofType(Integer.class).describedAs("count");
 
         OptionSpec<String> appendJvmArgs = parser.accepts("jvmArgs", "Append these JVM arguments for the forked runs.")
@@ -321,32 +321,6 @@
         }
     }
 
-    public Collection<String> buildForkedCmdLine() {
-        // omit -f, -p, -t
-        Collection<String> cmdLine = new ArrayList<>();
-        cmdLine.add("-r");
-        cmdLine.add(resultDir);
-        cmdLine.add("-minStride");
-        cmdLine.add(Integer.toString(minStride));
-        cmdLine.add("-maxStride");
-        cmdLine.add(Integer.toString(maxStride));
-        cmdLine.add("-time");
-        cmdLine.add(Integer.toString(time));
-        cmdLine.add("-iters");
-        cmdLine.add(Integer.toString(iters));
-        cmdLine.add("-yield");
-        cmdLine.add(Boolean.toString(shouldYield));
-        cmdLine.add("-c");
-        cmdLine.add(Integer.toString(userCPUs));
-        cmdLine.add("-sc");
-        cmdLine.add(Integer.toString(systemCPUs));
-        cmdLine.add("-f");
-        cmdLine.add("0");
-        if (verbose) cmdLine.add("-v");
-
-        return  cmdLine;
-    }
-
     public int getMinStride() {
         return minStride;
     }
@@ -383,10 +357,6 @@
         return forks > 0;
     }
 
-    public boolean shouldNeverFork() {
-        return forks < 0;
-    }
-
     public int getIterations() {
         return iters;
     }
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/collectors/NetworkInputCollector.java	Thu May 12 20:33:26 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,204 +0,0 @@
-/*
- * Copyright (c) 2005, 2014, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 org.openjdk.jcstress.infra.collectors;
-
-import org.openjdk.jcstress.infra.EndResult;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectStreamException;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Receives the test results over the network.
- *
- * @author Aleksey Shipilev (aleksey.shipilev@oracle.com)
- */
-public class NetworkInputCollector {
-
-    private final Acceptor acceptor;
-    private final List<Reader> registeredReaders;
-    private final TestResultCollector out;
-
-    public NetworkInputCollector(TestResultCollector out) throws IOException {
-        this.out = out;
-
-        registeredReaders = Collections.synchronizedList(new ArrayList<>());
-
-        acceptor = new Acceptor();
-        acceptor.start();
-    }
-
-    public void terminate() {
-        acceptor.close();
-
-        for (Reader r : registeredReaders) {
-            r.close();
-        }
-
-        try {
-            acceptor.join();
-            for (Reader r : registeredReaders) {
-                r.join();
-            }
-        } catch (InterruptedException e) {
-            // ignore
-        }
-    }
-
-    public void waitFinish() {
-        for (Iterator<Reader> iterator = registeredReaders.iterator(); iterator.hasNext(); ) {
-            Reader r = iterator.next();
-            try {
-                r.join();
-                iterator.remove();
-            } catch (InterruptedException e) {
-                // ignore
-            }
-        }
-    }
-
-    private final class Acceptor extends Thread {
-
-        private final ServerSocket server;
-
-        public Acceptor() throws IOException {
-            server = new ServerSocket(0);
-        }
-
-        @Override
-        public void run() {
-            try {
-                while (!Thread.interrupted()) {
-                    Socket clientSocket = server.accept();
-                    Reader r = new Reader(clientSocket);
-                    registeredReaders.add(r);
-                    r.start();
-                }
-            } catch (SocketException e) {
-                // assume this is "Socket closed", return
-            } catch (IOException e) {
-                throw new IllegalStateException(e);
-            } finally {
-                close();
-            }
-        }
-
-        public String getHost() {
-            try {
-                return InetAddress.getLocalHost().getHostAddress();
-            } catch (UnknownHostException e) {
-                throw new IllegalStateException("Unable to resolve local host", e);
-            }
-        }
-
-        public int getPort() {
-            return server.getLocalPort();
-        }
-
-        public void close() {
-            try {
-                server.close();
-            } catch (IOException e) {
-                // do nothing
-            }
-        }
-    }
-
-    public String getHost() {
-        return acceptor.getHost();
-    }
-
-    public int getPort() {
-        return acceptor.getPort();
-    }
-
-    private final class Reader extends Thread {
-        private final InputStream is;
-        private final Socket socket;
-        private ObjectInputStream ois;
-
-        public Reader(Socket socket) throws IOException {
-            this.socket = socket;
-            this.is = socket.getInputStream();
-        }
-
-        @Override
-        public void run() {
-            try {
-                // late OIS initialization, otherwise we'll block reading the header
-                ois = new ObjectInputStream(is);
-
-                Object obj;
-                while ((obj = ois.readObject()) != null) {
-                    if (obj instanceof EndResult) return;
-
-                    if (obj instanceof TestResult) {
-                        out.add((TestResult) obj);
-                    }
-                }
-            } catch (EOFException e) {
-                // expect
-            } catch (ClassNotFoundException | IOException e) {
-                throw new IllegalStateException(e);
-            } finally {
-                close();
-            }
-        }
-
-        public void close() {
-            try {
-                ois.close();
-            } catch (IOException e) {
-                // ignore
-            }
-
-            try {
-                is.close();
-            } catch (IOException e) {
-                // ignore
-            }
-
-            try {
-                socket.close();
-            } catch (IOException e) {
-                // ignore
-            }
-        }
-
-    }
-
-}
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/collectors/NetworkOutputCollector.java	Thu May 12 20:33:26 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2005, 2014, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 org.openjdk.jcstress.infra.collectors;
-
-import org.openjdk.jcstress.infra.EndResult;
-
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.net.Socket;
-
-/**
- * Sends the test results over the network.
- *
- * @author Aleksey Shipilev (aleksey.shipilev@oracle.com)
- */
-public class NetworkOutputCollector implements TestResultCollector {
-
-    private final ObjectOutputStream oos;
-    private final Socket clientSocket;
-
-    public NetworkOutputCollector(String hostName, int hostPort) throws IOException {
-        this.clientSocket = new Socket(hostName, hostPort);
-        this.oos = new ObjectOutputStream(clientSocket.getOutputStream());
-    }
-
-    @Override
-    public void add(TestResult result) {
-        try {
-            oos.writeObject(result);
-            oos.flush();
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    public void close() {
-        try {
-            oos.writeObject(new EndResult());
-            oos.flush();
-        } catch (IOException e) {
-            // do nothing
-        }
-
-        try {
-            oos.close();
-        } catch (IOException e) {
-            // do nothing
-        }
-
-        try {
-            clientSocket.close();
-        } catch (IOException e) {
-            // do nothing
-        }
-    }
-}
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/collectors/TestResult.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/collectors/TestResult.java	Fri May 13 00:41:21 2016 +0300
@@ -25,6 +25,7 @@
 package org.openjdk.jcstress.infra.collectors;
 
 import org.openjdk.jcstress.infra.Status;
+import org.openjdk.jcstress.infra.runners.TestConfig;
 import org.openjdk.jcstress.util.Environment;
 import org.openjdk.jcstress.util.HashMultiset;
 import org.openjdk.jcstress.util.Multiset;
@@ -43,15 +44,15 @@
     private static final String VM_ID = UUID.randomUUID().toString();
 
     private final String vmID;
-    private final String name;
+    private final TestConfig config;
     private final Multiset<String> states;
     private volatile Environment env;
     private final Status status;
     private final List<String> auxData;
 
-    public TestResult(String name, Status status) {
+    public TestResult(TestConfig config, Status status) {
         this.vmID = VM_ID;
-        this.name = name;
+        this.config = config;
         this.status = status;
         this.states = new HashMultiset<>();
         this.auxData = new ArrayList<>();
@@ -70,7 +71,7 @@
     }
 
     public String getName() {
-        return name;
+        return config.name;
     }
 
     public Environment getEnv() {
@@ -100,4 +101,8 @@
     public Collection<String> getStateKeys() {
         return states.keys();
     }
+
+    public TestConfig getConfig() {
+        return config;
+    }
 }
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/grading/ExceptionReportPrinter.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/grading/ExceptionReportPrinter.java	Fri May 13 00:41:21 2016 +0300
@@ -30,6 +30,7 @@
 import org.openjdk.jcstress.infra.TestInfo;
 import org.openjdk.jcstress.infra.collectors.InProcessCollector;
 import org.openjdk.jcstress.infra.collectors.TestResult;
+import org.openjdk.jcstress.infra.runners.TestConfig;
 import org.openjdk.jcstress.infra.runners.TestList;
 import org.openjdk.jcstress.util.*;
 
@@ -59,16 +60,16 @@
     }
 
     public void parse() throws FileNotFoundException, JAXBException {
-        Map<String, TestResult> results = new TreeMap<>();
+        Map<TestConfig, TestResult> results = new TreeMap<>();
 
         {
-            Multimap<String, TestResult> multiResults = new HashMultimap<>();
+            Multimap<TestConfig, TestResult> multiResults = new HashMultimap<>();
             for (TestResult r : collector.getTestResults()) {
-                multiResults.put(r.getName(), r);
+                multiResults.put(r.getConfig(), r);
             }
 
-            for (String name : multiResults.keys()) {
-                Collection<TestResult> mergeable = multiResults.get(name);
+            for (TestConfig config : multiResults.keys()) {
+                Collection<TestResult> mergeable = multiResults.get(config);
 
                 Multiset<String> stateCounts = new HashMultiset<>();
 
@@ -80,28 +81,29 @@
                     }
                 }
 
-                TestResult root = new TestResult(name, status);
+                TestResult root = new TestResult(config, status);
 
                 for (String s : stateCounts.keys()) {
                     root.addState(s, stateCounts.count(s));
                 }
 
-                results.put(name, root);
+                results.put(config, root);
             }
         }
 
         // build prefixes
-        Multimap<String, String> packages = new TreeMultimap<>();
-        for (String k : results.keySet()) {
-            String pack = k.substring(0, k.lastIndexOf("."));
+        Multimap<String, TestConfig> packages = new TreeMultimap<>();
+        for (TestConfig k : results.keySet()) {
+            String name = k.name;
+            String pack = name.substring(0, name.lastIndexOf("."));
             packages.put(pack, k);
         }
 
         for (String k : packages.keys()) {
-            Collection<String> testNames = packages.get(k);
-            for (String testName : testNames) {
-                TestInfo test = TestList.getInfo(testName);
-                TestResult result = results.get(testName);
+            Collection<TestConfig> rs = packages.get(k);
+            for (TestConfig r : rs) {
+                TestInfo test = TestList.getInfo(r.name);
+                TestResult result = results.get(r);
                 emitTest(result, test);
             }
         }
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/grading/HTMLReportPrinter.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/grading/HTMLReportPrinter.java	Fri May 13 00:41:21 2016 +0300
@@ -32,6 +32,7 @@
 import org.openjdk.jcstress.infra.TestInfo;
 import org.openjdk.jcstress.infra.collectors.InProcessCollector;
 import org.openjdk.jcstress.infra.collectors.TestResult;
+import org.openjdk.jcstress.infra.runners.TestConfig;
 import org.openjdk.jcstress.infra.runners.TestList;
 import org.openjdk.jcstress.util.*;
 
@@ -71,15 +72,15 @@
 
     public void parse() throws FileNotFoundException, JAXBException {
 
-        Map<String, TestResult> results = new TreeMap<>();
+        Map<TestConfig, TestResult> results = new TreeMap<>();
 
         {
-            Multimap<String, TestResult> multiResults = new HashMultimap<>();
+            Multimap<TestConfig, TestResult> multiResults = new HashMultimap<>();
             for (TestResult r : collector.getTestResults()) {
-                multiResults.put(r.getName(), r);
+                multiResults.put(r.getConfig(), r);
             }
 
-            for (String name : multiResults.keys()) {
+            for (TestConfig name : multiResults.keys()) {
                 Collection<TestResult> mergeable = multiResults.get(name);
 
                 Multiset<String> stateCounts = new HashMultiset<>();
@@ -114,9 +115,9 @@
         }
 
         // build prefixes
-        Multimap<String, String> packages = new TreeMultimap<>();
-        for (String k : results.keySet()) {
-            String pack = k.substring(0, k.lastIndexOf("."));
+        Multimap<String, TestConfig> packages = new TreeMultimap<>();
+        for (TestConfig k : results.keySet()) {
+            String pack = k.name.substring(0, k.name.lastIndexOf("."));
             packages.put(pack, k);
         }
 
@@ -155,10 +156,10 @@
             int failedCount = 0;
             int sanityFailedCount = 0;
             for (String k : packages.keys()) {
-                Collection<String> testNames = packages.get(k);
-                for (String testName : testNames) {
-                    TestInfo test = TestList.getInfo(testName);
-                    TestResult result = results.get(testName);
+                Collection<TestConfig> testNames = packages.get(k);
+                for (TestConfig cfg : testNames) {
+                    TestInfo test = TestList.getInfo(cfg.name);
+                    TestResult result = results.get(cfg);
                     if (result.status() == Status.NORMAL) {
                         if (new TestGrading(result, test).isPassed) {
                             passedCount++;
@@ -208,8 +209,8 @@
         {
             SortedMap<String, String> env = new TreeMap<>();
             for (String k : packages.keys()) {
-                for (String testName : packages.get(k)) {
-                    TestResult result = results.get(testName);
+                for (TestConfig cfg : packages.get(k)) {
+                    TestResult result = results.get(cfg);
                     if (result != null) {
                         for (Map.Entry<String, String> kv : result.getEnv().entries().entrySet()) {
                             String key = kv.getKey();
@@ -259,26 +260,26 @@
         output.close();
 
         if (verbose) {
-            for (String k : results.keySet()) {
+            for (TestConfig k : results.keySet()) {
                 TestResult result = results.get(k);
                 printer.add(result);
             }
         }
     }
 
-    private void printFailedTests(Map<String, TestResult> results, Multimap<String, String> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
+    private void printFailedTests(Map<TestConfig, TestResult> results, Multimap<String, TestConfig> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
         output.println("<h3>FAILED tests:<br>");
         output.println("&nbsp;Some asserts have been violated.<br>&nbsp;Correct implementations should have none.</h3>");
         output.println("<table cellspacing=0 cellpadding=0 width=\"100%\">");
 
         boolean hadAnyTests = false;
         for (String k : packages.keys()) {
-            Collection<String> testNames = packages.get(k);
+            Collection<TestConfig> testNames = packages.get(k);
 
             boolean packageEmitted = false;
-            for (String testName : testNames) {
-                TestInfo test = TestList.getInfo(testName);
-                TestResult result = results.get(testName);
+            for (TestConfig cfg : testNames) {
+                TestInfo test = TestList.getInfo(cfg.name);
+                TestResult result = results.get(cfg);
                 TestGrading grading = new TestGrading(result, test);
                 if (result.status() == Status.NORMAL && !grading.isPassed) {
                     if (!packageEmitted) {
@@ -301,19 +302,18 @@
     }
 
 
-    private void printErrorTests(Map<String, TestResult> results, Multimap<String, String> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
+    private void printErrorTests(Map<TestConfig, TestResult> results, Multimap<String, TestConfig> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
         output.println("<h3>ERROR tests:<br>");
         output.println("&nbsp;Tests break for some reason, other than failing the assert.<br>&nbsp;Correct implementations should have none.</h3>");
         output.println("<table cellspacing=0 cellpadding=0 width=\"100%\">");
 
         boolean hadAnyTests = false;
         for (String k : packages.keys()) {
-            Collection<String> testNames = packages.get(k);
 
             boolean packageEmitted = false;
-            for (String testName : testNames) {
-                TestInfo test = TestList.getInfo(testName);
-                TestResult result = results.get(testName);
+            for (TestConfig cfg : packages.get(k)) {
+                TestInfo test = TestList.getInfo(cfg.name);
+                TestResult result = results.get(cfg);
                 if (result.status() != Status.NORMAL && result.status() != Status.API_MISMATCH) {
                     if (!packageEmitted) {
                         emitPackage(output, k);
@@ -334,19 +334,18 @@
         output.println("<br>");
     }
 
-    private void printInterestingTests(Map<String, TestResult> results, Multimap<String, String> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
+    private void printInterestingTests(Map<TestConfig, TestResult> results, Multimap<String, TestConfig> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
         output.println("<h3>INTERESTING tests:<br>");
         output.println("&nbsp;Some interesting behaviors observed.<br>&nbsp;This is for the plain curiosity.</h3>");
         output.println("<table cellspacing=0 cellpadding=0 width=\"100%\">");
 
         boolean hadAnyTests = false;
         for (String k : packages.keys()) {
-            Collection<String> testNames = packages.get(k);
 
             boolean packageEmitted = false;
-            for (String testName : testNames) {
-                TestInfo test = TestList.getInfo(testName);
-                TestResult result = results.get(testName);
+            for (TestConfig cfg : packages.get(k)) {
+                TestInfo test = TestList.getInfo(cfg.name);
+                TestResult result = results.get(cfg);
                 TestGrading grading = new TestGrading(result, test);
                 if (grading.hasInteresting) {
                     if (!packageEmitted) {
@@ -367,19 +366,18 @@
         output.println("<br>");
     }
 
-    private void printSpecTests(Map<String, TestResult> results, Multimap<String, String> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
+    private void printSpecTests(Map<TestConfig, TestResult> results, Multimap<String, TestConfig> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
         output.println("<h3>SPEC tests:<br>");
         output.println("&nbsp;Formally acceptable, but surprising results are observed.<br>&nbsp;Implementations going beyond the minimal requirements should have none.</h3>");
         output.println("<table cellspacing=0 cellpadding=0 width=\"100%\">");
 
         boolean hadAnyTests = false;
         for (String k : packages.keys()) {
-            Collection<String> testNames = packages.get(k);
 
             boolean packageEmitted = false;
-            for (String testName : testNames) {
-                TestInfo test = TestList.getInfo(testName);
-                TestResult result = results.get(testName);
+            for (TestConfig cfg : packages.get(k)) {
+                TestInfo test = TestList.getInfo(cfg.name);
+                TestResult result = results.get(cfg);
                 TestGrading grading = new TestGrading(result, test);
                 if (grading.hasSpec) {
                     if (!packageEmitted) {
@@ -401,7 +399,7 @@
         output.println("<br>");
     }
 
-    private void printAllTests(Map<String, TestResult> results, Multimap<String, String> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
+    private void printAllTests(Map<TestConfig, TestResult> results, Multimap<String, TestConfig> packages, PrintWriter output) throws FileNotFoundException, JAXBException {
         output.println("<h3>ALL tests:</h3>");
         output.println("<table cellspacing=0 cellpadding=0 width=\"100%\">\n" +
                 "<tr>\n" +
@@ -413,17 +411,16 @@
         for (String k : packages.keys()) {
             emitPackage(output, k);
 
-            Collection<String> testNames = packages.get(k);
-            for (String testName : testNames) {
-                TestInfo test = TestList.getInfo(testName);
-                TestResult result = results.get(testName);
+            for (TestConfig cfg : packages.get(k)) {
+                TestInfo test = TestList.getInfo(cfg.name);
+                TestResult result = results.get(cfg);
                 if (result.status() == Status.NORMAL) {
                     emitTest(output, result, test);
                 } else {
                     emitTestFailure(output, result, test);
                 }
 
-                PrintWriter local = new PrintWriter(resultDir + "/" + testName + ".html");
+                PrintWriter local = new PrintWriter(resultDir + "/" + cfg + ".html");
                 parseTest(local, result, test);
                 local.close();
             }
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/processors/JCStressTestProcessor.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/processors/JCStressTestProcessor.java	Fri May 13 00:41:21 2016 +0300
@@ -24,13 +24,9 @@
  */
 package org.openjdk.jcstress.infra.processors;
 
-import org.openjdk.jcstress.Options;
 import org.openjdk.jcstress.annotations.*;
 import org.openjdk.jcstress.infra.collectors.TestResultCollector;
-import org.openjdk.jcstress.infra.runners.Control;
-import org.openjdk.jcstress.infra.runners.Runner;
-import org.openjdk.jcstress.infra.runners.StateHolder;
-import org.openjdk.jcstress.infra.runners.TestList;
+import org.openjdk.jcstress.infra.runners.*;
 import org.openjdk.jcstress.util.*;
 import org.openjdk.jcstress.vm.WhiteBoxSupport;
 
@@ -300,8 +296,8 @@
         pw.println("    volatile int epoch;");
         pw.println();
 
-        pw.println("    public " + className + "(Options opts, TestResultCollector collector, ExecutorService pool) {");
-        pw.println("        super(opts, collector, pool, \"" + getQualifiedName(info.getTest()) + "\");");
+        pw.println("    public " + className + "(TestConfig config, TestResultCollector collector, ExecutorService pool) {");
+        pw.println("        super(config, collector, pool, \"" + getQualifiedName(info.getTest()) + "\");");
         pw.println("    }");
         pw.println();
 
@@ -356,7 +352,7 @@
 
         pw.println();
         pw.println("        try {");
-        pw.println("            TimeUnit.MILLISECONDS.sleep(control.time);");
+        pw.println("            TimeUnit.MILLISECONDS.sleep(config.time);");
         pw.println("        } catch (InterruptedException e) {");
         pw.println("        }");
         pw.println();
@@ -405,7 +401,7 @@
         pw.println("        Pair[] pairs = holder.pairs;");
         pw.println("        int len = pairs.length;");
         pw.println();
-        pw.println("        int newLen = holder.hasLaggedWorkers ? Math.max(control.minStride, Math.min(len * 2, control.maxStride)) : len;");
+        pw.println("        int newLen = holder.hasLaggedWorkers ? Math.max(config.minStride, Math.min(len * 2, config.maxStride)) : len;");
         pw.println();
         pw.println("        Pair[] newPairs = pairs;");
         pw.println("        if (newLen > len) {");
@@ -430,7 +426,7 @@
             if (!isStateItself) {
                 pw.println("        " + t + " lt = test;");
             }
-            pw.println("        boolean yield = control.shouldYield;");
+            pw.println("        boolean yield = config.shouldYield;");
             pw.println();
             pw.println("        while (true) {");
             pw.println("            StateHolder<Pair> holder = version;");
@@ -543,8 +539,8 @@
         pw.println("public class " + generatedName + " extends Runner<" + generatedName + ".Outcome> {");
         pw.println();
 
-        pw.println("    public " + generatedName + "(Options opts, TestResultCollector collector, ExecutorService pool) {");
-        pw.println("        super(opts, collector, pool, \"" + getQualifiedName(info.getTest()) + "\");");
+        pw.println("    public " + generatedName + "(TestConfig config, TestResultCollector collector, ExecutorService pool) {");
+        pw.println("        super(config, collector, pool, \"" + getQualifiedName(info.getTest()) + "\");");
         pw.println("    }");
         pw.println();
 
@@ -555,9 +551,9 @@
         pw.println("        Counter<Outcome> results = new OpenAddressHashCounter<>();");
         pw.println();
         pw.println("        testLog.print(\"Iterations \");");
-        pw.println("        for (int c = 0; c < control.iters; c++) {");
+        pw.println("        for (int c = 0; c < config.iters; c++) {");
         pw.println("            try {");
-        pw.println("                WhiteBoxSupport.tryDeopt(control.deoptRatio);");
+        pw.println("                WhiteBoxSupport.tryDeopt(config.deoptRatio);");
         pw.println("            } catch (NoClassDefFoundError err) {");
         pw.println("                // gracefully \"handle\"");
         pw.println("            }");
@@ -589,7 +585,7 @@
         pw.println("    }");
         pw.println();
         pw.println("    private void run(Counter<Outcome> results) {");
-        pw.println("        long target = System.currentTimeMillis() + control.time;");
+        pw.println("        long target = System.currentTimeMillis() + config.time;");
         pw.println("        while (System.currentTimeMillis() < target) {");
         pw.println();
 
@@ -722,7 +718,7 @@
                 ArrayList.class, Arrays.class, Collection.class,
                 Callable.class, ExecutorService.class, Future.class, TimeUnit.class,
                 AtomicIntegerFieldUpdater.class,
-                Options.class, TestResultCollector.class,
+                TestConfig.class, TestResultCollector.class,
                 Control.class, Runner.class, StateHolder.class,
                 ArrayUtils.class, Counter.class,
                 WhiteBoxSupport.class, OpenAddressHashCounter.class, ExecutionException.class
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/Control.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/Control.java	Fri May 13 00:41:21 2016 +0300
@@ -31,21 +31,4 @@
  */
 public class Control {
     public volatile boolean isStopped;
-    public final boolean shouldYield;
-    public final boolean verbose;
-    public final int minStride;
-    public final int maxStride;
-    public final int time;
-    public final int iters;
-    public final int deoptRatio;
-
-    public Control(Options opts) {
-        time = opts.getTime();
-        minStride = opts.getMinStride();
-        maxStride = opts.getMaxStride();
-        iters = opts.getIterations();
-        shouldYield = opts.shouldYield();
-        verbose = opts.isVerbose();
-        deoptRatio = opts.deoptRatio();
-    }
 }
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/Runner.java	Thu May 12 20:33:26 2016 +0300
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/Runner.java	Fri May 13 00:41:21 2016 +0300
@@ -24,7 +24,6 @@
  */
 package org.openjdk.jcstress.infra.runners;
 
-import org.openjdk.jcstress.Options;
 import org.openjdk.jcstress.infra.Status;
 import org.openjdk.jcstress.infra.collectors.TestResult;
 import org.openjdk.jcstress.infra.collectors.TestResultCollector;
@@ -52,14 +51,16 @@
     protected final ExecutorService pool;
     protected final PrintWriter testLog;
     protected final String testName;
+    protected final TestConfig config;
 
-    public Runner(Options opts, TestResultCollector collector, ExecutorService pool, String testName) {
+    public Runner(TestConfig config, TestResultCollector collector, ExecutorService pool, String testName) {
         this.collector = collector;
         this.pool = pool;
         this.testName = testName;
-        this.control = new Control(opts);
+        this.control = new Control();
+        this.config = config;
 
-        if (control.verbose) {
+        if (config.verbose) {
             testLog = new PrintWriter(System.out, true);
         } else {
             testLog = new PrintWriter(new NullOutputStream(), true);
@@ -88,9 +89,9 @@
         }
 
         testLog.print("Iterations ");
-        for (int c = 0; c < control.iters; c++) {
+        for (int c = 0; c < config.iters; c++) {
             try {
-                WhiteBoxSupport.tryDeopt(control.deoptRatio);
+                WhiteBoxSupport.tryDeopt(config.deoptRatio);
             } catch (NoClassDefFoundError err) {
                 // gracefully "handle"
             }
@@ -104,12 +105,12 @@
 
 
     protected void dumpFailure(String testName, Status status) {
-        TestResult result = new TestResult(testName, status);
+        TestResult result = new TestResult(config, status);
         collector.add(result);
     }
 
     protected void dumpFailure(String testName, Status status, Throwable aux) {
-        TestResult result = new TestResult(testName, status);
+        TestResult result = new TestResult(config, status);
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
         aux.printStackTrace(pw);
@@ -119,7 +120,7 @@
     }
 
     protected void dump(String testName, Counter<R> results) {
-        TestResult result = new TestResult(testName, Status.NORMAL);
+        TestResult result = new TestResult(config, Status.NORMAL);
 
         for (R e : results.elementSet()) {
             result.addState(String.valueOf(e), results.count(e));
@@ -150,7 +151,7 @@
                 }
             }
 
-            if (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) > Math.max(control.time, 60*1000)) {
+            if (TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) > Math.max(config.time, 60*1000)) {
                 dumpFailure(testName, Status.TIMEOUT_ERROR);
                 return;
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java	Fri May 13 00:41:21 2016 +0300
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2014, 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 org.openjdk.jcstress.infra.runners;
+
+import org.openjdk.jcstress.Options;
+import org.openjdk.jcstress.infra.TestInfo;
+
+import java.io.Serializable;
+
+public class TestConfig implements Serializable, Comparable<TestConfig> {
+
+    public final boolean shouldYield;
+    public final boolean verbose;
+    public final int minStride;
+    public final int maxStride;
+    public final int time;
+    public final int iters;
+    public final int deoptRatio;
+    public final int threads;
+    public final String name;
+    public final String generatedRunnerName;
+    public final String appendJvmArgs;
+    public final RunMode runMode;
+
+    public enum RunMode {
+        EMBEDDED,
+        FORKED,
+    }
+
+    public TestConfig(Options opts, TestInfo info, RunMode runMode) {
+        this.runMode = runMode;
+        time = opts.getTime();
+        minStride = opts.getMinStride();
+        maxStride = opts.getMaxStride();
+        iters = opts.getIterations();
+        shouldYield = opts.shouldYield();
+        verbose = opts.isVerbose();
+        deoptRatio = opts.deoptRatio();
+        threads = info.threads();
+        name = info.name();
+        appendJvmArgs = opts.getAppendJvmArgs();
+        generatedRunnerName = info.generatedRunner();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        TestConfig that = (TestConfig) o;
+
+        if (shouldYield != that.shouldYield) return false;
+        if (verbose != that.verbose) return false;
+        if (minStride != that.minStride) return false;
+        if (maxStride != that.maxStride) return false;
+        if (time != that.time) return false;
+        if (iters != that.iters) return false;
+        if (deoptRatio != that.deoptRatio) return false;
+        if (threads != that.threads) return false;
+        if (!name.equals(that.name)) return false;
+        if (!generatedRunnerName.equals(that.generatedRunnerName)) return false;
+        return appendJvmArgs.equals(that.appendJvmArgs);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (shouldYield ? 1 : 0);
+        result = 31 * result + (verbose ? 1 : 0);
+        result = 31 * result + minStride;
+        result = 31 * result + maxStride;
+        result = 31 * result + time;
+        result = 31 * result + iters;
+        result = 31 * result + deoptRatio;
+        result = 31 * result + threads;
+        result = 31 * result + name.hashCode();
+        result = 31 * result + generatedRunnerName.hashCode();
+        result = 31 * result + appendJvmArgs.hashCode();
+        return result;
+    }
+
+    @Override
+    public int compareTo(TestConfig o) {
+        return name.compareTo(o.name);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/link/BinaryLinkClient.java	Fri May 13 00:41:21 2016 +0300
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2005, 2013, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 org.openjdk.jcstress.link;
+
+import org.openjdk.jcstress.infra.collectors.TestResult;
+import org.openjdk.jcstress.infra.collectors.TestResultCollector;
+import org.openjdk.jcstress.infra.runners.TestConfig;
+import org.openjdk.jcstress.util.FileUtils;
+
+import java.io.*;
+import java.net.Socket;
+
+public final class BinaryLinkClient implements TestResultCollector {
+
+    private static final int RESET_EACH = Integer.getInteger("jcstress.link.resetEach", 100);
+    private static final int BUFFER_SIZE = Integer.getInteger("jcstress.link.bufferSize", 64*1024);
+
+    private final Object lock;
+
+    private final Socket clientSocket;
+    private final ObjectOutputStream oos;
+    private final ObjectInputStream ois;
+    private volatile boolean failed;
+    private int resetToGo;
+
+    public BinaryLinkClient(String hostName, int hostPort) throws IOException {
+        this.lock = new Object();
+        this.clientSocket = new Socket(hostName, hostPort);
+
+        // Initialize the OOS first, and flush, letting the other party read the stream header.
+        this.oos = new ObjectOutputStream(new BufferedOutputStream(clientSocket.getOutputStream(), BUFFER_SIZE));
+        this.oos.flush();
+
+        this.ois = new ObjectInputStream(new BufferedInputStream(clientSocket.getInputStream(), BUFFER_SIZE));
+    }
+
+    private void pushFrame(Serializable frame) throws IOException {
+        if (failed) {
+            throw new IOException("Link had failed already");
+        }
+
+        // It is important to reset the OOS to avoid garbage buildup in internal identity
+        // tables. However, we cannot do that after each frame since the huge referenced
+        // objects like benchmark and iteration parameters will be duplicated on the receiver
+        // side. This is why we reset only each RESET_EACH frames.
+        //
+        // It is as much as important to flush the stream to let the other party know we
+        // pushed something out.
+
+        synchronized (lock) {
+            try {
+                if (resetToGo-- < 0) {
+                    oos.reset();
+                    resetToGo = RESET_EACH;
+                }
+
+                oos.writeObject(frame);
+                oos.flush();
+            } catch (IOException e) {
+                failed = true;
+                throw e;
+            }
+        }
+    }
+
+    private Object readFrame() throws IOException, ClassNotFoundException {
+        try {
+            return ois.readObject();
+        } catch (ClassNotFoundException ex) {
+            failed = true;
+            throw ex;
+        } catch (IOException ex) {
+            failed = true;
+            throw ex;
+        }
+    }
+
+    public void close() throws IOException {
+        synchronized (lock) {
+            oos.writeObject(new FinishingFrame());
+            FileUtils.safelyClose(ois);
+            FileUtils.safelyClose(oos);
+            clientSocket.close();
+        }
+    }
+
+    public TestConfig nextJob() throws IOException, ClassNotFoundException {
+        synchronized (lock) {
+            pushFrame(new JobRequestFrame());
+
+            Object reply = readFrame();
+            if (reply instanceof JobResponseFrame) {
+                return ((JobResponseFrame) reply).getConfig();
+            } else {
+                throw new IllegalStateException("Got the erroneous reply: " + reply);
+            }
+        }
+    }
+
+    @Override
+    public void add(TestResult result) {
+        try {
+            pushFrame(new ResultsFrame(result));
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/link/BinaryLinkServer.java	Fri May 13 00:41:21 2016 +0300
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2005, 2013, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 org.openjdk.jcstress.link;
+
+import org.openjdk.jcstress.infra.collectors.TestResultCollector;
+import org.openjdk.jcstress.infra.runners.TestConfig;
+
+import java.io.*;
+import java.net.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+/**
+ * Accepts the binary data from the forked VM and pushes it to parent VM
+ * as appropriate. This server assumes there is only the one and only
+ * client at any given point of time.
+ */
+public final class BinaryLinkServer {
+
+    private static final int BUFFER_SIZE = Integer.getInteger("jcstress.link.bufferSize", 64*1024);
+
+    private final Acceptor acceptor;
+    private final Collection<Handler> handlers;
+    private final TestResultCollector out;
+    private final Queue<TestConfig> configs;
+
+    public BinaryLinkServer(TestResultCollector out) throws IOException {
+        this.out = out;
+        this.configs = new ConcurrentLinkedDeque<>();
+        acceptor = new Acceptor();
+        acceptor.start();
+
+        handlers = Collections.synchronizedCollection(new ArrayList<>());
+    }
+
+    public void terminate() {
+        acceptor.close();
+
+        synchronized (handlers) {
+            for (Handler h : handlers) {
+                h.close();
+            }
+        }
+
+        try {
+            acceptor.join();
+            synchronized (handlers) {
+                for (Handler h : handlers) {
+                    h.join();
+                }
+            }
+        } catch (InterruptedException e) {
+            // ignore
+        }
+    }
+
+    private InetAddress getListenAddress() {
+        // Try to use user-provided override first.
+        String addr = System.getProperty("jcstress.link.address");
+        if (addr != null) {
+            try {
+                return InetAddress.getByName(addr);
+            } catch (UnknownHostException e) {
+                // override failed, notify user
+                throw new IllegalStateException("Can not initialize binary link.", e);
+            }
+        }
+
+        return InetAddress.getLoopbackAddress();
+    }
+
+    private int getListenPort() {
+        return Integer.getInteger("jmh.link.port", 0);
+    }
+
+    public void addTask(TestConfig cfg) {
+        configs.add(cfg);
+    }
+
+    private final class Acceptor extends Thread {
+
+        private final ServerSocket server;
+        private final InetAddress listenAddress;
+
+        public Acceptor() throws IOException {
+            listenAddress = getListenAddress();
+            server = new ServerSocket(getListenPort(), 50, listenAddress);
+        }
+
+        @Override
+        public void run() {
+            try {
+                while (!Thread.interrupted()) {
+                    Socket clientSocket = server.accept();
+                    Handler r = new Handler(clientSocket);
+                    handlers.add(r);
+                    r.start();
+                }
+            } catch (SocketException e) {
+                // assume this is "Socket closed", return
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
+            } finally {
+                close();
+            }
+        }
+
+        public String getHost() {
+            return listenAddress.getHostAddress();
+        }
+
+        public int getPort() {
+            // Poll the actual listen port, in case it is ephemeral
+            return server.getLocalPort();
+        }
+
+        public void close() {
+            try {
+                server.close();
+            } catch (IOException e) {
+                // do nothing
+            }
+        }
+    }
+
+    public String getHost() {
+        return acceptor.getHost();
+    }
+
+    public int getPort() {
+        return acceptor.getPort();
+    }
+
+    private final class Handler extends Thread {
+        private final InputStream is;
+        private final Socket socket;
+        private ObjectInputStream ois;
+        private final OutputStream os;
+        private ObjectOutputStream oos;
+
+        public Handler(Socket socket) throws IOException {
+            this.socket = socket;
+            this.is = socket.getInputStream();
+            this.os = socket.getOutputStream();
+
+            // eager OOS initialization, let the other party read the stream header
+            oos = new ObjectOutputStream(new BufferedOutputStream(os, BUFFER_SIZE));
+            oos.flush();
+        }
+
+        @Override
+        public void run() {
+            try {
+                // late OIS initialization, otherwise we'll block reading the header
+                ois = new ObjectInputStream(new BufferedInputStream(is, BUFFER_SIZE));
+
+                Object obj;
+                while ((obj = ois.readObject()) != null) {
+
+                    if (obj instanceof JobRequestFrame) {
+                        handleHandshake((JobRequestFrame) obj);
+                    }
+                    if (obj instanceof ResultsFrame) {
+                        handleResults((ResultsFrame) obj);
+                    }
+                    if (obj instanceof FinishingFrame) {
+                        // close the streams
+                        break;
+                    }
+                }
+            } catch (EOFException e) {
+                // ignore
+            } catch (Exception e) {
+                System.out.println("<binary link had failed, forked VM corrupted the stream?");
+                e.printStackTrace(System.out);
+            } finally {
+                close();
+            }
+        }
+
+        private void handleResults(ResultsFrame obj) {
+            out.add(obj.getRes());
+        }
+
+        private void handleHandshake(JobRequestFrame obj) throws IOException {
+            TestConfig poll = configs.poll();
+            if (poll == null) {
+                throw new IllegalStateException("No jobs left, this should not happen");
+            }
+            oos.writeObject(new JobResponseFrame(poll));
+            oos.flush();
+        }
+
+        public void close() {
+            try {
+                socket.close();
+            } catch (IOException e) {
+                // ignore
+            }
+            BinaryLinkServer.this.handlers.remove(this);
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/link/FinishingFrame.java	Fri May 13 00:41:21 2016 +0300
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2005, 2013, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 org.openjdk.jcstress.link;
+
+import java.io.Serializable;
+
+class FinishingFrame implements Serializable {
+    private static final long serialVersionUID = 2309975914801631608L;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/link/JobRequestFrame.java	Fri May 13 00:41:21 2016 +0300
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2005, 2013, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 org.openjdk.jcstress.link;
+
+import java.io.Serializable;
+
+class JobRequestFrame implements Serializable {
+    private static final long serialVersionUID = 2082214387637725282L;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/link/JobResponseFrame.java	Fri May 13 00:41:21 2016 +0300
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2005, 2013, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 org.openjdk.jcstress.link;
+
+import org.openjdk.jcstress.infra.runners.TestConfig;
+
+import java.io.Serializable;
+
+class JobResponseFrame implements Serializable {
+    private static final long serialVersionUID = 2082214387637725282L;
+
+    private final TestConfig config;
+
+    public JobResponseFrame(TestConfig config) {
+        this.config = config;
+    }
+
+    public TestConfig getConfig() {
+        return config;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/link/ResultsFrame.java	Fri May 13 00:41:21 2016 +0300
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2005, 2013, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 org.openjdk.jcstress.link;
+
+import org.openjdk.jcstress.infra.collectors.TestResult;
+
+import java.io.Serializable;
+
+class ResultsFrame implements Serializable {
+    private static final long serialVersionUID = -5627086531281515824L;
+
+    private final TestResult res;
+
+    public ResultsFrame(TestResult res) {
+        this.res = res;
+    }
+
+    public TestResult getRes() {
+        return res;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/util/FileUtils.java	Fri May 13 00:41:21 2016 +0300
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2014, 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 org.openjdk.jcstress.util;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+public class FileUtils {
+
+    public static <T extends Flushable & Closeable> void safelyClose(T obj) {
+        if (obj != null) {
+            try {
+                obj.flush();
+            } catch (IOException e) {
+                // ignore
+            }
+            try {
+                obj.close();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+    }
+
+    public static <T extends Closeable> void safelyClose(T obj) {
+        if (obj != null) {
+            try {
+                obj.close();
+            } catch (IOException e) {
+                // do nothing
+            }
+        }
+    }
+
+
+}