60 * @author Robert Field 61 * @author Jan Lahoda 62 */ 63 public class JDIDefaultExecutionControl extends JDIExecutionControl { 64 65 private static final String REMOTE_AGENT = RemoteExecutionControl.class.getName(); 66 67 private VirtualMachine vm; 68 private Process process; 69 70 private final Object STOP_LOCK = new Object(); 71 private boolean userCodeRunning = false; 72 73 /** 74 * Creates an ExecutionControl instance based on a JDI 75 * {@code LaunchingConnector}. 76 * 77 * @return the generator 78 */ 79 public static ExecutionControl.Generator launch() { 80 return env -> create(env, true); 81 } 82 83 /** 84 * Creates an ExecutionControl instance based on a JDI 85 * {@code ListeningConnector}. 86 * 87 * @return the generator 88 */ 89 public static ExecutionControl.Generator listen() { 90 return env -> create(env, false); 91 } 92 93 /** 94 * Creates an ExecutionControl instance based on a JDI 95 * {@code ListeningConnector} or {@code LaunchingConnector}. 96 * 97 * Initialize JDI and use it to launch the remote JVM. Set-up a socket for 98 * commands and results. This socket also transports the user 99 * input/output/error. 100 * 101 * @param env the context passed by 102 * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) } 103 * @return the channel 104 * @throws IOException if there are errors in set-up 105 */ 106 private static JDIDefaultExecutionControl create(ExecutionEnv env, boolean isLaunch) throws IOException { 107 try (final ServerSocket listener = new ServerSocket(0)) { 108 // timeout after 60 seconds 109 listener.setSoTimeout(60000); 110 int port = listener.getLocalPort(); 111 112 // Set-up the JDI connection 113 JDIInitiator jdii = new JDIInitiator(port, 114 env.extraRemoteVMOptions(), REMOTE_AGENT, isLaunch); 115 VirtualMachine vm = jdii.vm(); 116 Process process = jdii.process(); 117 118 // Forward input to the remote agent 119 Util.forwardInputToRemote(env.userIn(), process.getOutputStream(), 120 ex -> debug(ex, "input forwarding failure")); 121 122 List<Consumer<String>> deathListeners = new ArrayList<>(); 123 deathListeners.add(s -> env.closeDown()); 124 Util.detectJDIExitEvent(vm, s -> { 125 for (Consumer<String> h : deathListeners) { 126 h.accept(s); 127 } 128 }); 129 130 // Set-up the commands/reslts on the socket. Piggy-back snippet 131 // output. 132 Socket socket = listener.accept(); 133 // out before in -- match remote creation so we don't hang 134 ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream()); | 60 * @author Robert Field 61 * @author Jan Lahoda 62 */ 63 public class JDIDefaultExecutionControl extends JDIExecutionControl { 64 65 private static final String REMOTE_AGENT = RemoteExecutionControl.class.getName(); 66 67 private VirtualMachine vm; 68 private Process process; 69 70 private final Object STOP_LOCK = new Object(); 71 private boolean userCodeRunning = false; 72 73 /** 74 * Creates an ExecutionControl instance based on a JDI 75 * {@code LaunchingConnector}. 76 * 77 * @return the generator 78 */ 79 public static ExecutionControl.Generator launch() { 80 return env -> create(env, true, false); 81 } 82 83 /** 84 * Creates an ExecutionControl instance based on a JDI 85 * {@code ListeningConnector}. 86 * 87 * @return the generator 88 */ 89 public static ExecutionControl.Generator listen() { 90 return env -> create(env, false, false); 91 } 92 93 /** 94 * Creates an ExecutionControl instance based on a JDI 95 * {@code ListeningConnector} connecting to "localhost". 96 * 97 * @return the generator 98 */ 99 public static ExecutionControl.Generator listenLocalhost() { 100 return env -> create(env, false, true); 101 } 102 103 /** 104 * Creates an ExecutionControl instance based on a JDI 105 * {@code ListeningConnector} or {@code LaunchingConnector}. 106 * 107 * Initialize JDI and use it to launch the remote JVM. Set-up a socket for 108 * commands and results. This socket also transports the user 109 * input/output/error. 110 * 111 * @param env the context passed by 112 * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) } 113 * @param isLaunch does JDI do the launch? That is, LaunchingConnector, 114 * otherwise we start explicitly and use ListeningConnector 115 * @param useLocalhost explicitly use "localhost" rather than discovered 116 * hostname, applies to listening only (!isLaunch) 117 * @return the channel 118 * @throws IOException if there are errors in set-up 119 */ 120 private static JDIDefaultExecutionControl create(ExecutionEnv env, 121 boolean isLaunch, boolean useLocalhost) throws IOException { 122 try (final ServerSocket listener = new ServerSocket(0)) { 123 // timeout after 60 seconds 124 listener.setSoTimeout(60000); 125 int port = listener.getLocalPort(); 126 127 // Set-up the JDI connection 128 JDIInitiator jdii = new JDIInitiator(port, 129 env.extraRemoteVMOptions(), REMOTE_AGENT, isLaunch, useLocalhost); 130 VirtualMachine vm = jdii.vm(); 131 Process process = jdii.process(); 132 133 // Forward input to the remote agent 134 Util.forwardInputToRemote(env.userIn(), process.getOutputStream(), 135 ex -> debug(ex, "input forwarding failure")); 136 137 List<Consumer<String>> deathListeners = new ArrayList<>(); 138 deathListeners.add(s -> env.closeDown()); 139 Util.detectJDIExitEvent(vm, s -> { 140 for (Consumer<String> h : deathListeners) { 141 h.accept(s); 142 } 143 }); 144 145 // Set-up the commands/reslts on the socket. Piggy-back snippet 146 // output. 147 Socket socket = listener.accept(); 148 // out before in -- match remote creation so we don't hang 149 ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream()); |