1 /* 2 * Copyright (c) 1998, 2015, 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.net.InetAddress; 26 import java.net.InetSocketAddress; 27 import java.net.ProtocolFamily; 28 import java.net.ServerSocket; 29 import java.nio.channels.Channel; 30 import java.nio.channels.DatagramChannel; 31 import java.nio.channels.Pipe; 32 import java.nio.channels.ServerSocketChannel; 33 import java.nio.channels.SocketChannel; 34 import java.nio.channels.spi.AbstractSelector; 35 import java.nio.channels.spi.SelectorProvider; 36 import java.rmi.*; 37 import java.rmi.activation.*; 38 import java.rmi.registry.*; 39 import java.util.concurrent.TimeoutException; 40 41 /** 42 * Utility class that creates an instance of rmid with a policy 43 * file of name <code>TestParams.defaultPolicy</code>. 44 * 45 * Activation groups should run with the same security manager as the 46 * test. 47 */ 48 public class RMID extends JavaVM { 49 50 // TODO: adjust these based on the timeout factor 51 // such as jcov.sleep.multiplier; see start(long) method. 52 // Also consider the test.timeout.factor property (a float). 53 private static final long TIMEOUT_SHUTDOWN_MS = 60_000L; 54 private static final long TIMEOUT_DESTROY_MS = 10_000L; 55 private static final long STARTTIME_MS = 15_000L; 56 private static final long POLLTIME_MS = 100L; 57 58 private static final String SYSTEM_NAME = ActivationSystem.class.getName(); 59 // "java.rmi.activation.ActivationSystem" 60 61 public static String MANAGER_OPTION="-Djava.security.manager="; 62 63 /** 64 * Test port for rmid. 65 * 66 * May initially be 0, which means that the child rmid process will choose 67 * an ephemeral port and report it back to the parent process. This field 68 * will then be set to the child rmid's ephemeral port value. 69 */ 70 private volatile int port; 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 private static void mesg(Object mesg) { 78 System.err.println("RMID: " + mesg.toString()); 79 } 80 81 /** make test options and arguments */ 82 private static String makeOptions(int port, boolean debugExec) { 83 84 String options = " -Dsun.rmi.server.activation.debugExec=" + 85 debugExec; 86 // + 87 //" -Djava.compiler= "; 88 89 // if test params set, want to propagate them 90 if (!TestParams.testSrc.equals("")) { 91 options += " -Dtest.src=" + TestParams.testSrc + " "; 92 } 93 //if (!TestParams.testClasses.equals("")) { 94 // options += " -Dtest.classes=" + TestParams.testClasses + " "; 95 //} 96 options += " -Dtest.classes=" + TestParams.testClasses //; 97 + 98 " -Djava.rmi.server.logLevel=v "; 99 100 // + 101 // " -Djava.security.debug=all "; 102 103 // Set execTimeout to 60 sec (default is 30 sec) 104 // to avoid spurious timeouts on slow machines. 105 options += " -Dsun.rmi.activation.execTimeout=60000"; 106 107 // Disable redirection of System.err to /tmp, even when using the 108 // inheritChannel mechanism. 109 options += " -Dsun.rmi.server.activation.disableErrRedirect"; 110 111 if (port == 0) { 112 // Ephemeral port, so have the rmid child process create the 113 // server socket channel and report its port number, over stdin. 114 options += " -classpath " + TestParams.testClassPath; 115 options += " --add-exports=java.base/sun.nio.ch=ALL-UNNAMED"; 116 options += " -Djava.nio.channels.spi.SelectorProvider=RMID$RMIDSelectorProvider"; 117 } 118 119 return options; 120 } 121 122 private static String makeArgs(boolean includePortArg, int port) { 123 String propagateManager = null; 124 125 // rmid will run with a security manager set, but no policy 126 // file - it should not need one. 127 if (System.getSecurityManager() == null) { 128 propagateManager = MANAGER_OPTION + 129 TestParams.defaultSecurityManager; 130 } else { 131 propagateManager = MANAGER_OPTION + 132 System.getSecurityManager().getClass().getName(); 133 } 134 135 // getAbsolutePath requires permission to read user.dir 136 String args = 137 " -log " + (new File(LOGDIR, log)).getAbsolutePath(); 138 139 // 0 = ephemeral port, do no include an explicit port number 140 if (includePortArg && port != 0) { 141 args += " -port " + port; 142 } 143 144 // + 145 // " -C-Djava.compiler= "; 146 147 // if test params set, want to propagate them 148 if (!TestParams.testSrc.equals("")) { 149 args += " -C-Dtest.src=" + TestParams.testSrc; 150 } 151 if (!TestParams.testClasses.equals("")) { 152 args += " -C-Dtest.classes=" + TestParams.testClasses; 153 } 154 155 if (!TestParams.testJavaOpts.equals("")) { 156 for (String a : TestParams.testJavaOpts.split(" +")) { 157 args += " -C" + a; 158 } 159 } 160 161 if (!TestParams.testVmOpts.equals("")) { 162 for (String a : TestParams.testVmOpts.split(" +")) { 163 args += " -C" + a; 164 } 165 } 166 167 args += " -C-Djava.rmi.server.useCodebaseOnly=false "; 168 169 args += " " + getCodeCoverageArgs(); 170 return args; 171 } 172 173 /** 174 * Routine that creates an rmid that will run with or without a 175 * policy file. 176 */ 177 public static RMID createRMID() { 178 return createRMID(System.out, System.err, true, true, 179 TestLibrary.getUnusedRandomPort()); 180 } 181 182 public static RMID createRMID(OutputStream out, OutputStream err, 183 boolean debugExec) 184 { 185 return createRMID(out, err, debugExec, true, 186 TestLibrary.getUnusedRandomPort()); 187 } 188 189 public static RMID createRMID(OutputStream out, OutputStream err, 190 boolean debugExec, boolean includePortArg, 191 int port) 192 { 193 String options = makeOptions(port, debugExec); 194 String args = makeArgs(includePortArg, port); 195 RMID rmid = new RMID("sun.rmi.server.Activation", options, args, 196 out, err, port); 197 rmid.setPolicyFile(TestParams.defaultRmidPolicy); 198 199 return rmid; 200 } 201 202 public static RMID createRMIDOnEphemeralPort() { 203 return createRMID(System.out, System.err, true, true, 0); 204 } 205 206 public static RMID createRMIDOnEphemeralPort(OutputStream out, 207 OutputStream err, 208 boolean debugExec) 209 { 210 return createRMID(out, err, debugExec, true, 0); 211 } 212 213 214 /** 215 * Private constructor. RMID instances should be created 216 * using the static factory methods. 217 */ 218 private RMID(String classname, String options, String args, 219 OutputStream out, OutputStream err, int port) 220 { 221 super(classname, options, args, out, err); 222 this.port = port; 223 } 224 225 /** 226 * Removes rmid's log file directory. 227 */ 228 public static void removeLog() { 229 File f = new File(LOGDIR, log); 230 231 if (f.exists()) { 232 mesg("Removing rmid's old log file."); 233 String[] files = f.list(); 234 235 if (files != null) { 236 for (int i=0; i<files.length; i++) { 237 (new File(f, files[i])).delete(); 238 } 239 } 240 241 if (! f.delete()) { 242 mesg("Warning: unable to delete old log file."); 243 } 244 } 245 } 246 247 /** 248 * This method is used for adding arguments to rmid (not its VM) 249 * for passing as VM options to its child group VMs. 250 * Returns the extra command line arguments required 251 * to turn on jcov code coverage analysis for rmid child VMs. 252 */ 253 protected static String getCodeCoverageArgs() { 254 return TestLibrary.getExtraProperty("rmid.jcov.args",""); 255 } 256 257 /** 258 * Looks up the activation system in the registry on the given port, 259 * returning its stub, or null if it's not present. This method differs from 260 * ActivationGroup.getSystem() because this method looks on a specific port 261 * instead of using the java.rmi.activation.port property like 262 * ActivationGroup.getSystem() does. This method also returns null instead 263 * of throwing exceptions. 264 */ 265 public static ActivationSystem lookupSystem(int port) { 266 try { 267 return (ActivationSystem)LocateRegistry.getRegistry(port).lookup(SYSTEM_NAME); 268 } catch (RemoteException | NotBoundException ex) { 269 return null; 270 } 271 } 272 273 /** 274 * Starts rmid and waits up to the default timeout period 275 * to confirm that it's running. 276 */ 277 public void start() throws IOException { 278 start(STARTTIME_MS); 279 } 280 281 /** 282 * Starts rmid and waits up to the given timeout period 283 * to confirm that it's running. 284 */ 285 public void start(long waitTime) throws IOException { 286 287 // if rmid is already running, then the test will fail with 288 // a well recognized exception (port already in use...). 289 290 mesg("Starting rmid on port " + port + "."); 291 int p = super.startAndGetPort(); 292 if (p != -1) 293 port = p; 294 mesg("Started rmid on port " + port + "."); 295 296 // int slopFactor = 1; 297 // try { 298 // slopFactor = Integer.valueOf( 299 // TestLibrary.getExtraProperty("jcov.sleep.multiplier","1")); 300 // } catch (NumberFormatException ignore) {} 301 // waitTime = waitTime * slopFactor; 302 303 long startTime = System.currentTimeMillis(); 304 long deadline = TestLibrary.computeDeadline(startTime, waitTime); 305 306 while (true) { 307 try { 308 Thread.sleep(POLLTIME_MS); 309 } catch (InterruptedException ie) { 310 Thread.currentThread().interrupt(); 311 mesg("Starting rmid interrupted, giving up at " + 312 (System.currentTimeMillis() - startTime) + "ms."); 313 return; 314 } 315 316 try { 317 int status = vm.exitValue(); 318 waitFor(TIMEOUT_SHUTDOWN_MS); 319 TestLibrary.bomb("Rmid process exited with status " + status + " after " + 320 (System.currentTimeMillis() - startTime) + "ms."); 321 } catch (InterruptedException | TimeoutException e) { 322 mesg(e); 323 } catch (IllegalThreadStateException ignore) { } 324 325 // The rmid process is alive; check to see whether 326 // it responds to a remote call. 327 328 if (lookupSystem(port) != null) { 329 /* 330 * We need to set the java.rmi.activation.port value as the 331 * activation system will use the property to determine the 332 * port #. The activation system will use this value if set. 333 * If it isn't set, the activation system will set it to an 334 * incorrect value. 335 */ 336 System.setProperty("java.rmi.activation.port", Integer.toString(port)); 337 mesg("Started successfully after " + 338 (System.currentTimeMillis() - startTime) + "ms."); 339 return; 340 } 341 342 if (System.currentTimeMillis() > deadline) { 343 TestLibrary.bomb("Failed to start rmid, giving up after " + 344 (System.currentTimeMillis() - startTime) + "ms.", null); 345 } 346 } 347 } 348 349 /** 350 * Destroys rmid and restarts it. Note that this does NOT clean up 351 * the log file, because it stores information about restartable 352 * and activatable objects that must be carried over to the new 353 * rmid instance. 354 */ 355 public void restart() throws IOException { 356 destroy(); 357 options = makeOptions(port, true); 358 args = makeArgs(true, port); 359 start(); 360 } 361 362 /** 363 * Ask rmid to shutdown gracefully using a remote method call. 364 * catch any errors that might occur from rmid not being present 365 * at time of shutdown invocation. If the remote call is 366 * successful, wait for the process to terminate. Return true 367 * if the process terminated, otherwise return false. 368 */ 369 private boolean shutdown() throws InterruptedException { 370 mesg("shutdown()"); 371 long startTime = System.currentTimeMillis(); 372 ActivationSystem system = lookupSystem(port); 373 if (system == null) { 374 mesg("lookupSystem() returned null after " + 375 (System.currentTimeMillis() - startTime) + "ms."); 376 return false; 377 } 378 379 try { 380 mesg("ActivationSystem.shutdown()"); 381 system.shutdown(); 382 } catch (Exception e) { 383 mesg("Caught exception from ActivationSystem.shutdown():"); 384 e.printStackTrace(); 385 } 386 387 try { 388 waitFor(TIMEOUT_SHUTDOWN_MS); 389 mesg("Shutdown successful after " + 390 (System.currentTimeMillis() - startTime) + "ms."); 391 return true; 392 } catch (TimeoutException ex) { 393 mesg("Shutdown timed out after " + 394 (System.currentTimeMillis() - startTime) + "ms:"); 395 ex.printStackTrace(); 396 return false; 397 } 398 } 399 400 /** 401 * Ask rmid to shutdown gracefully but then destroy the rmid 402 * process if it does not exit by itself. This method only works 403 * if rmid is a child process of the current VM. 404 */ 405 public void destroy() { 406 if (vm == null) { 407 throw new IllegalStateException("can't wait for RMID that isn't running"); 408 } 409 410 long startTime = System.currentTimeMillis(); 411 412 // First, attempt graceful shutdown of the activation system. 413 try { 414 if (! shutdown()) { 415 // Graceful shutdown failed, use Process.destroy(). 416 mesg("Destroying RMID process."); 417 vm.destroy(); 418 try { 419 waitFor(TIMEOUT_DESTROY_MS); 420 mesg("Destroy successful after " + 421 (System.currentTimeMillis() - startTime) + "ms."); 422 } catch (TimeoutException ex) { 423 mesg("Destroy timed out, giving up after " + 424 (System.currentTimeMillis() - startTime) + "ms:"); 425 ex.printStackTrace(); 426 } 427 } 428 } catch (InterruptedException ie) { 429 mesg("Shutdown/destroy interrupted, giving up at " + 430 (System.currentTimeMillis() - startTime) + "ms."); 431 ie.printStackTrace(); 432 Thread.currentThread().interrupt(); 433 return; 434 } 435 436 vm = null; 437 } 438 439 /** 440 * Shuts down rmid and then removes its log file. 441 */ 442 public void cleanup() { 443 destroy(); 444 RMID.removeLog(); 445 } 446 447 /** 448 * Gets the port on which this rmid is listening. 449 */ 450 public int getPort() { 451 return port; 452 } 453 454 455 /** 456 * A SelectorProvider, that can be loaded by the child rmid process, whose 457 * inheritedChannel method will create a new server socket channel and 458 * report it back to the parent process, over stdout. 459 */ 460 public static class RMIDSelectorProvider extends SelectorProvider { 461 462 private final SelectorProvider provider; 463 private ServerSocketChannel channel; 464 465 public RMIDSelectorProvider() { 466 provider = sun.nio.ch.DefaultSelectorProvider.create(); 467 } 468 469 public DatagramChannel openDatagramChannel() 470 throws IOException 471 { 472 return provider.openDatagramChannel(); 473 } 474 475 public DatagramChannel openDatagramChannel(ProtocolFamily family) 476 throws IOException 477 { 478 return provider.openDatagramChannel(family); 479 } 480 481 public Pipe openPipe() 482 throws IOException 483 { 484 return provider.openPipe(); 485 } 486 487 public AbstractSelector openSelector() 488 throws IOException 489 { 490 return provider.openSelector(); 491 } 492 493 public ServerSocketChannel openServerSocketChannel() 494 throws IOException 495 { 496 return provider.openServerSocketChannel(); 497 } 498 499 public SocketChannel openSocketChannel() 500 throws IOException 501 { 502 return provider.openSocketChannel(); 503 } 504 505 public static final String MESSAGE = "RmidSelectorProvider-listening On:"; 506 507 public synchronized Channel inheritedChannel() throws IOException { 508 System.out.println("RMIDSelectorProvider.inheritedChannel"); 509 if (channel == null) { 510 // Create and bind a new server socket channel 511 channel = ServerSocketChannel.open().bind(new InetSocketAddress(0)); 512 System.out.println(MESSAGE + channel.socket().getLocalPort()); 513 } 514 return channel; 515 } 516 } 517 }