Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

package com.microsoft.java.debug.core.adapter;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
Expand Down Expand Up @@ -159,6 +161,19 @@ public static String toUri(String path) {
}
}

/**
* Convert a file path to an url string.
* @param path
* the file path
* @return the url string
* @throws MalformedURLException
* if the file path cannot be constructed to an url because of some errors.
*/
public static String toUrl(String path) throws MalformedURLException {
File file = new File(path);
return file.toURI().toURL().toString();
}

/**
* Check a string variable is an uri or not.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ public CompletableFuture<Messages.Response> dispatchRequest(Messages.Request req
Command command = Command.parse(request.command);
Arguments cmdArgs = JsonUtils.fromJson(request.arguments, command.getArgumentType());

if (debugContext.isVmTerminated()) {
// the operation is meaningless
if (debugContext.isVmTerminated() && command != Command.DISCONNECT) {
return CompletableFuture.completedFuture(response);
}
List<IDebugRequestHandler> handlers = this.debugContext.getLaunchMode() == LaunchMode.DEBUG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package com.microsoft.java.debug.core.adapter;

import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;

Expand Down Expand Up @@ -42,6 +43,8 @@ public class DebugAdapterContext implements IDebugAdapterContext {
private Process debuggeeProcess;
private String mainClass;
private StepFilters stepFilters;
private Path classpathJar = null;
private Path argsfile = null;

private IdCollection<String> sourceReferences = new IdCollection<>();
private RecyclableObjectPool<Long, Object> recyclableIdPool = new RecyclableObjectPool<>();
Expand Down Expand Up @@ -253,4 +256,24 @@ public Process getDebuggeeProcess() {
public void setDebuggeeProcess(Process debuggeeProcess) {
this.debuggeeProcess = debuggeeProcess;
}

@Override
public void setClasspathJar(Path classpathJar) {
this.classpathJar = classpathJar;
}

@Override
public Path getClasspathJar() {
return this.classpathJar;
}

@Override
public void setArgsfile(Path argsfile) {
this.argsfile = argsfile;
}

@Override
public Path getArgsfile() {
return this.argsfile;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package com.microsoft.java.debug.core.adapter;

import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Map;

import com.microsoft.java.debug.core.IDebugSession;
Expand Down Expand Up @@ -109,4 +110,12 @@ public interface IDebugAdapterContext {
Process getDebuggeeProcess();

void setDebuggeeProcess(Process debuggeeProcess);

void setClasspathJar(Path classpathJar);

Path getClasspathJar();

void setArgsfile(Path argsfile);

Path getArgsfile();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*******************************************************************************
* Copyright (c) 2019 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/

package com.microsoft.java.debug.core.adapter.handler;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.adapter.LaunchMode;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;

public abstract class AbstractDisconnectRequestHandler implements IDebugRequestHandler {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);

@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.DISCONNECT);
}

@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
destroyDebugSession(command, arguments, response, context);
destroyResource(context);
return CompletableFuture.completedFuture(response);
}

/**
* Destroy the resources generated by the debug session.
*
* @param context the debug context
*/
private void destroyResource(IDebugAdapterContext context) {
if (shouldDestroyLaunchFiles(context)) {
destroyLaunchFiles(context);
}
}

private boolean shouldDestroyLaunchFiles(IDebugAdapterContext context) {
// Delete the temporary launch files must happen after the debuggee process is fully exited,
// otherwise it throws error saying the file is being used by other process.
// In Debug mode, the debugger is able to receive VM terminate event. It's sensible to do cleanup.
// In noDebug mode, if the debuggee is launched internally by the debugger, the debugger knows
// when the debuggee process exited. Should do cleanup. But if the debuggee is launched in the
// integrated/external terminal, the debugger lost the contact with the debuggee after it's launched.
// Have no idea when the debuggee is exited. So ignore the cleanup.
return context.getLaunchMode() == LaunchMode.DEBUG || context.getDebuggeeProcess() != null;
}

