1 /* 2 * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.*; 25 import java.rmi.*; 26 import java.rmi.activation.*; 27 import java.rmi.registry.*; 28 import java.time.LocalTime; 29 import java.util.concurrent.TimeoutException; 30 31 /** 32 * Utility class that creates an instance of rmid with a policy 33 * file of name <code>TestParams.defaultPolicy</code>. 34 * 35 * Activation groups should run with the same security manager as the 36 * test. 37 */ 38 public class RMID extends JavaVM { 39 40 // TODO: adjust these based on the timeout factor 41 // such as jcov.sleep.multiplier; see start(long) method. 42 // Also consider the test.timeout.factor property (a float). 43 private static final long TIMEOUT_SHUTDOWN_MS = 60_000L; 44 private static final long TIMEOUT_DESTROY_MS = 10_000L; 45 private static final long STARTTIME_MS = 15_000L; 46 private static final long POLLTIME_MS = 100L; 47 48 // when restart rmid, it may take more time than usual because of 49 // "port in use" by a possible interloper (check JDK-8168975), 50 // so need to set a longer timeout for restart. 51 private static long restartTimeout; 52 // Same reason to inheritedChannel in RMIDSelectorProvider. 53 // Put it here rather than in RMIDSelectorProvider to adjust 54 // both timeout values together. 55 private static long inheritedChannelTimeout; 56 57 private static final String SYSTEM_NAME = ActivationSystem.class.getName(); 58 // "java.rmi.activation.ActivationSystem" 59 60 public static String MANAGER_OPTION="-Djava.security.manager="; 61 62 /** 63 * Test port for rmid. 64 * 65 * May initially be 0, which means that the child rmid process will choose 66 * an ephemeral port and report it back to the parent process. This field 67 * will then be set to the child rmid's ephemeral port value. 68 */ 69 private volatile int port; 70 //private final boolean ephemeralPort 71 72 /** Initial log name */ 73 protected static String log = "log"; 74 /** rmid's logfile directory; currently must be "." */ 75 protected static String LOGDIR = "."; 76 77 /** The output message from the child rmid process that directly precedes 78 * the ephemeral port number.*/ 79 public static final String EPHEMERAL_MSG = "RmidSelectorProvider-listening-On:"; 80 81 private static void mesg(Object mesg) { 82 System.err.println("RMID: " + mesg.toString()); 83 } 84 85 /** make test options and arguments */ 86 private static String makeOptions(int port, boolean debugExec, 87 boolean enableSelectorProvider) { 88 89 String options = " -Dsun.rmi.server.activation.debugExec=" + 90 debugExec; 91 // + 92 //" -Djava.compiler= "; 93 94 // if test params set, want to propagate them 95 if (!TestParams.testSrc.equals("")) { 96 options += " -Dtest.src=" + TestParams.testSrc + " "; 97 } 98 //if (!TestParams.testClasses.equals("")) { 99 // options += " -Dtest.classes=" + TestParams.testClasses + " "; 100 //} 101 options += " -Dtest.classes=" + TestParams.testClasses //; 102 + 103 " -Djava.rmi.server.logLevel=v "; 104 105 // + 106 // " -Djava.security.debug=all "; 107 108 // Set execTimeout to 60 sec (default is 30 sec) 109 // to avoid spurious timeouts on slow machines. 110 options += " -Dsun.rmi.activation.execTimeout=60000"; 111 112 // It's important to set handshakeTimeout to small value, for example 113 // 5 sec (default is 60 sec) to avoid wasting too much time when 114 // calling lookupSystem(port) in restart(), because 115 // 1. If use default value of this option, it will take about 2 minutes 116 // to finish lookupSystem(port) in 2 loops in restart(); 117 // 2. If set this option as 5 sec then lookupSystem(port) will return 118 // very quickly. 119 options += " -Dsun.rmi.transport.tcp.handshakeTimeout=5000"; 120 121 if (port == 0 || enableSelectorProvider) { 122 // Ephemeral port, so have the rmid child process create the 123 // server socket channel and report its port number, over stdin. 124 options += " -classpath " + TestParams.testClassPath; 125 options += " --add-exports=java.base/sun.nio.ch=ALL-UNNAMED"; 126 options += " -Djava.nio.channels.spi.SelectorProvider=RMIDSelectorProvider"; 127 options += " -Dtest.java.rmi.testlibrary.RMIDSelectorProvider.port=" + port; 128 options += " -Dtest.java.rmi.testlibrary.RMIDSelectorProvider.timeout=" 129 + inheritedChannelTimeout; 130 131 // Disable redirection of System.err to /tmp 132 options += " -Dsun.rmi.server.activation.disableErrRedirect=true"; 133 } 134 135 return options; 136 } 137 138 private static String makeArgs() { 139 return makeArgs(false, 0); 140 } 141 142 private static String makeArgs(boolean includePortArg, int port) { 143 // getAbsolutePath requires permission to read user.dir 144 String args = 145 " -log " + (new File(LOGDIR, log)).getAbsolutePath(); 146 147 // 0 = ephemeral port, do not include an explicit port number 148 if (includePortArg && port != 0) { 149 args += " -port " + port; 150 } 151 152 // + 153 // " -C-Djava.compiler= "; 154 155 // if test params set, want to propagate them 156 if (!TestParams.testSrc.equals("")) { 157 args += " -C-Dtest.src=" + TestParams.testSrc; 158 } 159 if (!TestParams.testClasses.equals("")) { 160 args += " -C-Dtest.classes=" + TestParams.testClasses; 161 } 162 163 if (!TestParams.testJavaOpts.equals("")) { 164 for (String a : TestParams.testJavaOpts.split(" +")) { 165 args += " -C" + a; 166 } 167 } 168 169 if (!TestParams.testVmOpts.equals("")) { 170 for (String a : TestParams.testVmOpts.split(" +")) { 171 args += " -C" + a; 172 } 173 } 174 175 args += " -C-Djava.rmi.server.useCodebaseOnly=false "; 176 177 args += " " + getCodeCoverageArgs(); 178 return args; 179 } 180 181 /** 182 * Routine that creates an rmid that will run with or without a 183 * policy file. 184 */ 185 public static RMID createRMID() { 186 return createRMID(System.out, System.err, true, true, 187 TestLibrary.getUnusedRandomPort()); 188 } 189 190 public static RMID createRMID(OutputStream out, OutputStream err, 191 boolean debugExec) 192 { 193 return createRMID(out, err, debugExec, true, 194 TestLibrary.getUnusedRandomPort()); 195 } 196 197 public static RMID createRMID(OutputStream out, OutputStream err, 198 boolean debugExec, boolean includePortArg, 199 int port) 200 { 201 return createRMIDWithOptions(out, err, debugExec, includePortArg, port, ""); 202 } 203 204 /** 205 * Create a RMID on a specified port capturing stdout and stderr 206 * with additional command line options and whether to print out 207 * debugging information that is used for spawning activation groups. 208 * 209 * @param out the OutputStream where the normal output of the 210 * rmid subprocess goes 211 * @param err the OutputStream where the error output of the 212 * rmid subprocess goes 213 * @param debugExec whether to print out debugging information 214 * @param includePortArg whether to include port argument 215 * @param port the port on which rmid accepts requests 216 * @param additionalOptions additional command line options 217 * @return a RMID instance 218 */ 219 public static RMID createRMIDWithOptions(OutputStream out, OutputStream err, 220 boolean debugExec, boolean includePortArg, 221 int port, String additionalOptions) 222 { 223 String options = makeOptions(port, debugExec, false); 224 options += " " + additionalOptions; 225 String args = makeArgs(includePortArg, port); 226 RMID rmid = new RMID("sun.rmi.server.Activation", options, args, 227 out, err, port); 228 rmid.setPolicyFile(TestParams.defaultRmidPolicy); 229 230 return rmid; 231 } 232 233 public static RMID createRMIDOnEphemeralPort() { 234 return createRMID(System.out, System.err, true, false, 0); 235 } 236 237 /** 238 * Create a RMID on an ephemeral port capturing stdout and stderr 239 * with additional command line options. 240 * 241 * @param additionalOptions additional command line options 242 * @return a RMID instance 243 */ 244 public static RMID createRMIDOnEphemeralPortWithOptions( 245 String additionalOptions) { 246 return createRMIDWithOptions(System.out, System.err, 247 true, false, 0, additionalOptions); 248 } 249 250 public static RMID createRMIDOnEphemeralPort(OutputStream out, 251 OutputStream err, 252 boolean debugExec) 253 { 254 return createRMID(out, err, debugExec, false, 0); 255 } 256 257 258 /** 259 * Private constructor. RMID instances should be created 260 * using the static factory methods. 261 */ 262 private RMID(String classname, String options, String args, 263 OutputStream out, OutputStream err, int port) 264 { 265 super(classname, options, args, out, err); 266 this.port = port; 267 long waitTime = (long)(240_000 * TestLibrary.getTimeoutFactor()); 268 restartTimeout = (long)(waitTime * 0.9); 269 inheritedChannelTimeout = (long)(waitTime * 0.8); 270 } 271 272 /** 273 * Removes rmid's log file directory. 274 */ 275 public static void removeLog() { 276 File f = new File(LOGDIR, log); 277 278 if (f.exists()) { 279 mesg("Removing rmid's old log file."); 280 String[] files = f.list(); 281 282 if (files != null) { 283 for (int i=0; i<files.length; i++) { 284 (new File(f, files[i])).delete(); 285 } 286 } 287 288 if (! f.delete()) { 289 mesg("Warning: unable to delete old log file."); 290 } 291 } 292 } 293 294 /** 295 * This method is used for adding arguments to rmid (not its VM) 296 * for passing as VM options to its child group VMs. 297 * Returns the extra command line arguments required 298 * to turn on jcov code coverage analysis for rmid child VMs. 299 */ 300 protected static String getCodeCoverageArgs() { 301 return TestLibrary.getExtraProperty("rmid.jcov.args",""); 302 } 303 304 /** 305 * Looks up the activation system in the registry on the given port, 306 * returning its stub, or null if it's not present. This method differs from 307 * ActivationGroup.getSystem() because this method looks on a specific port 308 * instead of using the java.rmi.activation.port property like 309 * ActivationGroup.getSystem() does. This method also returns null instead 310 * of throwing exceptions. 311 */ 312 public static ActivationSystem lookupSystem(int port) { 313 try { 314 return (ActivationSystem)LocateRegistry.getRegistry(port).lookup(SYSTEM_NAME); 315 } catch (RemoteException | NotBoundException ex) { 316 return null; 317 } 318 } 319 320 /** 321 * Starts rmid and waits up to the default timeout period 322 * to confirm that it's running. 323 */ 324 public void start() throws IOException { 325 start(STARTTIME_MS); 326 } 327 328 /** 329 * Starts rmid and waits up to the given timeout period 330 * to confirm that it's running. 331 */ 332 public void start(long waitTime) throws IOException { 333 334 // if rmid is already running, then the test will fail with 335 // a well recognized exception (port already in use...). 336 337 mesg("Starting rmid on port " + port + ", at " + LocalTime.now()); 338 int p = super.startAndGetPort(); 339 if (p != -1) 340 port = p; 341 mesg("Started rmid on port " + port + ", at " + LocalTime.now()); 342 343 // int slopFactor = 1; 344 // try { 345 // slopFactor = Integer.valueOf( 346 // TestLibrary.getExtraProperty("jcov.sleep.multiplier","1")); 347 // } catch (NumberFormatException ignore) {} 348 // waitTime = waitTime * slopFactor; 349 350 long startTime = System.currentTimeMillis(); 351 long deadline = TestLibrary.computeDeadline(startTime, waitTime); 352 353 while (true) { 354 try { 355 Thread.sleep(POLLTIME_MS); 356 } catch (InterruptedException ie) { 357 Thread.currentThread().interrupt(); 358 mesg("Starting rmid interrupted, giving up at " + 359 (System.currentTimeMillis() - startTime) + "ms."); 360 return; 361 } 362 363 try { 364 int status = vm.exitValue(); 365 waitFor(TIMEOUT_SHUTDOWN_MS); 366 TestLibrary.bomb("Rmid process exited with status " + status + " after " + 367 (System.currentTimeMillis() - startTime) + "ms."); 368 } catch (InterruptedException | TimeoutException e) { 369 mesg(e); 370 } catch (IllegalThreadStateException ignore) { } 371 372 // The rmid process is alive; check to see whether 373 // it responds to a remote call. 374 375 mesg("looking up activation system, at " + LocalTime.now()); 376 if (lookupSystem(port) != null) { 377 /* 378 * We need to set the java.rmi.activation.port value as the 379 * activation system will use the property to determine the 380 * port #. The activation system will use this value if set. 381 * If it isn't set, the activation system will set it to an 382 * incorrect value. 383 */ 384 System.setProperty("java.rmi.activation.port", Integer.toString(port)); 385 mesg("Started successfully after " + 386 (System.currentTimeMillis() - startTime) + "ms, at " + LocalTime.now()); 387 return; 388 } 389 390 mesg("after fail to looking up activation system, at " + LocalTime.now()); 391 if (System.currentTimeMillis() > deadline) { 392 TestLibrary.bomb("Failed to start rmid, giving up after " + 393 (System.currentTimeMillis() - startTime) + "ms.", null); 394 } 395 } 396 } 397 398 /** 399 * Destroys rmid and restarts it. Note that this does NOT clean up 400 * the log file, because it stores information about restartable 401 * and activatable objects that must be carried over to the new 402 * rmid instance. 403 */ 404 public void restart() throws IOException { 405 destroy(); 406 options = makeOptions(port, true, true); 407 args = makeArgs(); 408 409 start(restartTimeout); 410 } 411 412 /** 413 * Ask rmid to shutdown gracefully using a remote method call. 414 * catch any errors that might occur from rmid not being present 415 * at time of shutdown invocation. If the remote call is 416 * successful, wait for the process to terminate. Return true 417 * if the process terminated, otherwise return false. 418 */ 419 private boolean shutdown() throws InterruptedException { 420 mesg("shutdown()"); 421 long startTime = System.currentTimeMillis(); 422 ActivationSystem system = lookupSystem(port); 423 if (system == null) { 424 mesg("lookupSystem() returned null after " + 425 (System.currentTimeMillis() - startTime) + "ms."); 426 return false; 427 } 428 429 try { 430 mesg("ActivationSystem.shutdown()"); 431 system.shutdown(); 432 } catch (Exception e) { 433 mesg("Caught exception from ActivationSystem.shutdown():"); 434 e.printStackTrace(); 435 } 436 437 try { 438 waitFor(TIMEOUT_SHUTDOWN_MS); 439 mesg("Shutdown successful after " + 440 (System.currentTimeMillis() - startTime) + "ms."); 441 return true; 442 } catch (TimeoutException ex) { 443 mesg("Shutdown timed out after " + 444 (System.currentTimeMillis() - startTime) + "ms:"); 445 ex.printStackTrace(); 446 return false; 447 } 448 } 449 450 /** 451 * Ask rmid to shutdown gracefully but then destroy the rmid 452 * process if it does not exit by itself. This method only works 453 * if rmid is a child process of the current VM. 454 */ 455 public void destroy() { 456 if (vm == null) { 457 throw new IllegalStateException("can't wait for RMID that isn't running"); 458 } 459 460 long startTime = System.currentTimeMillis(); 461 462 // First, attempt graceful shutdown of the activation system. 463 try { 464 if (! shutdown()) { 465 // Graceful shutdown failed, use Process.destroy(). 466 mesg("Destroying RMID process."); 467 vm.destroy(); 468 try { 469 waitFor(TIMEOUT_DESTROY_MS); 470 mesg("Destroy successful after " + 471 (System.currentTimeMillis() - startTime) + "ms."); 472 } catch (TimeoutException ex) { 473 mesg("Destroy timed out, giving up after " + 474 (System.currentTimeMillis() - startTime) + "ms:"); 475 ex.printStackTrace(); 476 } 477 } 478 } catch (InterruptedException ie) { 479 mesg("Shutdown/destroy interrupted, giving up at " + 480 (System.currentTimeMillis() - startTime) + "ms."); 481 ie.printStackTrace(); 482 Thread.currentThread().interrupt(); 483 return; 484 } 485 486 vm = null; 487 } 488 489 /** 490 * Shuts down rmid and then removes its log file. 491 */ 492 public void cleanup() { 493 destroy(); 494 RMID.removeLog(); 495 } 496 497 /** 498 * Gets the port on which this rmid is listening. 499 */ 500 public int getPort() { 501 return port; 502 } 503 }