8131029
: JShell: recover from VMConnection launch failure
Reviewed-by: vromero
This commit is contained in:
parent
9e48d360f5
commit
19c685726f
@ -28,17 +28,18 @@ import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import java.util.List;
|
||||
import com.sun.jdi.VirtualMachine;
|
||||
|
||||
/**
|
||||
* Tracks the state of a class.
|
||||
*/
|
||||
class ClassTracker {
|
||||
|
||||
private final JDIEnv jdiEnv;
|
||||
private final VirtualMachine vm;
|
||||
private final HashMap<String, ClassInfo> map;
|
||||
|
||||
ClassTracker(JDIEnv jdiEnv) {
|
||||
this.jdiEnv = jdiEnv;
|
||||
ClassTracker(VirtualMachine vm) {
|
||||
this.vm = vm;
|
||||
this.map = new HashMap<>();
|
||||
}
|
||||
|
||||
@ -96,7 +97,7 @@ class ClassTracker {
|
||||
}
|
||||
|
||||
private ReferenceType nameToRef(String name) {
|
||||
List<ReferenceType> rtl = jdiEnv.vm().classesByName(name);
|
||||
List<ReferenceType> rtl = vm.classesByName(name);
|
||||
if (rtl.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 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. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package jdk.internal.jshell.jdi;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import jdk.internal.jshell.debug.InternalDebugControl;
|
||||
import jdk.jshell.JShellException;
|
||||
import jdk.jshell.spi.ExecutionControl;
|
||||
import jdk.jshell.spi.ExecutionEnv;
|
||||
|
||||
/**
|
||||
* A meta implementation of ExecutionControl which cycles through the specified
|
||||
* ExecutionControl instances until it finds one that starts.
|
||||
*/
|
||||
public class FailOverExecutionControl implements ExecutionControl {
|
||||
|
||||
private final List<ExecutionControl> ecl = new ArrayList<>();
|
||||
private ExecutionControl active = null;
|
||||
private final List<Exception> thrown = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create the ExecutionControl instance with at least one actual
|
||||
* ExecutionControl instance.
|
||||
*
|
||||
* @param ec0 the first instance to try
|
||||
* @param ecs the second and on instance to try
|
||||
*/
|
||||
public FailOverExecutionControl(ExecutionControl ec0, ExecutionControl... ecs) {
|
||||
ecl.add(ec0);
|
||||
for (ExecutionControl ec : ecs) {
|
||||
ecl.add(ec);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ExecutionEnv env) throws Exception {
|
||||
for (ExecutionControl ec : ecl) {
|
||||
try {
|
||||
ec.start(env);
|
||||
// Success! This is our active ExecutionControl
|
||||
active = ec;
|
||||
return;
|
||||
} catch (Exception ex) {
|
||||
thrown.add(ex);
|
||||
} catch (Throwable ex) {
|
||||
thrown.add(new RuntimeException(ex));
|
||||
}
|
||||
InternalDebugControl.debug(env.state(), env.userErr(),
|
||||
thrown.get(thrown.size() - 1), "failed one in FailOverExecutionControl");
|
||||
}
|
||||
// They have all failed -- rethrow the first exception we encountered
|
||||
throw thrown.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
active.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addToClasspath(String path) {
|
||||
return active.addToClasspath(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String invoke(String classname, String methodname) throws JShellException {
|
||||
return active.invoke(classname, methodname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load(Collection<String> classes) {
|
||||
return active.load(classes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean redefine(Collection<String> classes) {
|
||||
return active.redefine(classes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassStatus getClassStatus(String classname) {
|
||||
return active.getClassStatus(classname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
active.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String varValue(String classname, String varname) {
|
||||
return active.varValue(classname, varname);
|
||||
}
|
||||
|
||||
}
|
@ -49,6 +49,8 @@ import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
*/
|
||||
class JDIConnection {
|
||||
|
||||
private static final String REMOTE_AGENT = "jdk.internal.jshell.remote.RemoteAgent";
|
||||
|
||||
private VirtualMachine vm;
|
||||
private boolean active = true;
|
||||
private Process process = null;
|
||||
@ -59,12 +61,12 @@ class JDIConnection {
|
||||
private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
|
||||
private final int traceFlags;
|
||||
|
||||
synchronized void notifyOutputComplete() {
|
||||
private synchronized void notifyOutputComplete() {
|
||||
outputCompleteCount++;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
synchronized void waitOutputComplete() {
|
||||
private synchronized void waitOutputComplete() {
|
||||
// Wait for stderr and stdout
|
||||
if (process != null) {
|
||||
while (outputCompleteCount < 2) {
|
||||
@ -102,61 +104,72 @@ class JDIConnection {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* The JShell specific Connector args for the LaunchingConnector.
|
||||
*
|
||||
* @param portthe socket port for (non-JDI) commands
|
||||
* @param remoteVMOptions any user requested VM options
|
||||
* @return the argument map
|
||||
*/
|
||||
private static Map<String, String> launchArgs(int port, String remoteVMOptions) {
|
||||
Map<String, String> argumentName2Value = new HashMap<>();
|
||||
argumentName2Value.put("main", REMOTE_AGENT + " " + port);
|
||||
argumentName2Value.put("options", remoteVMOptions);
|
||||
return argumentName2Value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the remote agent and establish a JDI connection to it.
|
||||
*
|
||||
* @param ec the execution control instance
|
||||
* @param port the socket port for (non-JDI) commands
|
||||
* @param remoteVMOptions any user requested VM options
|
||||
* @param isLaunch does JDI do the launch? That is, LaunchingConnector,
|
||||
* otherwise we start explicitly and use ListeningConnector
|
||||
*/
|
||||
JDIConnection(JDIExecutionControl ec, int port, List<String> remoteVMOptions, boolean isLaunch) {
|
||||
this(ec,
|
||||
isLaunch
|
||||
? "com.sun.jdi.CommandLineLaunch"
|
||||
: "com.sun.jdi.SocketListen",
|
||||
isLaunch
|
||||
? launchArgs(port, String.join(" ", remoteVMOptions))
|
||||
: new HashMap<>(),
|
||||
0);
|
||||
if (isLaunch) {
|
||||
vm = launchTarget();
|
||||
} else {
|
||||
vm = listenTarget(port, remoteVMOptions);
|
||||
}
|
||||
|
||||
if (isOpen() && vm().canBeModified()) {
|
||||
/*
|
||||
* Connection opened on startup.
|
||||
*/
|
||||
new JDIEventHandler(vm(), (b) -> ec.handleVMExit())
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base constructor -- set-up a JDI connection.
|
||||
*
|
||||
* @param ec the execution control instance
|
||||
* @param connectorName the standardized name of the connector
|
||||
* @param argumentName2Value the argument map
|
||||
* @param traceFlags should we trace JDI behavior
|
||||
*/
|
||||
JDIConnection(JDIExecutionControl ec, String connectorName, Map<String, String> argumentName2Value, int traceFlags) {
|
||||
this.ec = ec;
|
||||
this.connector = findConnector(connectorName);
|
||||
|
||||
if (connector == null) {
|
||||
throw new IllegalArgumentException("No connector named: " + connectorName);
|
||||
}
|
||||
|
||||
connectorArgs = mergeConnectorArgs(connector, argumentName2Value);
|
||||
this.traceFlags = traceFlags;
|
||||
}
|
||||
|
||||
synchronized VirtualMachine open() {
|
||||
if (connector instanceof LaunchingConnector) {
|
||||
vm = launchTarget();
|
||||
} else if (connector instanceof AttachingConnector) {
|
||||
vm = attachTarget();
|
||||
} else if (connector instanceof ListeningConnector) {
|
||||
vm = listenTarget();
|
||||
} else {
|
||||
throw new InternalError("Invalid connect type");
|
||||
}
|
||||
vm.setDebugTraceMode(traceFlags);
|
||||
// Uncomment here and below to enable event requests
|
||||
// installEventRequests(vm);
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
synchronized boolean setConnectorArg(String name, String value) {
|
||||
/*
|
||||
* Too late if the connection already made
|
||||
*/
|
||||
if (vm != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Connector.Argument argument = connectorArgs.get(name);
|
||||
if (argument == null) {
|
||||
return false;
|
||||
}
|
||||
argument.setValue(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
String connectorArg(String name) {
|
||||
Connector.Argument argument = connectorArgs.get(name);
|
||||
if (argument == null) {
|
||||
return "";
|
||||
}
|
||||
return argument.value();
|
||||
}
|
||||
|
||||
public synchronized VirtualMachine vm() {
|
||||
final synchronized VirtualMachine vm() {
|
||||
if (vm == null) {
|
||||
throw new JDINotConnectedException();
|
||||
} else {
|
||||
@ -164,14 +177,10 @@ class JDIConnection {
|
||||
}
|
||||
}
|
||||
|
||||
synchronized boolean isOpen() {
|
||||
private synchronized boolean isOpen() {
|
||||
return (vm != null);
|
||||
}
|
||||
|
||||
boolean isLaunch() {
|
||||
return (connector instanceof LaunchingConnector);
|
||||
}
|
||||
|
||||
synchronized boolean isRunning() {
|
||||
return process != null && process.isAlive();
|
||||
}
|
||||
@ -181,7 +190,7 @@ class JDIConnection {
|
||||
active = false;
|
||||
}
|
||||
|
||||
public synchronized void disposeVM() {
|
||||
synchronized void disposeVM() {
|
||||
try {
|
||||
if (vm != null) {
|
||||
vm.dispose(); // This could NPE, so it is caught below
|
||||
@ -189,6 +198,8 @@ class JDIConnection {
|
||||
}
|
||||
} catch (VMDisconnectedException ex) {
|
||||
// Ignore if already closed
|
||||
} catch (Throwable e) {
|
||||
ec.debug(DBG_GEN, null, "disposeVM threw: " + e);
|
||||
} finally {
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
@ -198,41 +209,6 @@ class JDIConnection {
|
||||
}
|
||||
}
|
||||
|
||||
/*** Preserved for possible future support of event requests
|
||||
|
||||
private void installEventRequests(VirtualMachine vm) {
|
||||
if (vm.canBeModified()){
|
||||
setEventRequests(vm);
|
||||
resolveEventRequests();
|
||||
}
|
||||
}
|
||||
|
||||
private void setEventRequests(VirtualMachine vm) {
|
||||
EventRequestManager erm = vm.eventRequestManager();
|
||||
|
||||
// Normally, we want all uncaught exceptions. We request them
|
||||
// via the same mechanism as Commands.commandCatchException()
|
||||
// so the user can ignore them later if they are not
|
||||
// interested.
|
||||
// FIXME: this works but generates spurious messages on stdout
|
||||
// during startup:
|
||||
// Set uncaught java.lang.Throwable
|
||||
// Set deferred uncaught java.lang.Throwable
|
||||
Commands evaluator = new Commands();
|
||||
evaluator.commandCatchException
|
||||
(new StringTokenizer("uncaught java.lang.Throwable"));
|
||||
|
||||
ThreadStartRequest tsr = erm.createThreadStartRequest();
|
||||
tsr.enable();
|
||||
ThreadDeathRequest tdr = erm.createThreadDeathRequest();
|
||||
tdr.enable();
|
||||
}
|
||||
|
||||
private void resolveEventRequests() {
|
||||
Env.specList.resolveAll();
|
||||
}
|
||||
***/
|
||||
|
||||
private void dumpStream(InputStream inStream, final PrintStream pStream) throws IOException {
|
||||
BufferedReader in =
|
||||
new BufferedReader(new InputStreamReader(inStream));
|
||||
@ -270,7 +246,7 @@ class JDIConnection {
|
||||
dumpStream(inStream, pStream);
|
||||
} catch (IOException ex) {
|
||||
ec.debug(ex, "Failed reading output");
|
||||
ec.jdiEnv.shutdown();
|
||||
ec.handleVMExit();
|
||||
} finally {
|
||||
notifyOutputComplete();
|
||||
}
|
||||
@ -297,7 +273,7 @@ class JDIConnection {
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ec.debug(ex, "Failed reading output");
|
||||
ec.jdiEnv.shutdown();
|
||||
ec.handleVMExit();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -305,15 +281,19 @@ class JDIConnection {
|
||||
thr.start();
|
||||
}
|
||||
|
||||
private void forwardIO() {
|
||||
displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr());
|
||||
displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut());
|
||||
readRemoteInput(process.getOutputStream(), ec.execEnv.userIn());
|
||||
}
|
||||
|
||||
/* launch child target vm */
|
||||
private VirtualMachine launchTarget() {
|
||||
LaunchingConnector launcher = (LaunchingConnector)connector;
|
||||
try {
|
||||
VirtualMachine new_vm = launcher.launch(connectorArgs);
|
||||
process = new_vm.process();
|
||||
displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr());
|
||||
displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut());
|
||||
readRemoteInput(process.getOutputStream(), ec.execEnv.userIn());
|
||||
forwardIO();
|
||||
return new_vm;
|
||||
} catch (Exception ex) {
|
||||
reportLaunchFail(ex, "launch");
|
||||
@ -321,25 +301,35 @@ class JDIConnection {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* JShell currently uses only launch, preserved for futures: */
|
||||
/* attach to running target vm */
|
||||
private VirtualMachine attachTarget() {
|
||||
AttachingConnector attacher = (AttachingConnector)connector;
|
||||
/**
|
||||
* Directly launch the remote agent and connect JDI to it with a
|
||||
* ListeningConnector.
|
||||
*/
|
||||
private VirtualMachine listenTarget(int port, List<String> remoteVMOptions) {
|
||||
ListeningConnector listener = (ListeningConnector) connector;
|
||||
try {
|
||||
return attacher.attach(connectorArgs);
|
||||
} catch (Exception ex) {
|
||||
reportLaunchFail(ex, "attach");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Start listening, get the JDI connection address
|
||||
String addr = listener.startListening(connectorArgs);
|
||||
ec.debug(DBG_GEN, "Listening at address: " + addr);
|
||||
|
||||
/* JShell currently uses only launch, preserved for futures: */
|
||||
/* listen for connection from target vm */
|
||||
private VirtualMachine listenTarget() {
|
||||
ListeningConnector listener = (ListeningConnector)connector;
|
||||
try {
|
||||
String retAddress = listener.startListening(connectorArgs);
|
||||
ec.debug(DBG_GEN, "Listening at address: " + retAddress);
|
||||
// Launch the RemoteAgent requesting a connection on that address
|
||||
String javaHome = System.getProperty("java.home");
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(javaHome == null
|
||||
? "java"
|
||||
: javaHome + File.separator + "bin" + File.separator + "java");
|
||||
args.add("-agentlib:jdwp=transport=" + connector.transport().name() +
|
||||
",address=" + addr);
|
||||
args.addAll(remoteVMOptions);
|
||||
args.add(REMOTE_AGENT);
|
||||
args.add("" + port);
|
||||
ProcessBuilder pb = new ProcessBuilder(args);
|
||||
process = pb.start();
|
||||
|
||||
// Forward out, err, and in
|
||||
forwardIO();
|
||||
|
||||
// Accept the connection from the remote agent
|
||||
vm = listener.accept(connectorArgs);
|
||||
listener.stopListening(connectorArgs);
|
||||
return vm;
|
||||
@ -353,4 +343,4 @@ class JDIConnection {
|
||||
throw new InternalError("Failed remote " + context + ": " + connector +
|
||||
" -- " + connectorArgs, ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2015, 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. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package jdk.internal.jshell.jdi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.sun.jdi.*;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
|
||||
/**
|
||||
* Representation of a Java Debug Interface environment
|
||||
* Select methods extracted from jdb Env; shutdown() adapted to JShell shutdown.
|
||||
*/
|
||||
class JDIEnv {
|
||||
|
||||
private JDIConnection connection;
|
||||
private final JDIExecutionControl ec;
|
||||
|
||||
JDIEnv(JDIExecutionControl ec) {
|
||||
this.ec = ec;
|
||||
}
|
||||
|
||||
void init(String connectorName, Map<String, String> argumentName2Value, boolean openNow, int flags) {
|
||||
connection = new JDIConnection(ec, connectorName, argumentName2Value, flags);
|
||||
if (!connection.isLaunch() || openNow) {
|
||||
connection.open();
|
||||
}
|
||||
}
|
||||
|
||||
JDIConnection connection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
VirtualMachine vm() {
|
||||
return connection.vm();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.disposeVM();
|
||||
} catch (VMDisconnectedException e) {
|
||||
// Shutting down after the VM has gone away. This is
|
||||
// not an error, and we just ignore it.
|
||||
} catch (Throwable e) {
|
||||
ec.debug(DBG_GEN, null, "disposeVM threw: " + e);
|
||||
}
|
||||
}
|
||||
if (ec.execEnv.state() != null) { // If state has been set-up
|
||||
try {
|
||||
ec.execEnv.closeDown();
|
||||
} catch (Throwable e) {
|
||||
ec.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@
|
||||
|
||||
package jdk.internal.jshell.jdi;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import com.sun.jdi.*;
|
||||
import com.sun.jdi.event.*;
|
||||
|
||||
@ -34,16 +35,20 @@ import com.sun.jdi.event.*;
|
||||
*/
|
||||
class JDIEventHandler implements Runnable {
|
||||
|
||||
Thread thread;
|
||||
volatile boolean connected = true;
|
||||
boolean completed = false;
|
||||
String shutdownMessageKey;
|
||||
final JDIEnv env;
|
||||
private final Thread thread;
|
||||
private volatile boolean connected = true;
|
||||
private boolean completed = false;
|
||||
private final VirtualMachine vm;
|
||||
private final Consumer<Boolean> reportVMExit;
|
||||
|
||||
JDIEventHandler(JDIEnv env) {
|
||||
this.env = env;
|
||||
JDIEventHandler(VirtualMachine vm, Consumer<Boolean> reportVMExit) {
|
||||
this.vm = vm;
|
||||
this.reportVMExit = reportVMExit;
|
||||
this.thread = new Thread(this, "event-handler");
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
void start() {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
synchronized void shutdown() {
|
||||
@ -56,7 +61,7 @@ class JDIEventHandler implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
EventQueue queue = env.vm().eventQueue();
|
||||
EventQueue queue = vm.eventQueue();
|
||||
while (connected) {
|
||||
try {
|
||||
EventSet eventSet = queue.remove();
|
||||
@ -111,27 +116,23 @@ class JDIEventHandler implements Runnable {
|
||||
private void handleExitEvent(Event event) {
|
||||
if (event instanceof VMDeathEvent) {
|
||||
vmDied = true;
|
||||
shutdownMessageKey = "The application exited";
|
||||
} else if (event instanceof VMDisconnectEvent) {
|
||||
connected = false;
|
||||
if (!vmDied) {
|
||||
shutdownMessageKey = "The application has been disconnected";
|
||||
}
|
||||
} else {
|
||||
throw new InternalError("Unexpected event type: " +
|
||||
event.getClass());
|
||||
}
|
||||
env.shutdown();
|
||||
reportVMExit.accept(vmDied);
|
||||
}
|
||||
|
||||
synchronized void handleDisconnectedException() {
|
||||
private synchronized void handleDisconnectedException() {
|
||||
/*
|
||||
* A VMDisconnectedException has happened while dealing with
|
||||
* another event. We need to flush the event queue, dealing only
|
||||
* with exit events (VMDeath, VMDisconnect) so that we terminate
|
||||
* correctly.
|
||||
*/
|
||||
EventQueue queue = env.vm().eventQueue();
|
||||
EventQueue queue = vm.eventQueue();
|
||||
while (connected) {
|
||||
try {
|
||||
EventSet eventSet = queue.remove();
|
||||
|
@ -34,13 +34,20 @@ import java.io.ObjectOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import com.sun.jdi.*;
|
||||
import java.io.EOFException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.sun.jdi.BooleanValue;
|
||||
import com.sun.jdi.ClassNotLoadedException;
|
||||
import com.sun.jdi.IncompatibleThreadStateException;
|
||||
import com.sun.jdi.InvalidTypeException;
|
||||
import com.sun.jdi.ObjectReference;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import com.sun.jdi.StackFrame;
|
||||
import com.sun.jdi.ThreadReference;
|
||||
import com.sun.jdi.VirtualMachine;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import jdk.jshell.JShellException;
|
||||
import jdk.jshell.spi.ExecutionControl;
|
||||
@ -59,13 +66,28 @@ import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
public class JDIExecutionControl implements ExecutionControl {
|
||||
|
||||
ExecutionEnv execEnv;
|
||||
JDIEnv jdiEnv;
|
||||
private ClassTracker tracker;
|
||||
private JDIEventHandler handler;
|
||||
private final boolean isLaunch;
|
||||
private JDIConnection connection;
|
||||
private ClassTracker classTracker;
|
||||
private Socket socket;
|
||||
private ObjectInputStream remoteIn;
|
||||
private ObjectOutputStream remoteOut;
|
||||
private String remoteVMOptions;
|
||||
|
||||
/**
|
||||
* Creates an ExecutionControl instance based on JDI.
|
||||
*
|
||||
* @param isLaunch true for LaunchingConnector; false for ListeningConnector
|
||||
*/
|
||||
public JDIExecutionControl(boolean isLaunch) {
|
||||
this.isLaunch = isLaunch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ExecutionControl instance based on a JDI LaunchingConnector.
|
||||
*/
|
||||
public JDIExecutionControl() {
|
||||
this.isLaunch = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the launching JDI execution engine. Initialize JDI and use it
|
||||
@ -79,20 +101,12 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
@Override
|
||||
public void start(ExecutionEnv execEnv) throws IOException {
|
||||
this.execEnv = execEnv;
|
||||
this.jdiEnv = new JDIEnv(this);
|
||||
this.tracker = new ClassTracker(jdiEnv);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
execEnv.extraRemoteVMOptions().stream()
|
||||
.forEach(s -> {
|
||||
sb.append(" ");
|
||||
sb.append(s);
|
||||
});
|
||||
this.remoteVMOptions = sb.toString();
|
||||
try (ServerSocket listener = new ServerSocket(0)) {
|
||||
// timeout after 60 seconds
|
||||
listener.setSoTimeout(60000);
|
||||
int port = listener.getLocalPort();
|
||||
jdiGo(port);
|
||||
connection = new JDIConnection(this, port, execEnv.extraRemoteVMOptions(), isLaunch);
|
||||
this.socket = listener.accept();
|
||||
// out before in -- match remote creation so we don't hang
|
||||
this.remoteOut = new ObjectOutputStream(socket.getOutputStream());
|
||||
@ -109,16 +123,15 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
JDIConnection c = jdiEnv.connection();
|
||||
if (c != null) {
|
||||
c.beginShutdown();
|
||||
if (connection != null) {
|
||||
connection.beginShutdown();
|
||||
}
|
||||
if (remoteOut != null) {
|
||||
remoteOut.writeInt(CMD_EXIT);
|
||||
remoteOut.flush();
|
||||
}
|
||||
if (c != null) {
|
||||
c.disposeVM();
|
||||
if (connection != null) {
|
||||
connection.disposeVM();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
|
||||
@ -185,9 +198,9 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
return result;
|
||||
}
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
if (!jdiEnv.connection().isRunning()) {
|
||||
if (!connection.isRunning()) {
|
||||
// The JDI connection is no longer live, shutdown.
|
||||
jdiEnv.shutdown();
|
||||
handleVMExit();
|
||||
} else {
|
||||
debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
|
||||
return "Execution failure: " + ex.getMessage();
|
||||
@ -221,7 +234,7 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
return result;
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
jdiEnv.shutdown();
|
||||
handleVMExit();
|
||||
} catch (IOException ex) {
|
||||
debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
|
||||
return "Execution failure: " + ex.getMessage();
|
||||
@ -273,7 +286,7 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
ci -> ci.getReferenceTypeOrNull(),
|
||||
ci -> ci.getBytes()));
|
||||
// Attempt redefine. Throws exceptions on failure.
|
||||
jdiEnv.vm().redefineClasses(rmp);
|
||||
connection.vm().redefineClasses(rmp);
|
||||
// Successful: mark the bytes as loaded.
|
||||
infos.stream()
|
||||
.forEach(ci -> ci.markLoaded());
|
||||
@ -287,6 +300,24 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
}
|
||||
}
|
||||
|
||||
// the VM has gone down in flames or because user evaled System.exit() or the like
|
||||
void handleVMExit() {
|
||||
if (connection != null) {
|
||||
// If there is anything left dispose of it
|
||||
connection.disposeVM();
|
||||
}
|
||||
// Tell JShell-core that the VM has died
|
||||
execEnv.closeDown();
|
||||
}
|
||||
|
||||
// Lazy init class tracker
|
||||
private ClassTracker classTracker() {
|
||||
if (classTracker == null) {
|
||||
classTracker = new ClassTracker(connection.vm());
|
||||
}
|
||||
return classTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a collection of class names into ClassInfo instances associated
|
||||
* with the most recently compiled class bytes.
|
||||
@ -296,7 +327,7 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
*/
|
||||
private List<ClassInfo> withBytes(Collection<String> classes) {
|
||||
return classes.stream()
|
||||
.map(cn -> tracker.classInfo(cn, execEnv.getClassBytes(cn)))
|
||||
.map(cn -> classTracker().classInfo(cn, execEnv.getClassBytes(cn)))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
@ -310,7 +341,7 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
*/
|
||||
@Override
|
||||
public ClassStatus getClassStatus(String classname) {
|
||||
ClassInfo ci = tracker.get(classname);
|
||||
ClassInfo ci = classTracker().get(classname);
|
||||
if (ci.getReferenceTypeOrNull() == null) {
|
||||
// If the class does not have a JDI ReferenceType it has not been loaded
|
||||
return ClassStatus.UNKNOWN;
|
||||
@ -403,37 +434,6 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
return elems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the remote agent as a JDI connection.
|
||||
*
|
||||
* @param port the socket port for (non-JDI) commands
|
||||
*/
|
||||
private void jdiGo(int port) {
|
||||
//MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
|
||||
// Locale.getDefault());
|
||||
|
||||
// Set-up for a fresh launch of a remote agent with any user-specified VM options.
|
||||
String connectorName = "com.sun.jdi.CommandLineLaunch";
|
||||
Map<String, String> argumentName2Value = new HashMap<>();
|
||||
argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
|
||||
argumentName2Value.put("options", remoteVMOptions);
|
||||
|
||||
boolean launchImmediately = true;
|
||||
int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
|
||||
|
||||
// Launch.
|
||||
jdiEnv.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
|
||||
|
||||
if (jdiEnv.connection().isOpen() && jdiEnv.vm().canBeModified()) {
|
||||
/*
|
||||
* Connection opened on startup. Start event handler
|
||||
* immediately, telling it (through arg 2) to stop on the
|
||||
* VM start event.
|
||||
*/
|
||||
handler = new JDIEventHandler(jdiEnv);
|
||||
}
|
||||
}
|
||||
|
||||
private final Object STOP_LOCK = new Object();
|
||||
private boolean userCodeRunning = false;
|
||||
|
||||
@ -447,7 +447,7 @@ public class JDIExecutionControl implements ExecutionControl {
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualMachine vm = handler.env.vm();
|
||||
VirtualMachine vm = connection.vm();
|
||||
vm.suspend();
|
||||
try {
|
||||
OUTER:
|
||||
|
@ -44,6 +44,7 @@ import java.util.function.Consumer;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import jdk.internal.jshell.debug.InternalDebugControl;
|
||||
import jdk.internal.jshell.jdi.FailOverExecutionControl;
|
||||
import static java.util.stream.Collectors.collectingAndThen;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static jdk.jshell.Util.expunge;
|
||||
@ -112,7 +113,9 @@ public class JShell implements AutoCloseable {
|
||||
this.idGenerator = b.idGenerator;
|
||||
this.extraRemoteVMOptions = b.extraRemoteVMOptions;
|
||||
this.executionControl = b.executionControl==null
|
||||
? new JDIExecutionControl()
|
||||
? new FailOverExecutionControl(
|
||||
new JDIExecutionControl(),
|
||||
new JDIExecutionControl(false))
|
||||
: b.executionControl;
|
||||
|
||||
this.maps = new SnippetMaps(this);
|
||||
@ -759,7 +762,11 @@ public class JShell implements AutoCloseable {
|
||||
if (!closed) {
|
||||
// Send only once
|
||||
closed = true;
|
||||
notifyShutdownEvent(this);
|
||||
try {
|
||||
notifyShutdownEvent(this);
|
||||
} catch (Throwable thr) {
|
||||
// Don't care about dying exceptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,40 +21,14 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8156101
|
||||
* @summary Tests for ExecutionControl SPI
|
||||
* @build KullaTesting LocalExecutionControl
|
||||
* @run testng ExecutionControlTest
|
||||
*/
|
||||
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import jdk.jshell.VarSnippet;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static jdk.jshell.Snippet.Status.VALID;
|
||||
import static jdk.jshell.Snippet.SubKind.*;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
||||
@Test
|
||||
public class ExecutionControlTest extends KullaTesting {
|
||||
|
||||
@BeforeMethod
|
||||
@Override
|
||||
public void setUp() {
|
||||
setUp(new LocalExecutionControl());
|
||||
}
|
||||
|
||||
public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
|
||||
System.setProperty("LOCAL_CHECK", "TBD");
|
||||
assertEquals(System.getProperty("LOCAL_CHECK"), "TBD");
|
||||
assertEval("System.setProperty(\"LOCAL_CHECK\", \"local\")");
|
||||
assertEquals(System.getProperty("LOCAL_CHECK"), "local");
|
||||
}
|
||||
public class ExecutionControlTestBase extends KullaTesting {
|
||||
|
||||
public void classesDeclaration() {
|
||||
assertEval("interface A { }");
|
||||
@ -71,7 +45,6 @@ public class ExecutionControlTest extends KullaTesting {
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interfaceTest() {
|
||||
String interfaceSource
|
||||
= "interface A {\n"
|
104
langtools/test/jdk/jshell/FailOverExecutionControlTest.java
Normal file
104
langtools/test/jdk/jshell/FailOverExecutionControlTest.java
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8131029
|
||||
* @summary Test that fail-over works for FailOverExecutionControl
|
||||
* @modules jdk.jshell/jdk.internal.jshell.jdi
|
||||
* jdk.jshell/jdk.jshell.spi
|
||||
* @build KullaTesting ExecutionControlTestBase
|
||||
* @run testng FailOverExecutionControlTest
|
||||
*/
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import jdk.internal.jshell.jdi.FailOverExecutionControl;
|
||||
import jdk.internal.jshell.jdi.JDIExecutionControl;
|
||||
import jdk.jshell.JShellException;
|
||||
import jdk.jshell.spi.ExecutionControl;
|
||||
import jdk.jshell.spi.ExecutionEnv;
|
||||
|
||||
@Test
|
||||
public class FailOverExecutionControlTest extends ExecutionControlTestBase {
|
||||
|
||||
@BeforeMethod
|
||||
@Override
|
||||
public void setUp() {
|
||||
setUp(new FailOverExecutionControl(
|
||||
new AlwaysFailingExecutionControl(),
|
||||
new AlwaysFailingExecutionControl(),
|
||||
new JDIExecutionControl()));
|
||||
}
|
||||
|
||||
class AlwaysFailingExecutionControl implements ExecutionControl {
|
||||
|
||||
@Override
|
||||
public void start(ExecutionEnv env) throws Exception {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addToClasspath(String path) {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String invoke(String classname, String methodname) throws JShellException {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load(Collection<String> classes) {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean redefine(Collection<String> classes) {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassStatus getClassStatus(String classname) {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String varValue(String classname, String varname) {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8131029
|
||||
* @summary Tests for alternate JDI connector -- listening
|
||||
* @modules jdk.jshell/jdk.internal.jshell.jdi
|
||||
* @build KullaTesting ExecutionControlTestBase
|
||||
* @run testng JDIListeningExecutionControlTest
|
||||
*/
|
||||
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import jdk.internal.jshell.jdi.JDIExecutionControl;
|
||||
|
||||
@Test
|
||||
public class JDIListeningExecutionControlTest extends ExecutionControlTestBase {
|
||||
|
||||
@BeforeMethod
|
||||
@Override
|
||||
public void setUp() {
|
||||
setUp(new JDIExecutionControl(false));
|
||||
}
|
||||
}
|
@ -560,6 +560,7 @@ public class ToolBasicTest extends ReplToolTesting {
|
||||
);
|
||||
}
|
||||
|
||||
@Test(enabled = false) // TODO 8158197
|
||||
public void testHeadlessEditPad() {
|
||||
String prevHeadless = System.getProperty("java.awt.headless");
|
||||
try {
|
||||
|
53
langtools/test/jdk/jshell/UserExecutionControlTest.java
Normal file
53
langtools/test/jdk/jshell/UserExecutionControlTest.java
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8156101
|
||||
* @summary Tests for ExecutionControl SPI
|
||||
* @build KullaTesting LocalExecutionControl ExecutionControlTestBase
|
||||
* @run testng UserExecutionControlTest
|
||||
*/
|
||||
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
||||
@Test
|
||||
public class UserExecutionControlTest extends ExecutionControlTestBase {
|
||||
|
||||
@BeforeMethod
|
||||
@Override
|
||||
public void setUp() {
|
||||
setUp(new LocalExecutionControl());
|
||||
}
|
||||
|
||||
public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
|
||||
System.setProperty("LOCAL_CHECK", "TBD");
|
||||
assertEquals(System.getProperty("LOCAL_CHECK"), "TBD");
|
||||
assertEval("System.setProperty(\"LOCAL_CHECK\", \"local\")");
|
||||
assertEquals(System.getProperty("LOCAL_CHECK"), "local");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user