You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/09/13 03:45:34 UTC
(groovy) 01/01: GROOVY-12404: GroovyScriptEngine resource loader leaks URLConnections and ignores a custom ResourceConnector
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch GROOVY-12404
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit d38a1c289cce37d1697972d2ecad446abb40a686
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Sep 13 12:35:22 2026 +0900
GROOVY-12404: GroovyScriptEngine resource loader leaks URLConnections and ignores a custom ResourceConnector
---
src/main/java/groovy/util/GroovyScriptEngine.java | 45 ++++++-----
.../groovy/util/GroovyScriptEngineTest.groovy | 90 ++++++++++++++++++++++
2 files changed, 116 insertions(+), 19 deletions(-)
diff --git a/src/main/java/groovy/util/GroovyScriptEngine.java b/src/main/java/groovy/util/GroovyScriptEngine.java
index 7b1ae85a0f..a1f3787659 100644
--- a/src/main/java/groovy/util/GroovyScriptEngine.java
+++ b/src/main/java/groovy/util/GroovyScriptEngine.java
@@ -145,6 +145,7 @@ public class GroovyScriptEngine implements ResourceConnector {
*/
public ScriptClassLoader(GroovyClassLoader loader) {
super(loader);
+ setResLoader();
}
/**
@@ -161,14 +162,16 @@ public class GroovyScriptEngine implements ResourceConnector {
private void setResLoader() {
final GroovyResourceLoader rl = getResourceLoader();
setResourceLoader(className -> {
- String filename;
for (String extension : getConfig().getScriptExtensions()) {
- filename = className.replace('.', File.separatorChar) + "." + extension;
+ String filename = className.replace('.', '/') + "." + extension;
+ URLConnection dependentScriptConn = null;
try {
- URLConnection dependentScriptConn = rc.getResourceConnection(filename);
+ dependentScriptConn = rc.getResourceConnection(filename);
return dependentScriptConn.getURL();
} catch (ResourceException e) {
- //TODO: maybe do something here?
+ // Missing this extension; try the next one, then the parent loader.
+ } finally {
+ forceClose(dependentScriptConn);
}
}
return rl.loadGroovySource(className);
@@ -189,7 +192,12 @@ public class GroovyScriptEngine implements ResourceConnector {
for (String depSourcePath : cache.get(".")) {
try {
cache.get(depSourcePath);
- cu.addSource(getResourceConnection(depSourcePath).getURL());
+ URLConnection conn = rc.getResourceConnection(depSourcePath);
+ try {
+ cu.addSource(conn.getURL());
+ } finally {
+ forceClose(conn);
+ }
} catch (ResourceException e) {
/* ignore */
}
@@ -216,24 +224,23 @@ public class GroovyScriptEngine implements ResourceConnector {
try {
String finalName = name + "." + ext;
URLConnection conn = rc.getResourceConnection(finalName);
- URL url = conn.getURL();
- String path = url.toExternalForm();
- ScriptCacheEntry entry = scriptCache.get(path);
- Class clazz = null;
- if (entry != null) clazz = entry.scriptClass;
- if (GroovyScriptEngine.this.isSourceNewer(entry)) {
- try {
+ try {
+ URL url = conn.getURL();
+ String path = url.toExternalForm();
+ ScriptCacheEntry entry = scriptCache.get(path);
+ Class clazz = null;
+ if (entry != null) clazz = entry.scriptClass;
+ if (GroovyScriptEngine.this.isSourceNewer(entry)) {
SourceUnit su = compilationUnit.addSource(url);
return new LookupResult(su, null);
- } finally {
- forceClose(conn);
}
- } else {
precompiledEntries.put(origName, path);
- }
- if (clazz != null) {
- ClassNode cn = ClassHelper.make(clazz);
- return new LookupResult(null, cn);
+ if (clazz != null) {
+ ClassNode cn = ClassHelper.make(clazz);
+ return new LookupResult(null, cn);
+ }
+ } finally {
+ forceClose(conn);
}
} catch (ResourceException re) {
// skip
diff --git a/src/test/groovy/groovy/util/GroovyScriptEngineTest.groovy b/src/test/groovy/groovy/util/GroovyScriptEngineTest.groovy
index aa15d50170..fea754230b 100644
--- a/src/test/groovy/groovy/util/GroovyScriptEngineTest.groovy
+++ b/src/test/groovy/groovy/util/GroovyScriptEngineTest.groovy
@@ -86,4 +86,94 @@ final class GroovyScriptEngineTest {
assert counts['Script1'] == 1
assert counts['Foo'] == 1
}
+
+ @Test
+ void resourceLoaderClosesConnectorConnection() {
+ File scriptFile = new File(temporaryFolder, 'Foo.groovy')
+ scriptFile.text = 'class Foo {}'
+
+ TrackingURLConnection tracking = null
+ ResourceConnector rc = { String name ->
+ File f = new File(temporaryFolder, name)
+ if (!f.exists()) throw new ResourceException("missing $name")
+ tracking = new TrackingURLConnection(f)
+ tracking
+ }
+
+ GroovyScriptEngine engine = new GroovyScriptEngine(rc)
+ engine.config.scriptExtensions = new LinkedHashSet(['groovy'])
+
+ URL url = engine.groovyClassLoader.resourceLoader.loadGroovySource('Foo')
+ assert url != null
+ assert tracking != null
+ assert tracking.inputStreamOpened : 'URLConnection obtained only for its URL must still be closed'
+ }
+
+ @Test
+ void resourceLoaderUsesCustomConnectorWhenParentIsGroovyClassLoader() {
+ new File(temporaryFolder, 'Helper.groovy').text = 'class Helper { def ping() { "pong" } }'
+
+ ResourceConnector rc = { String name ->
+ File f = new File(temporaryFolder, name)
+ if (!f.exists()) throw new ResourceException("missing $name")
+ f.toURI().toURL().openConnection()
+ }
+
+ GroovyScriptEngine engine = new GroovyScriptEngine(rc, new GroovyClassLoader())
+ Class helper = engine.groovyClassLoader.loadClass('Helper')
+ assert helper.newInstance().ping() == 'pong'
+ }
+
+ @Test
+ void resourceLoaderTriesNextExtensionWhenFirstIsMissing() {
+ new File(temporaryFolder, 'Helper.gy').text = 'class Helper { def ping() { "pong" } }'
+
+ GroovyScriptEngine engine = new GroovyScriptEngine([temporaryFolder.toURI().toURL()] as URL[])
+ engine.config.scriptExtensions = new LinkedHashSet(['groovy', 'gy'])
+
+ Class helper = engine.groovyClassLoader.loadClass('Helper')
+ assert helper.newInstance().ping() == 'pong'
+ }
+
+ @Test
+ void resourceLoaderRequestsForwardSlashPathsForPackagedClasses() {
+ File pkg = new File(temporaryFolder, 'com/example')
+ pkg.mkdirs()
+ new File(pkg, 'Helper.groovy').text = 'package com.example; class Helper { def ping() { "pong" } }'
+
+ def requested = []
+ ResourceConnector rc = { String name ->
+ requested << name
+ File f = new File(temporaryFolder, name)
+ if (!f.exists()) throw new ResourceException("missing $name")
+ f.toURI().toURL().openConnection()
+ }
+
+ GroovyScriptEngine engine = new GroovyScriptEngine(rc)
+ engine.config.scriptExtensions = new LinkedHashSet(['groovy'])
+
+ Class helper = engine.groovyClassLoader.loadClass('com.example.Helper')
+ assert helper.newInstance().ping() == 'pong'
+ assert requested.contains('com/example/Helper.groovy')
+ }
+
+ static final class TrackingURLConnection extends URLConnection {
+ boolean inputStreamOpened
+ private final File file
+
+ TrackingURLConnection(File file) {
+ super(file.toURI().toURL())
+ this.file = file
+ }
+
+ @Override
+ void connect() {
+ }
+
+ @Override
+ InputStream getInputStream() {
+ inputStreamOpened = true
+ file.newInputStream()
+ }
+ }
}