Skip to content

Commit 7f6f76b

Browse files
Report more detail in VM start errors (microsoft#398)
* Report more detail in VM start errors Co-authored-by: Jinbo Wang <[email protected]>
1 parent fca1d23 commit 7f6f76b

File tree

3 files changed

+164
-8
lines changed

3 files changed

+164
-8
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2018-2021 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.core;
13+
14+
import com.sun.jdi.connect.VMStartException;
15+
16+
/**
17+
* Extends {@link VMStartException} to provide more detail about the failed process
18+
* from before it is destroyed.
19+
*/
20+
public class LaunchException extends VMStartException {
21+
22+
boolean exited;
23+
int exitStatus;
24+
String stdout;
25+
String stderr;
26+
27+
public LaunchException(String message, Process process, boolean exited, int exitStatus, String stdout, String stderr) {
28+
super(message, process);
29+
this.exited = exited;
30+
this.exitStatus = exitStatus;
31+
this.stdout = stdout;
32+
this.stderr = stderr;
33+
}
34+
35+
public boolean isExited() {
36+
return exited;
37+
}
38+
39+
public int getExitStatus() {
40+
return exitStatus;
41+
}
42+
43+
public String getStdout() {
44+
return stdout;
45+
}
46+
47+
public String getStderr() {
48+
return stderr;
49+
}
50+
51+
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.microsoft.java.debug.core.DebugSettings;
4242
import com.microsoft.java.debug.core.DebugUtility;
4343
import com.microsoft.java.debug.core.IDebugSession;
44+
import com.microsoft.java.debug.core.LaunchException;
4445
import com.microsoft.java.debug.core.adapter.AdapterUtils;
4546
import com.microsoft.java.debug.core.adapter.ErrorCode;
4647
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
@@ -245,6 +246,22 @@ protected CompletableFuture<Response> launch(LaunchArguments launchArguments, Re
245246
.subscribe((event) -> context.getProtocolServer().sendEvent(event));
246247
debuggeeConsole.start();
247248
resultFuture.complete(response);
249+
} catch (LaunchException e) {
250+
if (StringUtils.isNotBlank(e.getStdout())) {
251+
OutputEvent event = convertToOutputEvent(e.getStdout(), Category.stdout, context);
252+
context.getProtocolServer().sendEvent(event);
253+
}
254+
if (StringUtils.isNotBlank(e.getStderr())) {
255+
OutputEvent event = convertToOutputEvent(e.getStderr(), Category.stderr, context);
256+
context.getProtocolServer().sendEvent(event);
257+
}
258+
259+
resultFuture.completeExceptionally(
260+
new DebugException(
261+
String.format("Failed to launch debuggee VM. Reason: %s", e.getMessage()),
262+
ErrorCode.LAUNCH_FAILURE.getId()
263+
)
264+
);
248265
} catch (IOException | IllegalConnectorArgumentsException | VMStartException e) {
249266
resultFuture.completeExceptionally(
250267
new DebugException(

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,22 @@
1313

1414
import java.io.File;
1515
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.nio.charset.StandardCharsets;
1618
import java.nio.file.Files;
1719
import java.nio.file.Paths;
1820
import java.util.Map;
21+
import java.util.concurrent.CompletableFuture;
22+
import java.util.concurrent.ExecutionException;
1923

24+
import org.apache.commons.io.IOUtils;
2025
import org.eclipse.jdi.internal.VirtualMachineImpl;
2126
import org.eclipse.jdi.internal.VirtualMachineManagerImpl;
2227
import org.eclipse.jdi.internal.connect.SocketLaunchingConnectorImpl;
2328
import org.eclipse.jdi.internal.connect.SocketListeningConnectorImpl;
2429

2530
import com.microsoft.java.debug.core.DebugUtility;
31+
import com.microsoft.java.debug.core.LaunchException;
2632
import com.sun.jdi.VirtualMachine;
2733
import com.sun.jdi.connect.Connector;
2834
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
@@ -83,18 +89,100 @@ public VirtualMachine launch(Map<String, ? extends Argument> connectionArgs)
8389
String address = listenConnector.startListening(args);
8490

8591
String[] cmds = constructLaunchCommand(connectionArgs, address);
86-
Process process = Runtime.getRuntime().exec(cmds, envVars, workingDir);
8792

88-
VirtualMachineImpl vm;
93+
/* Launch the Java process */
94+
final Process process = Runtime.getRuntime().exec(cmds, envVars, workingDir);
95+
96+
/* A Future that will be completed if we successfully connect to the launched process, or
97+
will fail with an Exception if we do not.
98+
*/
99+
final CompletableFuture<VirtualMachineImpl> result = new CompletableFuture<>();
100+
101+
/* Listen for the debug connection from the Java process */
102+
CompletableFuture.runAsync(() -> {
103+
try {
104+
VirtualMachineImpl vm = (VirtualMachineImpl) listenConnector.accept(args);
105+
vm.setLaunchedProcess(process);
106+
result.complete(vm);
107+
} catch (IllegalConnectorArgumentsException e) {
108+
result.completeExceptionally(e);
109+
} catch (IOException e) {
110+
if (result.isDone()) {
111+
/* The result Future has already been completed by the Process onExit hook */
112+
return;
113+
}
114+
115+
final String stdout = streamToString(process.getInputStream());
116+
final String stderr = streamToString(process.getErrorStream());
117+
118+
process.destroy();
119+
120+
result.completeExceptionally(new LaunchException(
121+
String.format("VM did not connect within given time: %d ms", ACCEPT_TIMEOUT),
122+
process,
123+
false,
124+
-1,
125+
stdout,
126+
stderr
127+
));
128+
} catch (RuntimeException e) {
129+
result.completeExceptionally(e);
130+
}
131+
});
132+
133+
/* Wait for the Java process to exit; if it exits before the debug connection is made, report it as an error. */
134+
process.onExit().thenAcceptAsync(theProcess -> {
135+
if (result.isDone()) {
136+
/* The result Future has already been completed by successfully connecting to the debug connection */
137+
return;
138+
}
139+
140+
final int exitStatus = theProcess.exitValue();
141+
final String stdout = streamToString(process.getInputStream());
142+
final String stderr = streamToString(process.getErrorStream());
143+
144+
result.completeExceptionally(new LaunchException(
145+
String.format("VM exited with status %d", exitStatus),
146+
theProcess,
147+
true,
148+
exitStatus,
149+
stdout,
150+
stderr
151+
));
152+
153+
/* Stop the debug connection attempt */
154+
try {
155+
listenConnector.stopListening(args);
156+
} catch (IOException e) {
157+
/* Ignore */
158+
}
159+
});
160+
89161
try {
90-
vm = (VirtualMachineImpl) listenConnector.accept(args);
91-
} catch (IOException | IllegalConnectorArgumentsException e) {
92-
process.destroy();
93-
throw new VMStartException(String.format("VM did not connect within given time: %d ms", ACCEPT_TIMEOUT), process);
162+
return result.get();
163+
} catch (ExecutionException e) {
164+
if (e.getCause() instanceof IOException) {
165+
throw (IOException) e.getCause();
166+
} else if (e.getCause() instanceof IllegalConnectorArgumentsException) {
167+
throw (IllegalConnectorArgumentsException) e.getCause();
168+
} else if (e.getCause() instanceof VMStartException) {
169+
throw (VMStartException) e.getCause();
170+
} else if (e.getCause() instanceof RuntimeException) {
171+
throw (RuntimeException) e.getCause();
172+
} else {
173+
throw new IllegalStateException("Unexpected exception thrown when launching VM", e.getCause());
174+
}
175+
} catch (InterruptedException e) {
176+
throw new VMStartException("VM start interrupted", process);
94177
}
178+
}
95179

96-
vm.setLaunchedProcess(process);
97-
return vm;
180+
private String streamToString(final InputStream inputStream) {
181+
try {
182+
return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
183+
} catch (IOException ioe) {
184+
return null;
185+
}
98186
}
99187

100188
private static String[] constructLaunchCommand(Map<String, ? extends Argument> launchingOptions, String address) {

0 commit comments

Comments
 (0)