Skip to content

Commit

Permalink
Add debug restart
Browse files Browse the repository at this point in the history
Signed-off-by: Adam Wisniewski <[email protected]>
  • Loading branch information
Adam Wisniewski authored and awisniew90 committed Jul 11, 2024
1 parent 42e706d commit 14d87c7
Show file tree
Hide file tree
Showing 7 changed files with 422 additions and 79 deletions.
4 changes: 3 additions & 1 deletion bundles/io.openliberty.tools.eclipse.ui/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ Export-Package: io.openliberty.tools.eclipse;x-friends:="io.openliberty.tools.ec
io.openliberty.tools.eclipse.ui.launch.shortcuts;x-friends:="io.openliberty.tools.eclipse.tests",
io.openliberty.tools.eclipse.ui.preferences;x-friends:="io.openliberty.tools.eclipse.tests"
Require-Bundle: org.eclipse.ui,
org.eclipse.m2e.maven.runtime
org.eclipse.m2e.maven.runtime,
org.eclipse.jdt.debug.ui,
org.eclipse.jdt.debug
Bundle-RequiredExecutionEnvironment: JavaSE-17
Automatic-Module-Name: io.openliberty.tools.eclipse.ui
Bundle-ActivationPolicy: lazy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -241,7 +240,7 @@ public void start(IProject iProject, String parms, String javaHomePath, ILaunch

// If there is a debugPort, start the job to attach the debugger to the Liberty server JVM.
if (debugPort != null) {
debugModeHandler.startDebugAttacher(project, launch, debugPort);
debugModeHandler.startDebugAttacher(project, launch, debugPort, false);
}
} catch (CommandNotFoundException e) {
String msg = "Maven or Gradle command not found for project " + projectName;
Expand Down Expand Up @@ -356,7 +355,7 @@ public void startInContainer(IProject iProject, String parms, String javaHomePat

// If there is a debugPort, start the job to attach the debugger to the Liberty server JVM.
if (debugPort != null) {
debugModeHandler.startDebugAttacher(project, launch, debugPort);
debugModeHandler.startDebugAttacher(project, launch, debugPort, false);
}
} catch (Exception e) {
String msg = "An error was detected during the start in container request on project " + projectName;
Expand Down Expand Up @@ -818,7 +817,7 @@ private void handleStopActionError(String projectName, String baseMsg) {
*
* @param projectName The name of the project for which the the Liberty plugin stop command is issued.
*/
private void issueLPStopCommand(String projectName) {
public void issueLPStopCommand(String projectName) {
if (Trace.isEnabled()) {
Trace.getTracer().traceEntry(Trace.TRACE_TOOLS, projectName);
}
Expand Down Expand Up @@ -989,6 +988,12 @@ public static Path getMavenIntegrationTestReportPath(String projectPath) {
return path;
}

public Path getLibertyPluginConfigXmlPath(String projectPath) {
Path path = Paths.get(projectPath, "target", "liberty-plugin-config.xml");

return path;
}

/**
* Returns the path of the HTML file containing the unit test report.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@
*******************************************************************************/
package io.openliberty.tools.eclipse.debug;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
Expand All @@ -41,16 +41,16 @@
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.jdi.Bootstrap;
import org.eclipse.jdi.TimeoutException;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.AttachingConnector;
Expand All @@ -62,9 +62,10 @@
import io.openliberty.tools.eclipse.LibertyDevPlugin;
import io.openliberty.tools.eclipse.Project;
import io.openliberty.tools.eclipse.Project.BuildType;
import io.openliberty.tools.eclipse.logging.Logger;
import io.openliberty.tools.eclipse.logging.Trace;
import io.openliberty.tools.eclipse.messages.Messages;
import io.openliberty.tools.eclipse.ui.dashboard.DashboardView;
import io.openliberty.tools.eclipse.ui.launch.LaunchConfigurationHelper;
import io.openliberty.tools.eclipse.ui.terminal.ProjectTabController;
import io.openliberty.tools.eclipse.ui.terminal.TerminalListener;
import io.openliberty.tools.eclipse.utils.ErrorHandler;
Expand Down Expand Up @@ -95,6 +96,9 @@ public class DebugModeHandler {
/** Job status return code indicating that an error took place while attempting to attach the debugger to the JVM. */
public static int JOB_STATUS_DEBUGGER_CONN_ERROR = 1;

/** Instance to this class. */
private LaunchConfigurationHelper launchConfigHelper = LaunchConfigurationHelper.getInstance();

/** DevModeOperations instance. */
private DevModeOperations devModeOps;

Expand Down Expand Up @@ -217,7 +221,7 @@ public String calculateDebugPort(Project project, String inputParms) throws Exce
*
* @throws Exception
*/
public void startDebugAttacher(Project project, ILaunch launch, String debugPort) {
public void startDebugAttacher(Project project, ILaunch launch, String port, boolean readDebugPort) {
String projectName = project.getIProject().getName();

Job job = new Job("Attaching Debugger to JVM...") {
Expand All @@ -228,6 +232,27 @@ protected IStatus run(IProgressMonitor monitor) {
return Status.CANCEL_STATUS;
}

String debugPort = null;

if (readDebugPort) {
// Read debug port from server.env. If devmode has restarted the server, it may
// take a few seconds for it to find a new port in the event that the previous port
// is still being held by the OS.
Thread.sleep(5000);

try {
File serverEnvFile = getServerEnvFile(project);
if (serverEnvFile != null) {
debugPort = readDebugPortFromServerEnv(serverEnvFile);
}
} catch (Exception e) {
Logger.logError("Failure while attempting to read debug port from server.env file", e);
}
}
if (debugPort == null) {
debugPort = port;
}

String portToConnect = waitForSocketActivation(project, DEFAULT_ATTACH_HOST, debugPort, monitor);
if (portToConnect == null) {
return Status.CANCEL_STATUS;
Expand All @@ -236,10 +261,14 @@ protected IStatus run(IProgressMonitor monitor) {
AttachingConnector connector = getAttachingConnector();
Map<String, Argument> map = connector.defaultArguments();
configureConnector(map, DEFAULT_ATTACH_HOST, Integer.parseInt(portToConnect));
IDebugTarget debugTarget = createRemoteJDTDebugTarget(launch, Integer.parseInt(portToConnect), DEFAULT_ATTACH_HOST,
IDebugTarget debugTarget = createRemoteJDTDebugTarget(launch, project, Integer.parseInt(portToConnect),
DEFAULT_ATTACH_HOST,
connector, map);

launch.addDebugTarget(debugTarget);
for (IDebugTarget target : launch.getDebugTargets()) {
System.out.println("DebugTarget: " + target.getName());
}

} catch (Exception e) {
return new Status(IStatus.ERROR, LibertyDevPlugin.PLUGIN_ID, JOB_STATUS_DEBUGGER_CONN_ERROR,
Expand Down Expand Up @@ -334,7 +363,7 @@ private void configureConnector(Map<String, Argument> map, String host, int port
}
}

private IDebugTarget createRemoteJDTDebugTarget(ILaunch launch, int remoteDebugPortNum, String hostName,
private IDebugTarget createRemoteJDTDebugTarget(ILaunch launch, Project project, int remoteDebugPortNum, String hostName,
AttachingConnector connector, Map<String, Argument> map) throws CoreException {
if (launch == null || hostName == null || hostName.length() == 0) {
return null;
Expand All @@ -351,8 +380,13 @@ private IDebugTarget createRemoteJDTDebugTarget(ILaunch launch, int remoteDebugP
throw new CoreException(
new Status(IStatus.ERROR, this.getClass(), IJavaLaunchConfigurationConstants.ERR_CONNECTION_FAILED, "", ex));
}
debugTarget = JDIDebugModel.newDebugTarget(launch, remoteVM, hostName + ":" + remoteDebugPortNum, null, true, false, true);
return debugTarget;
LibertyDebugTarget libertyDebugTarget = new LibertyDebugTarget(launch, remoteVM, hostName + ":" + remoteDebugPortNum,
new RestartDebugger(project, launch, String.valueOf(remoteDebugPortNum)));

// Add hot code replace listener to listen for hot code replace failure.
libertyDebugTarget.addHotCodeReplaceListener(new LibertyHotCodeReplaceListener());

return libertyDebugTarget;
}

/**
Expand Down Expand Up @@ -461,64 +495,41 @@ private void openDebugPerspective() {
}

/**
* Returns the default path of the server.env file after Liberty server deployment.
* Returns the path of the server.env file after Liberty server deployment.
*
* @param project The project for which this operations is being performed.
*
* @return The default path of the server.env file after Liberty server deployment.
* @return The path of the server.env file after Liberty server deployment.
*
* @throws Exception
*/
private Path getServerEnvPath(Project project) throws Exception {
Path basePath = null;
private File getServerEnvFile(Project project) throws Exception {

Project serverProj = getLibertyServerProject(project);
String projectPath = serverProj.getPath();
String projectName = serverProj.getName();
BuildType buildType = serverProj.getBuildType();

if (buildType == Project.BuildType.MAVEN) {
basePath = Paths.get(projectPath, "target", "liberty", "wlp", "usr", "servers");
} else if (buildType == Project.BuildType.GRADLE) {
basePath = Paths.get(projectPath, "build", "wlp", "usr", "servers");
} else {
throw new Exception("Unexpected project build type: " + buildType + ". Project" + projectName
+ "does not appear to be a Maven or Gradle built project.");
}
Path libertyPluginConfigXmlPath = devModeOps.getLibertyPluginConfigXmlPath(projectPath);

// Read server.env path from liberty-plugin-config.xml

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder db = dbf.newDocumentBuilder();

// Make sure the base path exists. If not return null.
File basePathFile = new File(basePath.toString());
if (!basePathFile.exists()) {
Document doc = db.parse(libertyPluginConfigXmlPath.toFile());
doc.getDocumentElement().normalize();

NodeList list = doc.getElementsByTagName("serverDirectory");
Path serverEnvPath = Paths.get(list.item(0).getTextContent(), "server.env");

File serverEnvFile = serverEnvPath.toFile();
// Make sure the server.env path exists. If not return null.
if (!serverEnvFile.exists()) {
return null;
}

try (Stream<Path> matchedStream = Files.find(basePath, 2, (path, basicFileAttribute) -> {
if (basicFileAttribute.isRegularFile()) {
return path.getFileName().toString().equalsIgnoreCase(WLP_SERVER_ENV_FILE_NAME);
}
return false;
});) {
List<Path> matchedPaths = matchedStream.collect(Collectors.toList());
int numberOfFilesFound = matchedPaths.size();

if (numberOfFilesFound != 1) {
if (numberOfFilesFound == 0) {
String msg = "Unable to find the server.env file for project " + projectName + ".";
if (Trace.isEnabled()) {
Trace.getTracer().trace(Trace.TRACE_UI, msg);
}
return null;
} else {
String msg = "More than one server.env file was found for project " + projectName
+ ". Unable to determine which server.env file to use.";
if (Trace.isEnabled()) {
Trace.getTracer().trace(Trace.TRACE_UI, msg);
}
ErrorHandler.processErrorMessage(NLS.bind(Messages.multiple_server_env, projectName), false);
throw new Exception(msg);
}
}
return matchedPaths.get(0);
}
return serverEnvFile;

}

/**
Expand All @@ -536,19 +547,25 @@ public String readDebugPortFromServerEnv(File serverEnv) throws Exception {
String port = null;

if (serverEnv.exists()) {
try (BufferedReader reader = new BufferedReader(new FileReader(serverEnv))) {
String line = null;
String lastEntry = null;
while ((line = reader.readLine()) != null) {
if (line.contains(WLP_ENV_DEBUG_ADDRESS)) {
lastEntry = line;
}
}
if (lastEntry != null) {
String[] parts = lastEntry.split("=");
port = parts[1].trim();
String lastEntry = null;

Scanner scan = new Scanner(serverEnv);
String line;

while (scan.hasNextLine()) {
line = scan.nextLine();
if (line.contains(WLP_ENV_DEBUG_ADDRESS)) {
lastEntry = line;
}
}

scan.close();

if (lastEntry != null) {
String[] parts = lastEntry.split("=");
port = parts[1].trim();
}

}

return port;
Expand All @@ -574,7 +591,6 @@ private String waitForSocketActivation(Project project, String host, String port
// this timeout value, we could potentially tie it to the "Launch timeout" setting of in the
// Eclipse "Debug" preferences.

byte[] handshakeString = "JDWP-Handshake".getBytes(StandardCharsets.US_ASCII);
int retryLimit = 180;
int envReadInterval = 2;

Expand Down Expand Up @@ -605,18 +621,28 @@ public void run() {
}
}

try (Socket socket = new Socket(host, Integer.valueOf(port))) {
socket.getOutputStream().write(handshakeString);
if (socketIsActivated(host, port)) {
return port;
} catch (ConnectException ce) {
TimeUnit.SECONDS.sleep(1);
}
}

throw new Exception("Timed out trying to attach the debugger to JVM on host: " + host + " and port: " + port
+ ". If the server starts later you might try to manually create a Remote Java Application debug configuration and attach to the server. You can confirm the debug port used in the terminal output looking for a message like 'Liberty debug port: [ 63624 ]'.");
}

private boolean socketIsActivated(String host, String port) throws Exception {
byte[] handshakeString = "JDWP-Handshake".getBytes(StandardCharsets.US_ASCII);

try (Socket socket = new Socket(host, Integer.valueOf(port))) {
socket.getOutputStream().write(handshakeString);
return true;
} catch (ConnectException ce) {
TimeUnit.SECONDS.sleep(1);
}

return false;
}

/**
* Returns the liberty server module project associated with the input project.
*
Expand Down Expand Up @@ -645,4 +671,21 @@ private Project getLibertyServerProject(Project project) throws Exception {
private class DataHolder {
boolean closed;
}

class RestartDebugger {

private Project project;
private ILaunch launch;
private String port;

RestartDebugger(Project project, ILaunch launch, String port) {
this.project = project;
this.launch = launch;
this.port = port;
}

public void restart(boolean readDebugPort) {
startDebugAttacher(project, launch, port, readDebugPort);
}
}
}
Loading

0 comments on commit 14d87c7

Please sign in to comment.