--- old/test/lib/sun/hotspot/WhiteBox.java 2017-03-09 23:06:50.623218920 -0500 +++ new/test/lib/sun/hotspot/WhiteBox.java 2017-03-09 23:06:50.547215250 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2017, 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 @@ -390,6 +390,39 @@ // Force Full GC public native void fullGC(); + // Returns true if the current GC supports control of its concurrent + // phase via requestConcurrentGCPhase(). If false, a request will + // always fail. + public native boolean supportsConcurrentGCPhaseControl(); + + // Returns an array of concurrent phase names provided by this + // collector. These are the names recognized by + // requestConcurrentGCPhase(). + public native String[] getConcurrentGCPhases(); + + // Attempt to put the collector into the indicated concurrent phase, + // and attempt to remain in that state until a new request is made. + // + // Returns immediately if already in the requested phase. + // Otherwise, waits until the phase is reached. + // + // Throws IllegalStateException if unsupported by the current collector. + // Throws NullPointerException if phase is null. + // Throws IllegalArgumentException if phase is not valid for the current collector. + public void requestConcurrentGCPhase(String phase) { + if (!supportsConcurrentGCPhaseControl()) { + throw new IllegalStateException("Concurrent GC phase control not supported"); + } else if (phase == null) { + throw new NullPointerException("null phase"); + } else if (!requestConcurrentGCPhase0(phase)) { + throw new IllegalArgumentException("Unknown concurrent GC phase: " + phase); + } + } + + // Helper for requestConcurrentGCPhase(). Returns true if request + // succeeded, false if the phase is invalid. + private native boolean requestConcurrentGCPhase0(String phase); + // Method tries to start concurrent mark cycle. // It returns false if CM Thread is always in concurrent cycle. public native boolean g1StartConcMarkCycle(); --- /dev/null 2017-03-08 17:39:57.499858527 -0500 +++ new/test/lib/jdk/test/lib/gc/ConcurrentPhaseControlUtil.java 2017-03-09 23:06:51.011237608 -0500 @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2017, 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. + */ + +// Support library for TestConcurrentPhaseControl variants. +// +// To use: +// +// (1) The main test class provides a subclass for stepping through +// the phases. That phase stepper class has a main function which +// constructs and runs an instance of this support class's Executor. +// +// (2) The main test class has a main function which constructs and +// runs an instance of the support class. The first argument is the +// name of the main class's phase stepper class. +// +// (3) The test program must provide access to WhiteBox, as it is used +// by this support class. +// +// (4) The test program should be invoked as a driver. The support +// class's run() function will run the phase stepper in a subprocess, +// in order to capture it's output for analysis. + +package jdk.test.lib.gc; + +import sun.hotspot.WhiteBox; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public final class ConcurrentPhaseControlUtil { + // The name of the class to "invoke" in the subprocess. + private final String phaseStepperName; + + // An array of pairs of strings. Each pair is a phase name + // and a regex pattern for recognizing the associated log message. + // The regex pattern can be null if no log message is associated + // with the named phase. The test will iterate through the array, + // requesting each phase in turn. + private final String[][] gcStepPhases; + + // Command line options for invoking the desired collector and + // logging options to produce output that can be matched against + // the regex patterns in the gcStepPhases pairs. + private final String[] gcOptions; + + // The name of the GC, logged as "Using " near the beginning + // of the log output. + private final String gcName; + + public ConcurrentPhaseControlUtil(String phaseStepperName, + String[][] gcStepPhases, + String[] gcOptions, + String gcName) { + this.phaseStepperName = phaseStepperName; + this.gcStepPhases = gcStepPhases; + this.gcOptions = gcOptions; + this.gcName = gcName; + } + + private static void fail(String message) throws Exception { + throw new RuntimeException(message); + } + + private static final String requestPrefix = "Requesting concurrent phase: "; + private static final String reachedPrefix = "Reached concurrent phase: "; + + public String executeTest() throws Exception { + System.out.println("\n---------- Testing ---------"); + + final String[] wb_arguments = { + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI" + }; + + List arglist = new ArrayList(); + Collections.addAll(arglist, wb_arguments); + Collections.addAll(arglist, gcOptions); + Collections.addAll(arglist, phaseStepperName); + String[] arguments = arglist.toArray(new String[arglist.size()]); + + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(arguments); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + + String messages = output.getStdout(); + System.out.println(messages); + + output.shouldHaveExitValue(0); + output.shouldContain("Using " + gcName); + + return messages; + } + + public void checkPhaseControl(String messages) throws Exception { + // Iterate through the phase sequence for the test, verifying + // output contains appropriate sequences of request message, + // log message for phase, and request reached message. Note + // that a log message for a phase may occur later than the + // associated request reached message, or even the following + // request message. + + Pattern nextReqP = Pattern.compile(requestPrefix); + Matcher nextReqM = nextReqP.matcher(messages); + + Pattern nextReachP = Pattern.compile(reachedPrefix); + Matcher nextReachM = nextReachP.matcher(messages); + + String pendingPhaseMessage = null; + int pendingPhaseMessagePosition = -1; + + int position = 0; + for (String[] phase: gcStepPhases) { + String phaseName = phase[0]; + String phaseMsg = phase[1]; + + System.out.println("Processing phase " + phaseName); + + // Update the "next" matchers to refer to the next + // corresponding pair of request and reached messages. + if (!nextReqM.find()) { + fail("Didn't find next phase request"); + } else if ((position != 0) && (nextReqM.start() < nextReachM.end())) { + fail("Next request before previous reached"); + } else if (!nextReachM.find()) { + fail("Didn't find next phase reached"); + } else if (nextReachM.start() <= nextReqM.end()) { + fail("Next request/reached misordered"); + } + + // Find the expected request message, and ensure it is the next. + Pattern reqP = Pattern.compile(requestPrefix + phaseName); + Matcher reqM = reqP.matcher(messages); + if (!reqM.find(position)) { + fail("Didn't find request for " + phaseName); + } else if (reqM.start() != nextReqM.start()) { + fail("Request mis-positioned for " + phaseName); + } + + // Find the expected reached message, and ensure it is the next. + Pattern reachP = Pattern.compile(reachedPrefix + phaseName); + Matcher reachM = reachP.matcher(messages); + if (!reachM.find(position)) { + fail("Didn't find reached for " + phaseName); + } else if (reachM.start() != nextReachM.start()) { + fail("Reached mis-positioned for " + phaseName); + } + + // If there is a pending log message (see below), ensure + // it was before the current reached message. + if (pendingPhaseMessage != null) { + if (pendingPhaseMessagePosition >= reachM.start()) { + fail("Log message after next reached message: " + + pendingPhaseMessage); + } + } + + // If the phase has an associated logging message, verify + // such a logging message is present following the + // request, and otherwise positioned appropriately. The + // complication here is that the logging message + // associated with a request might follow the reached + // message, and even the next request message, if there is + // a later request. But it must preceed the next + // logging message and the next reached message. + boolean clearPendingPhaseMessage = true; + if (phaseMsg != null) { + Pattern logP = Pattern.compile("GC\\(\\d+\\)\\s+" + phaseMsg); + Matcher logM = logP.matcher(messages); + if (!logM.find(reqM.end())) { + fail("Didn't find message " + phaseMsg); + } + + if (pendingPhaseMessage != null) { + if (pendingPhaseMessagePosition >= logM.start()) { + fail("Log messages out of order: " + + pendingPhaseMessage + " should preceed " + + phaseMsg); + } + } + + if (reachM.end() <= logM.start()) { + clearPendingPhaseMessage = false; + pendingPhaseMessage = phaseMsg; + pendingPhaseMessagePosition = logM.end(); + } + } + if (clearPendingPhaseMessage) { + pendingPhaseMessage = null; + pendingPhaseMessagePosition = -1; + } + + // Update position for start of next phase search. + position = reachM.end(); + } + // It's okay for there to be a leftover pending phase message. + // We know it was found before the end of the log. + } + + public void run() throws Exception { + String messages = executeTest(); + checkPhaseControl(messages); + } + + public static final class Executor { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + // An array of all the concurrent GC phases expected to be + // provided by the collector. + private final String[] gcAllPhases; + + private final String[][] gcStepPhases; + + public Executor(String[] gcAllPhases, String[][] gcStepPhases) { + this.gcAllPhases = gcAllPhases; + this.gcStepPhases = gcStepPhases; + } + + private void checkAllPhases() throws Exception { + System.out.println("Checking phase set is as expected"); + + String[] phases = WB.getConcurrentGCPhases(); + + List expectedList = Arrays.asList(gcAllPhases); + List actualList = Arrays.asList(phases); + + Set expected = new HashSet(expectedList); + Set actual = new HashSet(actualList); + + expected.removeAll(actualList); + actual.removeAll(expectedList); + + boolean match = true; + if (!expected.isEmpty()) { + match = false; + System.out.println("Unexpected phases:"); + for (String s: expected) { + System.out.println(" " + s); + } + } + if (!actual.isEmpty()) { + match = false; + System.out.println("Expected but missing phases:"); + for (String s: actual) { + System.out.println(" " + s); + } + } + if (!match) { + fail("Mismatch between expected and actual phases"); + } + } + + private void step(String phase) { + System.out.println(requestPrefix + phase); + WB.requestConcurrentGCPhase(phase); + System.out.println(reachedPrefix + phase); + } + + public void run() throws Exception { + if (!WB.supportsConcurrentGCPhaseControl()) { + fail("GC doesn't support concurrent phase control"); + } + + checkAllPhases(); + + // Iterate through test sequence of phases, requesting each. + for (String[] phaseData: gcStepPhases) { + step(phaseData[0]); + } + // Wait a little to allow a delayed logging message for + // the final request/reached to be printed before exiting + // the program. + Thread.sleep(250); + } + } +}