Skip to content

Commit 7e8df44

Browse files
Avoid using the same temporary launch file when triggering multiple debug sessions in parallel (#360)
* Avoid using the same temporary launch file when triggering multiple debug sessions in parallel * Fix checkstyle
1 parent ca7d197 commit 7e8df44

File tree

3 files changed

+172
-113
lines changed

3 files changed

+172
-113
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java

Lines changed: 1 addition & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017-2019 Microsoft Corporation and others.
2+
* Copyright (c) 2017-2021 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -12,10 +12,7 @@
1212
package com.microsoft.java.debug.core.adapter;
1313

1414
import java.io.File;
15-
import java.io.FileOutputStream;
16-
import java.io.IOException;
1715
import java.io.UnsupportedEncodingException;
18-
import java.math.BigInteger;
1916
import java.net.MalformedURLException;
2017
import java.net.URI;
2118
import java.net.URISyntaxException;
@@ -28,26 +25,18 @@
2825
import java.nio.file.Paths;
2926
import java.security.MessageDigest;
3027
import java.security.NoSuchAlgorithmException;
31-
import java.util.ArrayList;
32-
import java.util.List;
3328
import java.util.concurrent.CompletableFuture;
3429
import java.util.concurrent.CompletionException;
35-
import java.util.jar.Attributes;
36-
import java.util.jar.JarOutputStream;
37-
import java.util.jar.Manifest;
3830
import java.util.regex.Matcher;
3931
import java.util.regex.Pattern;
4032

41-
import org.apache.commons.lang3.ArrayUtils;
4233
import org.apache.commons.lang3.StringUtils;
4334

4435
import com.microsoft.java.debug.core.DebugException;
4536
import com.microsoft.java.debug.core.protocol.Messages.Response;
4637
import com.microsoft.java.debug.core.protocol.Responses;
4738
import com.microsoft.java.debug.core.protocol.Types;
4839

49-
import sun.security.action.GetPropertyAction;
50-
5140
public class AdapterUtils {
5241
private static final String OS_NAME = System.getProperty("os.name", "").toLowerCase();
5342
private static final Pattern ENCLOSING_CLASS_REGEX = Pattern.compile("^([^\\$]*)");
@@ -301,102 +290,4 @@ public static String decodeURIComponent(String uri) {
301290
return uri;
302291
}
303292
}
304-
305-
/**
306-
* Generate the classpath parameters to a temporary classpath.jar.
307-
* @param classPaths - the classpath parameters
308-
* @return the file path of the generate classpath.jar
309-
* @throws IOException Some errors occur during generating the classpath.jar
310-
*/
311-
public static Path generateClasspathJar(String[] classPaths) throws IOException {
312-
List<String> classpathUrls = new ArrayList<>();
313-
for (String classpath : classPaths) {
314-
classpathUrls.add(AdapterUtils.toUrl(classpath));
315-
}
316-
317-
Manifest manifest = new Manifest();
318-
Attributes attributes = manifest.getMainAttributes();
319-
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
320-
// In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar
321-
String classpathValue = String.join(" ", classpathUrls);
322-
attributes.put(Attributes.Name.CLASS_PATH, classpathValue);
323-
Path tempfile = createTempFile("cp_" + getMd5(classpathValue), ".jar");
324-
JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest);
325-
jar.close();
326-
327-
return tempfile;
328-
}
329-
330-
/**
331-
* Generate the classpath parameters to a temporary argfile file.
332-
* @param classPaths - the classpath parameters
333-
* @param modulePaths - the modulepath parameters
334-
* @return the file path of the generated argfile
335-
* @throws IOException Some errors occur during generating the argfile
336-
*/
337-
public static Path generateArgfile(String[] classPaths, String[] modulePaths) throws IOException {
338-
String argfile = "";
339-
if (ArrayUtils.isNotEmpty(classPaths)) {
340-
argfile = "-classpath \"" + String.join(File.pathSeparator, classPaths) + "\"";
341-
}
342-
343-
if (ArrayUtils.isNotEmpty(modulePaths)) {
344-
argfile = " --module-path \"" + String.join(File.pathSeparator, modulePaths) + "\"";
345-
}
346-
347-
argfile = argfile.replace("\\", "\\\\");
348-
Path tempfile = createTempFile("cp_" + getMd5(argfile), ".argfile");
349-
Files.write(tempfile, argfile.getBytes());
350-
351-
return tempfile;
352-
}
353-
354-
private static Path tmpdir = null;
355-
356-
private static synchronized Path getTmpDir() throws IOException {
357-
if (tmpdir == null) {
358-
try {
359-
tmpdir = Paths.get(java.security.AccessController.doPrivileged(new GetPropertyAction("java.io.tmpdir")));
360-
} catch (NullPointerException | InvalidPathException e) {
361-
Path tmpfile = Files.createTempFile("", ".tmp");
362-
tmpdir = tmpfile.getParent();
363-
try {
364-
Files.deleteIfExists(tmpfile);
365-
} catch (Exception ex) {
366-
// do nothing
367-
}
368-
}
369-
}
370-
371-
return tmpdir;
372-
}
373-
374-
private static Path createTempFile(String baseName, String suffix) throws IOException {
375-
// loop until the temp file can be created
376-
SecurityManager sm = System.getSecurityManager();
377-
for (int i = 0; ; i++) {
378-
Path tempFile = getTmpDir().resolve(baseName + (i == 0 ? "" : i) + suffix);
379-
try {
380-
// delete the old temp file
381-
Files.deleteIfExists(tempFile);
382-
} catch (Exception e) {
383-
// do nothing
384-
}
385-
386-
if (!Files.exists(tempFile)) {
387-
return Files.createFile(tempFile);
388-
}
389-
}
390-
}
391-
392-
private static String getMd5(String input) {
393-
try {
394-
MessageDigest md = MessageDigest.getInstance("MD5");
395-
byte[] messageDigest = md.digest(input.getBytes());
396-
BigInteger md5 = new BigInteger(1, messageDigest);
397-
return md5.toString(Character.MAX_RADIX);
398-
} catch (NoSuchAlgorithmException e) {
399-
return Integer.toString(input.hashCode(), Character.MAX_RADIX);
400-
}
401-
}
402293
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018 Microsoft Corporation and others.
2+
* Copyright (c) 2018-2021 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -115,7 +115,7 @@ protected CompletableFuture<Response> handleLaunchCommand(Arguments arguments, R
115115
if (launchArguments.shortenCommandLine == ShortenApproach.JARMANIFEST) {
116116
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
117117
try {
118-
Path tempfile = AdapterUtils.generateClasspathJar(launchArguments.classPaths);
118+
Path tempfile = LaunchUtils.generateClasspathJar(launchArguments.classPaths);
119119
launchArguments.vmArgs += " -cp \"" + tempfile.toAbsolutePath().toString() + "\"";
120120
launchArguments.classPaths = new String[0];
121121
context.setClasspathJar(tempfile);
@@ -129,7 +129,7 @@ protected CompletableFuture<Response> handleLaunchCommand(Arguments arguments, R
129129
}
130130
} else if (launchArguments.shortenCommandLine == ShortenApproach.ARGFILE) {
131131
try {
132-
Path tempfile = AdapterUtils.generateArgfile(launchArguments.classPaths, launchArguments.modulePaths);
132+
Path tempfile = LaunchUtils.generateArgfile(launchArguments.classPaths, launchArguments.modulePaths);
133133
launchArguments.vmArgs += " \"@" + tempfile.toAbsolutePath().toString() + "\"";
134134
launchArguments.classPaths = new String[0];
135135
launchArguments.modulePaths = new String[0];
@@ -140,6 +140,8 @@ protected CompletableFuture<Response> handleLaunchCommand(Arguments arguments, R
140140
}
141141

142142
return launch(launchArguments, response, context).thenCompose(res -> {
143+
LaunchUtils.releaseTempLaunchFile(context.getClasspathJar());
144+
LaunchUtils.releaseTempLaunchFile(context.getArgsfile());
143145
if (res.success) {
144146
activeLaunchHandler.postLaunch(launchArguments, context);
145147
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*******************************************************************************
2+
* Copyright (c) 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.adapter.handler;
13+
14+
import java.io.File;
15+
import java.io.FileOutputStream;
16+
import java.io.IOException;
17+
import java.math.BigInteger;
18+
import java.nio.file.Files;
19+
import java.nio.file.InvalidPathException;
20+
import java.nio.file.Path;
21+
import java.nio.file.Paths;
22+
import java.security.MessageDigest;
23+
import java.security.NoSuchAlgorithmException;
24+
import java.util.ArrayList;
25+
import java.util.HashSet;
26+
import java.util.List;
27+
import java.util.Set;
28+
import java.util.jar.Attributes;
29+
import java.util.jar.JarOutputStream;
30+
import java.util.jar.Manifest;
31+
32+
import com.microsoft.java.debug.core.adapter.AdapterUtils;
33+
34+
import org.apache.commons.lang3.ArrayUtils;
35+
36+
import sun.security.action.GetPropertyAction;
37+
38+
public class LaunchUtils {
39+
private static Set<Path> tempFilesInUse = new HashSet<>();
40+
41+
/**
42+
* Generate the classpath parameters to a temporary classpath.jar.
43+
* @param classPaths - the classpath parameters
44+
* @return the file path of the generate classpath.jar
45+
* @throws IOException Some errors occur during generating the classpath.jar
46+
*/
47+
public static synchronized Path generateClasspathJar(String[] classPaths) throws IOException {
48+
List<String> classpathUrls = new ArrayList<>();
49+
for (String classpath : classPaths) {
50+
classpathUrls.add(AdapterUtils.toUrl(classpath));
51+
}
52+
53+
Manifest manifest = new Manifest();
54+
Attributes attributes = manifest.getMainAttributes();
55+
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
56+
// In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar
57+
String classpathValue = String.join(" ", classpathUrls);
58+
attributes.put(Attributes.Name.CLASS_PATH, classpathValue);
59+
String baseName = "cp_" + getMd5(classpathValue);
60+
cleanupTempFiles(baseName, ".jar");
61+
Path tempfile = createTempFile(baseName, ".jar");
62+
JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest);
63+
jar.close();
64+
lockTempLaunchFile(tempfile);
65+
66+
return tempfile;
67+
}
68+
69+
/**
70+
* Generate the classpath parameters to a temporary argfile file.
71+
* @param classPaths - the classpath parameters
72+
* @param modulePaths - the modulepath parameters
73+
* @return the file path of the generated argfile
74+
* @throws IOException Some errors occur during generating the argfile
75+
*/
76+
public static synchronized Path generateArgfile(String[] classPaths, String[] modulePaths) throws IOException {
77+
String argfile = "";
78+
if (ArrayUtils.isNotEmpty(classPaths)) {
79+
argfile = "-classpath \"" + String.join(File.pathSeparator, classPaths) + "\"";
80+
}
81+
82+
if (ArrayUtils.isNotEmpty(modulePaths)) {
83+
argfile = " --module-path \"" + String.join(File.pathSeparator, modulePaths) + "\"";
84+
}
85+
86+
argfile = argfile.replace("\\", "\\\\");
87+
String baseName = "cp_" + getMd5(argfile);
88+
cleanupTempFiles(baseName, ".argfile");
89+
Path tempfile = createTempFile(baseName, ".argfile");
90+
Files.write(tempfile, argfile.getBytes());
91+
lockTempLaunchFile(tempfile);
92+
93+
return tempfile;
94+
}
95+
96+
public static void lockTempLaunchFile(Path tempFile) {
97+
if (tempFile != null) {
98+
tempFilesInUse.add(tempFile);
99+
}
100+
}
101+
102+
public static void releaseTempLaunchFile(Path tempFile) {
103+
if (tempFile != null) {
104+
tempFilesInUse.remove(tempFile);
105+
}
106+
}
107+
108+
private static Path tmpdir = null;
109+
110+
private static synchronized Path getTmpDir() throws IOException {
111+
if (tmpdir == null) {
112+
try {
113+
tmpdir = Paths.get(java.security.AccessController.doPrivileged(new GetPropertyAction("java.io.tmpdir")));
114+
} catch (NullPointerException | InvalidPathException e) {
115+
Path tmpfile = Files.createTempFile("", ".tmp");
116+
tmpdir = tmpfile.getParent();
117+
try {
118+
Files.deleteIfExists(tmpfile);
119+
} catch (Exception ex) {
120+
// do nothing
121+
}
122+
}
123+
}
124+
125+
return tmpdir;
126+
}
127+
128+
private static void cleanupTempFiles(String baseName, String suffix) throws IOException {
129+
for (int i = 0; ; i++) {
130+
Path tempFile = getTmpDir().resolve(baseName + (i == 0 ? "" : i) + suffix);
131+
if (tempFilesInUse.contains(tempFile)) {
132+
continue;
133+
} else if (!Files.exists(tempFile)) {
134+
break;
135+
} else {
136+
try {
137+
// delete the old temp file
138+
Files.deleteIfExists(tempFile);
139+
} catch (Exception e) {
140+
// do nothing
141+
}
142+
}
143+
}
144+
}
145+
146+
private static Path createTempFile(String baseName, String suffix) throws IOException {
147+
// loop until the temp file can be created
148+
for (int i = 0; ; i++) {
149+
Path tempFile = getTmpDir().resolve(baseName + (i == 0 ? "" : i) + suffix);
150+
if (!Files.exists(tempFile)) {
151+
return Files.createFile(tempFile);
152+
}
153+
}
154+
}
155+
156+
private static String getMd5(String input) {
157+
try {
158+
MessageDigest md = MessageDigest.getInstance("MD5");
159+
byte[] messageDigest = md.digest(input.getBytes());
160+
BigInteger md5 = new BigInteger(1, messageDigest);
161+
return md5.toString(Character.MAX_RADIX);
162+
} catch (NoSuchAlgorithmException e) {
163+
return Integer.toString(input.hashCode(), Character.MAX_RADIX);
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)