private void destroyLaunchFiles(IDebugAdapterContext context) {
// Sometimes when the debug session is terminated, the debuggee process is not exited immediately.
// Add retry to delete the temporary launch files.
int retry = 5;
while (retry-- > 0) {
try {
if (context.getClasspathJar() != null) {
Files.deleteIfExists(context.getClasspathJar());
context.setClasspathJar(null);
}

if (context.getArgsfile() != null) {
Files.deleteIfExists(context.getArgsfile());
context.setArgsfile(null);
}
} catch (IOException e) {
// do nothing.
logger.log(Level.WARNING, "Failed to destory launch files, will retry again.");
}

try {
Thread.sleep(1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {
// do nothing.
}
}
}

protected abstract void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,17 @@

package com.microsoft.java.debug.core.adapter.handler;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Requests.DisconnectArguments;

public class DisconnectRequestHandler implements IDebugRequestHandler {

@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.DISCONNECT);
}
public class DisconnectRequestHandler extends AbstractDisconnectRequestHandler {

@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
public void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
DisconnectArguments disconnectArguments = (DisconnectArguments) arguments;
IDebugSession debugSession = context.getDebugSession();
if (debugSession != null) {
Expand All @@ -41,7 +31,6 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
debugSession.detach();
}
}
return CompletableFuture.completedFuture(response);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,20 @@

package com.microsoft.java.debug.core.adapter.handler;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Requests.DisconnectArguments;

public class DisconnectRequestWithoutDebuggingHandler implements IDebugRequestHandler {
public class DisconnectRequestWithoutDebuggingHandler extends AbstractDisconnectRequestHandler {

@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.DISCONNECT);
}

@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
public void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
DisconnectArguments disconnectArguments = (DisconnectArguments) arguments;
Process debuggeeProcess = context.getDebuggeeProcess();
if (debuggeeProcess != null && disconnectArguments.terminateDebuggee) {
debuggeeProcess.destroy();
}
return CompletableFuture.completedFuture(response);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,24 @@
package com.microsoft.java.debug.core.adapter.handler;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang3.ArrayUtils;
Expand All @@ -39,6 +48,7 @@
import com.microsoft.java.debug.core.protocol.Requests.CONSOLE;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments;
import com.microsoft.java.debug.core.protocol.Requests.ShortenApproach;

public class LaunchRequestHandler implements IDebugRequestHandler {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
Expand Down Expand Up @@ -87,6 +97,62 @@ protected CompletableFuture<Response> handleLaunchCommand(Arguments arguments, R

activeLaunchHandler.preLaunch(launchArguments, context);

Path tempfile = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The body of this function is already long. Please refactor by putting the procedures of generating Jar Manifest and ArgFile into different utility functions. Those functions should be functional without referencing states from other parts. After that the body looks like:

if (launchArguments.shortenCommandLine == ShortenApproach.JARMANIFEST) {
    Path path = AdapterUtils.generateClasspathJar(...);
    context.setClasspathJar(path);
} else if (...) {
}

// Use the specified cli style to launch the program.
if (launchArguments.shortenCommandLine == ShortenApproach.JARMANIFEST) {
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
List<String> classpathUrls = new ArrayList<>();
for (String classpath : launchArguments.classPaths) {
try {
classpathUrls.add(AdapterUtils.toUrl(classpath));
} catch (IllegalArgumentException | MalformedURLException ex) {
logger.log(Level.SEVERE, String.format("Failed to launch the program with jarmanifest style: %s", ex.toString(), ex));
throw AdapterUtils.createCompletionException("Failed to launch the program with jarmanifest style: " + ex.toString(),
ErrorCode.LAUNCH_FAILURE, ex);
}
}

try {
tempfile = Files.createTempFile("classpath_", ".jar");
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
// In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar
attributes.put(Attributes.Name.CLASS_PATH, String.join(" ", classpathUrls));
JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest);
jar.close();
launchArguments.vmArgs += " -cp \"" + tempfile.toAbsolutePath().toString() + "\"";
launchArguments.classPaths = new String[0];
context.setClasspathJar(tempfile);
} catch (IOException e) {
logger.log(Level.SEVERE, String.format("Failed to create a temp classpath.jar: %s", e.toString()), e);
tempfile = null;
}
}
} else if (launchArguments.shortenCommandLine == ShortenApproach.ARGFILE) {
try {
tempfile = Files.createTempFile("java_", ".argfile");
String argfile = "";
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
argfile = "-classpath \"" + String.join(File.pathSeparator, launchArguments.classPaths) + "\"";
}

if (ArrayUtils.isNotEmpty(launchArguments.modulePaths)) {
argfile = " --module-path \"" + String.join(File.pathSeparator, launchArguments.modulePaths) + "\"";
}

argfile = argfile.replace("\\", "\\\\");
Files.write(tempfile, argfile.getBytes());
launchArguments.vmArgs += " @" + tempfile.toAbsolutePath().toString();
launchArguments.classPaths = new String[0];
launchArguments.modulePaths = new String[0];
context.setArgsfile(tempfile);
} catch (IOException e) {
logger.log(Level.SEVERE, String.format("Failed to create a temp argfile: %s", e.toString()), e);
tempfile = null;
}
}

return launch(launchArguments, response, context).thenCompose(res -> {
if (res.success) {
activeLaunchHandler.postLaunch(launchArguments, context);
Expand Down
Loading