langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIExecutionControl.java
changeset 38836 b09d1cfbf28c
parent 38608 691b607bbcd6
equal deleted inserted replaced
38835:37280d52d723 38836:b09d1cfbf28c
    32 import java.io.ObjectInputStream;
    32 import java.io.ObjectInputStream;
    33 import java.io.ObjectOutputStream;
    33 import java.io.ObjectOutputStream;
    34 import java.io.PrintStream;
    34 import java.io.PrintStream;
    35 import java.net.ServerSocket;
    35 import java.net.ServerSocket;
    36 import java.net.Socket;
    36 import java.net.Socket;
    37 import com.sun.jdi.*;
       
    38 import java.io.EOFException;
    37 import java.io.EOFException;
    39 import java.util.Arrays;
    38 import java.util.Arrays;
    40 import java.util.Collection;
    39 import java.util.Collection;
    41 import java.util.HashMap;
       
    42 import java.util.List;
    40 import java.util.List;
    43 import java.util.Map;
    41 import java.util.Map;
       
    42 import com.sun.jdi.BooleanValue;
       
    43 import com.sun.jdi.ClassNotLoadedException;
       
    44 import com.sun.jdi.IncompatibleThreadStateException;
       
    45 import com.sun.jdi.InvalidTypeException;
       
    46 import com.sun.jdi.ObjectReference;
       
    47 import com.sun.jdi.ReferenceType;
       
    48 import com.sun.jdi.StackFrame;
       
    49 import com.sun.jdi.ThreadReference;
       
    50 import com.sun.jdi.VirtualMachine;
    44 import static java.util.stream.Collectors.toList;
    51 import static java.util.stream.Collectors.toList;
    45 import jdk.jshell.JShellException;
    52 import jdk.jshell.JShellException;
    46 import jdk.jshell.spi.ExecutionControl;
    53 import jdk.jshell.spi.ExecutionControl;
    47 import jdk.jshell.spi.ExecutionEnv;
    54 import jdk.jshell.spi.ExecutionEnv;
    48 import jdk.internal.jshell.jdi.ClassTracker.ClassInfo;
    55 import jdk.internal.jshell.jdi.ClassTracker.ClassInfo;
    57  * Launches a remote process.
    64  * Launches a remote process.
    58  */
    65  */
    59 public class JDIExecutionControl implements ExecutionControl {
    66 public class JDIExecutionControl implements ExecutionControl {
    60 
    67 
    61     ExecutionEnv execEnv;
    68     ExecutionEnv execEnv;
    62     JDIEnv jdiEnv;
    69     private final boolean isLaunch;
    63     private ClassTracker tracker;
    70     private JDIConnection connection;
    64     private JDIEventHandler handler;
    71     private ClassTracker classTracker;
    65     private Socket socket;
    72     private Socket socket;
    66     private ObjectInputStream remoteIn;
    73     private ObjectInputStream remoteIn;
    67     private ObjectOutputStream remoteOut;
    74     private ObjectOutputStream remoteOut;
    68     private String remoteVMOptions;
    75 
       
    76     /**
       
    77      * Creates an ExecutionControl instance based on JDI.
       
    78      *
       
    79      * @param isLaunch true for LaunchingConnector; false for ListeningConnector
       
    80      */
       
    81     public JDIExecutionControl(boolean isLaunch) {
       
    82         this.isLaunch = isLaunch;
       
    83     }
       
    84 
       
    85     /**
       
    86      * Creates an ExecutionControl instance based on a JDI LaunchingConnector.
       
    87      */
       
    88     public JDIExecutionControl() {
       
    89         this.isLaunch = true;
       
    90     }
    69 
    91 
    70     /**
    92     /**
    71      * Initializes the launching JDI execution engine. Initialize JDI and use it
    93      * Initializes the launching JDI execution engine. Initialize JDI and use it
    72      * to launch the remote JVM. Set-up control and result communications socket
    94      * to launch the remote JVM. Set-up control and result communications socket
    73      * to the remote execution environment. This socket also transports the
    95      * to the remote execution environment. This socket also transports the
    77      * @throws IOException
    99      * @throws IOException
    78      */
   100      */
    79     @Override
   101     @Override
    80     public void start(ExecutionEnv execEnv) throws IOException {
   102     public void start(ExecutionEnv execEnv) throws IOException {
    81         this.execEnv = execEnv;
   103         this.execEnv = execEnv;
    82         this.jdiEnv = new JDIEnv(this);
       
    83         this.tracker = new ClassTracker(jdiEnv);
       
    84         StringBuilder sb = new StringBuilder();
   104         StringBuilder sb = new StringBuilder();
    85         execEnv.extraRemoteVMOptions().stream()
       
    86                 .forEach(s -> {
       
    87                     sb.append(" ");
       
    88                     sb.append(s);
       
    89                 });
       
    90         this.remoteVMOptions = sb.toString();
       
    91         try (ServerSocket listener = new ServerSocket(0)) {
   105         try (ServerSocket listener = new ServerSocket(0)) {
    92             // timeout after 60 seconds
   106             // timeout after 60 seconds
    93             listener.setSoTimeout(60000);
   107             listener.setSoTimeout(60000);
    94             int port = listener.getLocalPort();
   108             int port = listener.getLocalPort();
    95             jdiGo(port);
   109             connection = new JDIConnection(this, port, execEnv.extraRemoteVMOptions(), isLaunch);
    96             this.socket = listener.accept();
   110             this.socket = listener.accept();
    97             // out before in -- match remote creation so we don't hang
   111             // out before in -- match remote creation so we don't hang
    98             this.remoteOut = new ObjectOutputStream(socket.getOutputStream());
   112             this.remoteOut = new ObjectOutputStream(socket.getOutputStream());
    99             PipeInputStream commandIn = new PipeInputStream();
   113             PipeInputStream commandIn = new PipeInputStream();
   100             new DemultiplexInput(socket.getInputStream(), commandIn, execEnv.userOut(), execEnv.userErr()).start();
   114             new DemultiplexInput(socket.getInputStream(), commandIn, execEnv.userOut(), execEnv.userErr()).start();
   107      * Shuts down the JDI connection. Should this close the socket?
   121      * Shuts down the JDI connection. Should this close the socket?
   108      */
   122      */
   109     @Override
   123     @Override
   110     public void close() {
   124     public void close() {
   111         try {
   125         try {
   112             JDIConnection c = jdiEnv.connection();
   126             if (connection != null) {
   113             if (c != null) {
   127                 connection.beginShutdown();
   114                 c.beginShutdown();
       
   115             }
   128             }
   116             if (remoteOut != null) {
   129             if (remoteOut != null) {
   117                 remoteOut.writeInt(CMD_EXIT);
   130                 remoteOut.writeInt(CMD_EXIT);
   118                 remoteOut.flush();
   131                 remoteOut.flush();
   119             }
   132             }
   120             if (c != null) {
   133             if (connection != null) {
   121                 c.disposeVM();
   134                 connection.disposeVM();
   122             }
   135             }
   123         } catch (IOException ex) {
   136         } catch (IOException ex) {
   124             debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
   137             debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
   125         }
   138         }
   126     }
   139     }
   183             if (readAndReportExecutionResult()) {
   196             if (readAndReportExecutionResult()) {
   184                 String result = remoteIn.readUTF();
   197                 String result = remoteIn.readUTF();
   185                 return result;
   198                 return result;
   186             }
   199             }
   187         } catch (IOException | RuntimeException ex) {
   200         } catch (IOException | RuntimeException ex) {
   188             if (!jdiEnv.connection().isRunning()) {
   201             if (!connection.isRunning()) {
   189                 // The JDI connection is no longer live, shutdown.
   202                 // The JDI connection is no longer live, shutdown.
   190                 jdiEnv.shutdown();
   203                 handleVMExit();
   191             } else {
   204             } else {
   192                 debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
   205                 debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
   193                 return "Execution failure: " + ex.getMessage();
   206                 return "Execution failure: " + ex.getMessage();
   194             }
   207             }
   195         } finally {
   208         } finally {
   219             if (readAndReportResult()) {
   232             if (readAndReportResult()) {
   220                 String result = remoteIn.readUTF();
   233                 String result = remoteIn.readUTF();
   221                 return result;
   234                 return result;
   222             }
   235             }
   223         } catch (EOFException ex) {
   236         } catch (EOFException ex) {
   224             jdiEnv.shutdown();
   237             handleVMExit();
   225         } catch (IOException ex) {
   238         } catch (IOException ex) {
   226             debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
   239             debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
   227             return "Execution failure: " + ex.getMessage();
   240             return "Execution failure: " + ex.getMessage();
   228         }
   241         }
   229         return "";
   242         return "";
   271             Map<ReferenceType, byte[]> rmp = infos.stream()
   284             Map<ReferenceType, byte[]> rmp = infos.stream()
   272                     .collect(toMap(
   285                     .collect(toMap(
   273                             ci -> ci.getReferenceTypeOrNull(),
   286                             ci -> ci.getReferenceTypeOrNull(),
   274                             ci -> ci.getBytes()));
   287                             ci -> ci.getBytes()));
   275             // Attempt redefine.  Throws exceptions on failure.
   288             // Attempt redefine.  Throws exceptions on failure.
   276             jdiEnv.vm().redefineClasses(rmp);
   289             connection.vm().redefineClasses(rmp);
   277             // Successful: mark the bytes as loaded.
   290             // Successful: mark the bytes as loaded.
   278             infos.stream()
   291             infos.stream()
   279                     .forEach(ci -> ci.markLoaded());
   292                     .forEach(ci -> ci.markLoaded());
   280             return true;
   293             return true;
   281         } catch (UnsupportedOperationException ex) {
   294         } catch (UnsupportedOperationException ex) {
   285             debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
   298             debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
   286             return false;
   299             return false;
   287         }
   300         }
   288     }
   301     }
   289 
   302 
       
   303     // the VM has gone down in flames or because user evaled System.exit() or the like
       
   304     void handleVMExit() {
       
   305         if (connection != null) {
       
   306             // If there is anything left dispose of it
       
   307             connection.disposeVM();
       
   308         }
       
   309         // Tell JShell-core that the VM has died
       
   310         execEnv.closeDown();
       
   311     }
       
   312 
       
   313     // Lazy init class tracker
       
   314     private ClassTracker classTracker() {
       
   315         if (classTracker == null) {
       
   316             classTracker = new ClassTracker(connection.vm());
       
   317         }
       
   318         return classTracker;
       
   319     }
       
   320 
   290     /**
   321     /**
   291      * Converts a collection of class names into ClassInfo instances associated
   322      * Converts a collection of class names into ClassInfo instances associated
   292      * with the most recently compiled class bytes.
   323      * with the most recently compiled class bytes.
   293      *
   324      *
   294      * @param classes names of the classes
   325      * @param classes names of the classes
   295      * @return a list of corresponding ClassInfo instances
   326      * @return a list of corresponding ClassInfo instances
   296      */
   327      */
   297     private List<ClassInfo> withBytes(Collection<String> classes) {
   328     private List<ClassInfo> withBytes(Collection<String> classes) {
   298         return classes.stream()
   329         return classes.stream()
   299                 .map(cn -> tracker.classInfo(cn, execEnv.getClassBytes(cn)))
   330                 .map(cn -> classTracker().classInfo(cn, execEnv.getClassBytes(cn)))
   300                 .collect(toList());
   331                 .collect(toList());
   301     }
   332     }
   302 
   333 
   303     /**
   334     /**
   304      * Reports the status of the named class. UNKNOWN if not loaded. CURRENT if
   335      * Reports the status of the named class. UNKNOWN if not loaded. CURRENT if
   308      * @param classname the name of the class to test
   339      * @param classname the name of the class to test
   309      * @return the status
   340      * @return the status
   310      */
   341      */
   311     @Override
   342     @Override
   312     public ClassStatus getClassStatus(String classname) {
   343     public ClassStatus getClassStatus(String classname) {
   313         ClassInfo ci = tracker.get(classname);
   344         ClassInfo ci = classTracker().get(classname);
   314         if (ci.getReferenceTypeOrNull() == null) {
   345         if (ci.getReferenceTypeOrNull() == null) {
   315             // If the class does not have a JDI ReferenceType it has not been loaded
   346             // If the class does not have a JDI ReferenceType it has not been loaded
   316             return ClassStatus.UNKNOWN;
   347             return ClassStatus.UNKNOWN;
   317         }
   348         }
   318         // Compare successfully loaded with last compiled bytes.
   349         // Compare successfully loaded with last compiled bytes.
   401             elems[i] = new StackTraceElement(className, methodName, fileName, line);
   432             elems[i] = new StackTraceElement(className, methodName, fileName, line);
   402         }
   433         }
   403         return elems;
   434         return elems;
   404     }
   435     }
   405 
   436 
   406     /**
       
   407      * Launch the remote agent as a JDI connection.
       
   408      *
       
   409      * @param port the socket port for (non-JDI) commands
       
   410      */
       
   411     private void jdiGo(int port) {
       
   412         //MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
       
   413         //        Locale.getDefault());
       
   414 
       
   415         // Set-up for a fresh launch of a remote agent with any user-specified VM options.
       
   416         String connectorName = "com.sun.jdi.CommandLineLaunch";
       
   417         Map<String, String> argumentName2Value = new HashMap<>();
       
   418         argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
       
   419         argumentName2Value.put("options", remoteVMOptions);
       
   420 
       
   421         boolean launchImmediately = true;
       
   422         int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
       
   423 
       
   424         // Launch.
       
   425         jdiEnv.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
       
   426 
       
   427         if (jdiEnv.connection().isOpen() && jdiEnv.vm().canBeModified()) {
       
   428             /*
       
   429              * Connection opened on startup. Start event handler
       
   430              * immediately, telling it (through arg 2) to stop on the
       
   431              * VM start event.
       
   432              */
       
   433             handler = new JDIEventHandler(jdiEnv);
       
   434         }
       
   435     }
       
   436 
       
   437     private final Object STOP_LOCK = new Object();
   437     private final Object STOP_LOCK = new Object();
   438     private boolean userCodeRunning = false;
   438     private boolean userCodeRunning = false;
   439 
   439 
   440     /**
   440     /**
   441      * Interrupt a running invoke.
   441      * Interrupt a running invoke.
   445         synchronized (STOP_LOCK) {
   445         synchronized (STOP_LOCK) {
   446             if (!userCodeRunning) {
   446             if (!userCodeRunning) {
   447                 return;
   447                 return;
   448             }
   448             }
   449 
   449 
   450             VirtualMachine vm = handler.env.vm();
   450             VirtualMachine vm = connection.vm();
   451             vm.suspend();
   451             vm.suspend();
   452             try {
   452             try {
   453                 OUTER:
   453                 OUTER:
   454                 for (ThreadReference thread : vm.allThreads()) {
   454                 for (ThreadReference thread : vm.allThreads()) {
   455                     // could also tag the thread (e.g. using name), to find it easier
   455                     // could also tag the thread (e.g. using name), to find it easier