Skip to content

Commit 7c6e912

Browse files
committed
Attempt to prevent JarFiles from being left open
Update `JarFile` so that `super.close()` is called early so that the file is not left open. Since we re-implement `JarFile` methods to work directly on the underlying `RandomAccessDataFile`, it should be safe to close immediately. See gh-21126
1 parent 5ed27dd commit 7c6e912

File tree

3 files changed

+22
-50
lines changed

3 files changed

+22
-50
lines changed

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public class JarFile extends java.util.jar.JarFile {
8484

8585
private String comment;
8686

87+
private volatile boolean closed;
88+
8789
/**
8890
* Create a new {@link JarFile} backed by the specified file.
8991
* @param file the root jar file
@@ -140,6 +142,7 @@ private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccess
140142
private JarFile(JarFile parent, RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data,
141143
JarEntryFilter filter, JarFileType type, Supplier<Manifest> manifestSupplier) throws IOException {
142144
super(rootFile.getFile());
145+
super.close();
143146
this.parent = parent;
144147
this.rootFile = rootFile;
145148
this.pathFromRoot = pathFromRoot;
@@ -321,12 +324,19 @@ public int size() {
321324

322325
@Override
323326
public void close() throws IOException {
324-
super.close();
327+
if (this.closed) {
328+
return;
329+
}
330+
this.closed = true;
325331
if (this.type == JarFileType.DIRECT && this.parent == null) {
326332
this.rootFile.close();
327333
}
328334
}
329335

336+
boolean isClosed() {
337+
return this.closed;
338+
}
339+
330340
String getUrlString() throws MalformedURLException {
331341
if (this.urlString == null) {
332342
this.urlString = getUrl().toString();

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java

Lines changed: 10 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.io.ByteArrayOutputStream;
2020
import java.io.FileNotFoundException;
2121
import java.io.FilePermission;
22-
import java.io.FilterInputStream;
2322
import java.io.IOException;
2423
import java.io.InputStream;
2524
import java.io.UnsupportedEncodingException;
@@ -81,18 +80,14 @@ protected URLConnection openConnection(URL u) throws IOException {
8180

8281
private final JarEntryName jarEntryName;
8382

84-
private final CloseAction closeAction;
85-
8683
private JarEntry jarEntry;
8784

88-
private JarURLConnection(URL url, JarFile jarFile, JarEntryName jarEntryName, CloseAction closeAction)
89-
throws IOException {
85+
private JarURLConnection(URL url, JarFile jarFile, JarEntryName jarEntryName) throws IOException {
9086
// What we pass to super is ultimately ignored
9187
super(EMPTY_JAR_URL);
9288
this.url = url;
9389
this.jarFile = jarFile;
9490
this.jarEntryName = jarEntryName;
95-
this.closeAction = closeAction;
9691
}
9792

9893
@Override
@@ -173,17 +168,7 @@ public InputStream getInputStream() throws IOException {
173168
if (inputStream == null) {
174169
throwFileNotFound(this.jarEntryName, this.jarFile);
175170
}
176-
return new FilterInputStream(inputStream) {
177-
178-
@Override
179-
public void close() throws IOException {
180-
super.close();
181-
if (JarURLConnection.this.closeAction != null) {
182-
JarURLConnection.this.closeAction.perform();
183-
}
184-
}
185-
186-
};
171+
return inputStream;
187172
}
188173

189174
private void throwFileNotFound(Object entry, JarFile jarFile) throws FileNotFoundException {
@@ -264,30 +249,24 @@ static JarURLConnection get(URL url, JarFile jarFile) throws IOException {
264249
int index = indexOfRootSpec(spec, jarFile.getPathFromRoot());
265250
if (index == -1) {
266251
return (Boolean.TRUE.equals(useFastExceptions.get()) ? NOT_FOUND_CONNECTION
267-
: new JarURLConnection(url, null, EMPTY_JAR_ENTRY_NAME, null));
252+
: new JarURLConnection(url, null, EMPTY_JAR_ENTRY_NAME));
268253
}
269254
int separator;
270-
JarFile connectionJarFile = jarFile;
271255
while ((separator = spec.indexOf(SEPARATOR, index)) > 0) {
272256
JarEntryName entryName = JarEntryName.get(spec.subSequence(index, separator));
273257
JarEntry jarEntry = jarFile.getJarEntry(entryName.toCharSequence());
274258
if (jarEntry == null) {
275-
return JarURLConnection.notFound(connectionJarFile, entryName,
276-
(connectionJarFile != jarFile) ? connectionJarFile::close : null);
259+
return JarURLConnection.notFound(jarFile, entryName);
277260
}
278-
connectionJarFile = connectionJarFile.getNestedJarFile(jarEntry);
261+
jarFile = jarFile.getNestedJarFile(jarEntry);
279262
index = separator + SEPARATOR.length();
280263
}
281264
JarEntryName jarEntryName = JarEntryName.get(spec, index);
282265
if (Boolean.TRUE.equals(useFastExceptions.get()) && !jarEntryName.isEmpty()
283-
&& !connectionJarFile.containsEntry(jarEntryName.toString())) {
284-
if (connectionJarFile != jarFile) {
285-
connectionJarFile.close();
286-
}
266+
&& !jarFile.containsEntry(jarEntryName.toString())) {
287267
return NOT_FOUND_CONNECTION;
288268
}
289-
return new JarURLConnection(url, new JarFile(connectionJarFile), jarEntryName,
290-
(connectionJarFile != jarFile) ? connectionJarFile::close : null);
269+
return new JarURLConnection(url, new JarFile(jarFile), jarEntryName);
291270
}
292271

293272
private static int indexOfRootSpec(StringSequence file, String pathFromRoot) {
@@ -300,22 +279,18 @@ private static int indexOfRootSpec(StringSequence file, String pathFromRoot) {
300279

301280
private static JarURLConnection notFound() {
302281
try {
303-
return notFound(null, null, null);
282+
return notFound(null, null);
304283
}
305284
catch (IOException ex) {
306285
throw new IllegalStateException(ex);
307286
}
308287
}
309288

310-
private static JarURLConnection notFound(JarFile jarFile, JarEntryName jarEntryName, CloseAction closeAction)
311-
throws IOException {
289+
private static JarURLConnection notFound(JarFile jarFile, JarEntryName jarEntryName) throws IOException {
312290
if (Boolean.TRUE.equals(useFastExceptions.get())) {
313-
if (closeAction != null) {
314-
closeAction.perform();
315-
}
316291
return NOT_FOUND_CONNECTION;
317292
}
318-
return new JarURLConnection(null, jarFile, jarEntryName, closeAction);
293+
return new JarURLConnection(null, jarFile, jarEntryName);
319294
}
320295

321296
/**
@@ -418,15 +393,4 @@ static JarEntryName get(StringSequence spec, int beginIndex) {
418393

419394
}
420395

421-
/**
422-
* An action to be taken when the connection is being "closed" and its underlying
423-
* resources are no longer needed.
424-
*/
425-
@FunctionalInterface
426-
private interface CloseAction {
427-
428-
void perform() throws IOException;
429-
430-
}
431-
432396
}

spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.io.FileNotFoundException;
2222
import java.io.InputStream;
2323
import java.net.URL;
24-
import java.util.zip.ZipFile;
2524

2625
import org.junit.jupiter.api.AfterEach;
2726
import org.junit.jupiter.api.BeforeEach;
@@ -30,7 +29,6 @@
3029

3130
import org.springframework.boot.loader.TestJarCreator;
3231
import org.springframework.boot.loader.jar.JarURLConnection.JarEntryName;
33-
import org.springframework.test.util.ReflectionTestUtils;
3432

3533
import static org.assertj.core.api.Assertions.assertThat;
3634
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -230,7 +228,7 @@ void openConnectionCanBeClosedWithoutClosingSourceJar() throws Exception {
230228
JarURLConnection connection = JarURLConnection.get(url, this.jarFile);
231229
JarFile connectionJarFile = connection.getJarFile();
232230
connectionJarFile.close();
233-
assertThat((Boolean) ReflectionTestUtils.getField(this.jarFile, ZipFile.class, "closeRequested")).isFalse();
231+
assertThat(this.jarFile.isClosed()).isFalse();
234232
}
235233

236234
private String getRelativePath() {

0 commit comments

Comments
 (0)