Skip to content

Commit 7c0d2d1

Browse files
authored
fix(core): prevent Stream closed on concurrent JAR access in TemplateManager (#23596)
When two Gradle tasks run concurrently in isolated classloaders that both point to the same generator JAR, the JVM's JarFileFactory shares a single JarFile between both classloaders. If one classloader is closed/GC'd while the other is still copying a non-template resource (e.g. README.md), the shared JarFile is closed and the other worker's InflaterInputStream throws 'Stream closed'. Two fixes in TemplateManager: 1. getInputStream() now opens a fresh URLConnection with setUseCaches(false) for classpath resources, preventing the underlying JarFile from being shared across classloaders. 2. write() now wraps the InputStream in try-with-resources, fixing the pre-existing resource leak where the stream was never closed after the binary file copy.
1 parent 20e3283 commit 7c0d2d1

File tree

1 file changed

+30
-15
lines changed

1 file changed

+30
-15
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/TemplateManager.java

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import org.slf4j.LoggerFactory;
1313

1414
import java.io.*;
15+
import java.net.URL;
16+
import java.net.URLConnection;
1517
import java.nio.charset.StandardCharsets;
1618
import java.nio.file.Files;
1719
import java.nio.file.Path;
@@ -146,22 +148,28 @@ public Reader getTemplateReader(String name) {
146148
try {
147149
InputStream is = getInputStream(name);
148150
return new InputStreamReader(is, StandardCharsets.UTF_8);
149-
} catch (FileNotFoundException e) {
151+
} catch (IOException e) {
150152
LOGGER.error(e.getMessage());
151153
throw new RuntimeException("can't load template " + name);
152154
}
153155
}
154156

155-
private InputStream getInputStream(String name) throws FileNotFoundException {
156-
InputStream is;
157-
is = this.getClass().getClassLoader().getResourceAsStream(getCPResourcePath(name));
158-
if (is == null) {
159-
if (name == null || name.contains("..")) {
160-
throw new IllegalArgumentException("Template location must be constrained to template directory.");
161-
}
162-
is = new FileInputStream(name); // May throw but never return a null value
157+
private InputStream getInputStream(String name) throws IOException {
158+
if (name == null || name.contains("..")) {
159+
throw new IllegalArgumentException("Template location must be constrained to template directory.");
160+
}
161+
String cpResourcePath = getCPResourcePath(name);
162+
URL resource = this.getClass().getClassLoader().getResource(cpResourcePath);
163+
if (resource != null) {
164+
// Open a fresh, non-cached connection each time.
165+
// setUseCaches(false) prevents sharing the underlying JarFile across classloaders,
166+
// which avoids "Stream closed" errors when concurrent Gradle workers use isolated
167+
// classloaders that happen to point to the same JAR URL.
168+
URLConnection conn = resource.openConnection();
169+
conn.setUseCaches(false);
170+
return conn.getInputStream();
163171
}
164-
return is;
172+
return new FileInputStream(name); // May throw but never return a null value
165173
}
166174

167175
/**
@@ -180,15 +188,22 @@ public File write(Map<String, Object> data, String template, File target) throws
180188
return writeToFile(target.getPath(), templateContent);
181189
} else {
182190
// Do a straight copy of the file if not listed as supported by the template engine.
183-
InputStream is;
191+
String fullTemplatePath = null;
184192
try {
185193
// look up the file using the same template resolution logic the adapters would use.
186-
String fullTemplatePath = getFullTemplateFile(template);
187-
is = getInputStream(fullTemplatePath);
194+
fullTemplatePath = getFullTemplateFile(template);
188195
} catch (TemplateNotFoundException ex) {
189-
is = new FileInputStream(Paths.get(template).toFile());
196+
// not found on classpath; fall through to direct file read below
197+
}
198+
if (fullTemplatePath != null) {
199+
try (InputStream is = getInputStream(fullTemplatePath)) {
200+
return writeToFile(target.getAbsolutePath(), IOUtils.toByteArray(is));
201+
}
202+
} else {
203+
try (InputStream is = new FileInputStream(Paths.get(template).toFile())) {
204+
return writeToFile(target.getAbsolutePath(), IOUtils.toByteArray(is));
205+
}
190206
}
191-
return writeToFile(target.getAbsolutePath(), IOUtils.toByteArray(is));
192207
}
193208
}
194209

0 commit comments

Comments
 (0)