/* * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.*; import java.rmi.*; import java.rmi.activation.*; import java.rmi.registry.*; import java.time.LocalTime; import java.util.concurrent.TimeoutException; /** * Utility class that creates an instance of rmid with a policy * file of name <code>TestParams.defaultPolicy</code>. * * Activation groups should run with the same security manager as the * test. */ public class RMID extends JavaVM { // TODO: adjust these based on the timeout factor // such as jcov.sleep.multiplier; see start(long) method. // Also consider the test.timeout.factor property (a float). private static final long TIMEOUT_SHUTDOWN_MS = 60_000L; private static final long TIMEOUT_DESTROY_MS = 10_000L; private static final long STARTTIME_MS = 15_000L; private static final long POLLTIME_MS = 100L; // when restart rmid, it may take more time than usual because of // "port in use" by a possible interloper (check JDK-8168975), // so need to set a longer timeout for restart. private static long restartTimeout; // Same reason to inheritedChannel in RMIDSelectorProvider. // Put it here rather than in RMIDSelectorProvider to adjust // both timeout values together. private static long inheritedChannelTimeout; private static final String SYSTEM_NAME = ActivationSystem.class.getName(); // "java.rmi.activation.ActivationSystem" public static String MANAGER_OPTION="-Djava.security.manager="; /** * Test port for rmid. * * May initially be 0, which means that the child rmid process will choose * an ephemeral port and report it back to the parent process. This field * will then be set to the child rmid's ephemeral port value. */ private volatile int port; //private final boolean ephemeralPort /** Initial log name */ protected static String log = "log"; /** rmid's logfile directory; currently must be "." */ protected static String LOGDIR = "."; /** The output message from the child rmid process that directly precedes * the ephemeral port number.*/ public static final String EPHEMERAL_MSG = "RmidSelectorProvider-listening-On:"; private static void mesg(Object mesg) { System.err.println("RMID: " + mesg.toString()); } /** make test options and arguments */ private static String makeOptions(int port, boolean debugExec, boolean enableSelectorProvider) { String options = " -Dsun.rmi.server.activation.debugExec=" + debugExec; // + //" -Djava.compiler= "; // if test params set, want to propagate them if (!TestParams.testSrc.equals("")) { options += " -Dtest.src=" + TestParams.testSrc + " "; } //if (!TestParams.testClasses.equals("")) { // options += " -Dtest.classes=" + TestParams.testClasses + " "; //} options += " -Dtest.classes=" + TestParams.testClasses //; + " -Djava.rmi.server.logLevel=v "; // + // " -Djava.security.debug=all "; // Set execTimeout to 60 sec (default is 30 sec) // to avoid spurious timeouts on slow machines. options += " -Dsun.rmi.activation.execTimeout=60000"; // It's important to set handshakeTimeout to small value, for example // 5 sec (default is 60 sec) to avoid wasting too much time when // calling lookupSystem(port) in restart(), because // 1. If use default value of this option, it will take about 2 minutes // to finish lookupSystem(port) in 2 loops in restart(); // 2. If set this option as 5 sec then lookupSystem(port) will return // very quickly. options += " -Dsun.rmi.transport.tcp.handshakeTimeout=5000"; if (port == 0 || enableSelectorProvider) { // Ephemeral port, so have the rmid child process create the // server socket channel and report its port number, over stdin. options += " -classpath " + TestParams.testClassPath; options += " --add-exports=java.base/sun.nio.ch=ALL-UNNAMED"; options += " -Djava.nio.channels.spi.SelectorProvider=RMIDSelectorProvider"; options += " -Dtest.java.rmi.testlibrary.RMIDSelectorProvider.port=" + port; options += " -Dtest.java.rmi.testlibrary.RMIDSelectorProvider.timeout=" + inheritedChannelTimeout; // Disable redirection of System.err to /tmp options += " -Dsun.rmi.server.activation.disableErrRedirect=true"; } return options; } private static String makeArgs() { return makeArgs(false, 0); } private static String makeArgs(boolean includePortArg, int port) { String propagateManager = null; // rmid will run with a security manager set, but no policy // file - it should not need one. if (System.getSecurityManager() == null) { propagateManager = MANAGER_OPTION + TestParams.defaultSecurityManager; } else { propagateManager = MANAGER_OPTION + System.getSecurityManager().getClass().getName(); } // getAbsolutePath requires permission to read user.dir String args = " -log " + (new File(LOGDIR, log)).getAbsolutePath(); // 0 = ephemeral port, do not include an explicit port number if (includePortArg && port != 0) { args += " -port " + port; } // + // " -C-Djava.compiler= "; // if test params set, want to propagate them if (!TestParams.testSrc.equals("")) { args += " -C-Dtest.src=" + TestParams.testSrc; } if (!TestParams.testClasses.equals("")) { args += " -C-Dtest.classes=" + TestParams.testClasses; } if (!TestParams.testJavaOpts.equals("")) { for (String a : TestParams.testJavaOpts.split(" +")) { args += " -C" + a; } } if (!TestParams.testVmOpts.equals("")) { for (String a : TestParams.testVmOpts.split(" +")) { args += " -C" + a; } } args += " -C-Djava.rmi.server.useCodebaseOnly=false "; args += " " + getCodeCoverageArgs(); return args; } /** * Routine that creates an rmid that will run with or without a * policy file. */ public static RMID createRMID() { return createRMID(System.out, System.err, true, true, TestLibrary.getUnusedRandomPort()); } public static RMID createRMID(OutputStream out, OutputStream err, boolean debugExec) { return createRMID(out, err, debugExec, true, TestLibrary.getUnusedRandomPort()); } public static RMID createRMID(OutputStream out, OutputStream err, boolean debugExec, boolean includePortArg, int port) { String options = makeOptions(port, debugExec, false); String args = makeArgs(includePortArg, port); RMID rmid = new RMID("sun.rmi.server.Activation", options, args, out, err, port); rmid.setPolicyFile(TestParams.defaultRmidPolicy); return rmid; } public static RMID createRMIDOnEphemeralPort() { return createRMID(System.out, System.err, true, false, 0); } public static RMID createRMIDOnEphemeralPort(OutputStream out, OutputStream err, boolean debugExec) { return createRMID(out, err, debugExec, false, 0); } /** * Private constructor. RMID instances should be created * using the static factory methods. */ private RMID(String classname, String options, String args, OutputStream out, OutputStream err, int port) { super(classname, options, args, out, err); this.port = port; long waitTime = (long)(240_000 * TestLibrary.getTimeoutFactor()); restartTimeout = (long)(waitTime * 0.9); inheritedChannelTimeout = (long)(waitTime * 0.8); } /** * Removes rmid's log file directory. */ public static void removeLog() { File f = new File(LOGDIR, log); if (f.exists()) { mesg("Removing rmid's old log file."); String[] files = f.list(); if (files != null) { for (int i=0; i<files.length; i++) { (new File(f, files[i])).delete(); } } if (! f.delete()) { mesg("Warning: unable to delete old log file."); } } } /** * This method is used for adding arguments to rmid (not its VM) * for passing as VM options to its child group VMs. * Returns the extra command line arguments required * to turn on jcov code coverage analysis for rmid child VMs. */ protected static String getCodeCoverageArgs() { return TestLibrary.getExtraProperty("rmid.jcov.args",""); } /** * Looks up the activation system in the registry on the given port, * returning its stub, or null if it's not present. This method differs from * ActivationGroup.getSystem() because this method looks on a specific port * instead of using the java.rmi.activation.port property like * ActivationGroup.getSystem() does. This method also returns null instead * of throwing exceptions. */ public static ActivationSystem lookupSystem(int port) { try { return (ActivationSystem)LocateRegistry.getRegistry(port).lookup(SYSTEM_NAME); } catch (RemoteException | NotBoundException ex) { return null; } } /** * Starts rmid and waits up to the default timeout period * to confirm that it's running. */ public void start() throws IOException { start(STARTTIME_MS); } /** * Starts rmid and waits up to the given timeout period * to confirm that it's running. */ public void start(long waitTime) throws IOException { // if rmid is already running, then the test will fail with // a well recognized exception (port already in use...). mesg("Starting rmid on port " + port + ", at " + LocalTime.now()); int p = super.startAndGetPort(); if (p != -1) port = p; mesg("Started rmid on port " + port + ", at " + LocalTime.now()); // int slopFactor = 1; // try { // slopFactor = Integer.valueOf( // TestLibrary.getExtraProperty("jcov.sleep.multiplier","1")); // } catch (NumberFormatException ignore) {} // waitTime = waitTime * slopFactor; long startTime = System.currentTimeMillis(); long deadline = TestLibrary.computeDeadline(startTime, waitTime); while (true) { try { Thread.sleep(POLLTIME_MS); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); mesg("Starting rmid interrupted, giving up at " + (System.currentTimeMillis() - startTime) + "ms."); return; } try { int status = vm.exitValue(); waitFor(TIMEOUT_SHUTDOWN_MS); TestLibrary.bomb("Rmid process exited with status " + status + " after " + (System.currentTimeMillis() - startTime) + "ms."); } catch (InterruptedException | TimeoutException e) { mesg(e); } catch (IllegalThreadStateException ignore) { } // The rmid process is alive; check to see whether // it responds to a remote call. mesg("looking up activation system, at " + LocalTime.now()); if (lookupSystem(port) != null) { /* * We need to set the java.rmi.activation.port value as the * activation system will use the property to determine the * port #. The activation system will use this value if set. * If it isn't set, the activation system will set it to an * incorrect value. */ System.setProperty("java.rmi.activation.port", Integer.toString(port)); mesg("Started successfully after " + (System.currentTimeMillis() - startTime) + "ms, at " + LocalTime.now()); return; } mesg("after fail to looking up activation system, at " + LocalTime.now()); if (System.currentTimeMillis() > deadline) { TestLibrary.bomb("Failed to start rmid, giving up after " + (System.currentTimeMillis() - startTime) + "ms.", null); } } } /** * Destroys rmid and restarts it. Note that this does NOT clean up * the log file, because it stores information about restartable * and activatable objects that must be carried over to the new * rmid instance. */ public void restart() throws IOException { destroy(); options = makeOptions(port, true, true); args = makeArgs(); start(restartTimeout); } /** * Ask rmid to shutdown gracefully using a remote method call. * catch any errors that might occur from rmid not being present * at time of shutdown invocation. If the remote call is * successful, wait for the process to terminate. Return true * if the process terminated, otherwise return false. */ private boolean shutdown() throws InterruptedException { mesg("shutdown()"); long startTime = System.currentTimeMillis(); ActivationSystem system = lookupSystem(port); if (system == null) { mesg("lookupSystem() returned null after " + (System.currentTimeMillis() - startTime) + "ms."); return false; } try { mesg("ActivationSystem.shutdown()"); system.shutdown(); } catch (Exception e) { mesg("Caught exception from ActivationSystem.shutdown():"); e.printStackTrace(); } try { waitFor(TIMEOUT_SHUTDOWN_MS); mesg("Shutdown successful after " + (System.currentTimeMillis() - startTime) + "ms."); return true; } catch (TimeoutException ex) { mesg("Shutdown timed out after " + (System.currentTimeMillis() - startTime) + "ms:"); ex.printStackTrace(); return false; } } /** * Ask rmid to shutdown gracefully but then destroy the rmid * process if it does not exit by itself. This method only works * if rmid is a child process of the current VM. */ public void destroy() { if (vm == null) { throw new IllegalStateException("can't wait for RMID that isn't running"); } long startTime = System.currentTimeMillis(); // First, attempt graceful shutdown of the activation system. try { if (! shutdown()) { // Graceful shutdown failed, use Process.destroy(). mesg("Destroying RMID process."); vm.destroy(); try { waitFor(TIMEOUT_DESTROY_MS); mesg("Destroy successful after " + (System.currentTimeMillis() - startTime) + "ms."); } catch (TimeoutException ex) { mesg("Destroy timed out, giving up after " + (System.currentTimeMillis() - startTime) + "ms:"); ex.printStackTrace(); } } } catch (InterruptedException ie) { mesg("Shutdown/destroy interrupted, giving up at " + (System.currentTimeMillis() - startTime) + "ms."); ie.printStackTrace(); Thread.currentThread().interrupt(); return; } vm = null; } /** * Shuts down rmid and then removes its log file. */ public void cleanup() { destroy(); RMID.removeLog(); } /** * Gets the port on which this rmid is listening. */ public int getPort() { return port; } }