This is an automated email from the ASF dual-hosted git repository.
sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new f348edd948 GROOVY-9742: GroovyClassLoader.parseClass()
StampedCommonCache.getAndPut() hang
f348edd948 is described below
commit f348edd9485fb1e951fd241e07beb862e0e17344
Author: Daniel Sun <[email protected]>
AuthorDate: Sun Mar 30 00:13:59 2025 +0900
GROOVY-9742: GroovyClassLoader.parseClass() StampedCommonCache.getAndPut()
hang
---
src/main/java/groovy/lang/GroovyClassLoader.java | 4 +-
src/test/groovy/bugs/groovy9742/Bar.groovy | 24 +++++++
.../bugs/groovy9742/CustomGroovyClassLoader.java | 73 ++++++++++++++++++++++
src/test/groovy/bugs/groovy9742/Foo.groovy | 24 +++++++
src/test/groovy/bugs/groovy9742/Groovy9742.groovy | 43 +++++++++++++
5 files changed, 166 insertions(+), 2 deletions(-)
diff --git a/src/main/java/groovy/lang/GroovyClassLoader.java
b/src/main/java/groovy/lang/GroovyClassLoader.java
index afaa29bb01..05f5b09790 100644
--- a/src/main/java/groovy/lang/GroovyClassLoader.java
+++ b/src/main/java/groovy/lang/GroovyClassLoader.java
@@ -35,8 +35,8 @@ import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.EncodingGroovyMethods;
import org.codehaus.groovy.runtime.IOGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;
+import org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache;
import org.codehaus.groovy.runtime.memoize.EvictableCache;
-import org.codehaus.groovy.runtime.memoize.StampedCommonCache;
import org.codehaus.groovy.runtime.memoize.UnlimitedConcurrentCache;
import org.codehaus.groovy.util.URLStreams;
import org.objectweb.asm.ClassVisitor;
@@ -99,7 +99,7 @@ public class GroovyClassLoader extends URLClassLoader {
/**
* This cache contains mappings of file name to class. It is used to
bypass compilation.
*/
- protected final StampedCommonCache<String, Class> sourceCache = new
StampedCommonCache<>();
+ protected final ConcurrentCommonCache<String, Class> sourceCache = new
ConcurrentCommonCache<>();
private final CompilerConfiguration config;
private final String sourceEncoding;
diff --git a/src/test/groovy/bugs/groovy9742/Bar.groovy
b/src/test/groovy/bugs/groovy9742/Bar.groovy
new file mode 100644
index 0000000000..e3e596fe76
--- /dev/null
+++ b/src/test/groovy/bugs/groovy9742/Bar.groovy
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.bugs.groovy9742
+
+import groovy.transform.CompileStatic
+
+@CompileStatic
+interface Bar {}
diff --git a/src/test/groovy/bugs/groovy9742/CustomGroovyClassLoader.java
b/src/test/groovy/bugs/groovy9742/CustomGroovyClassLoader.java
new file mode 100644
index 0000000000..2bc87f7ab1
--- /dev/null
+++ b/src/test/groovy/bugs/groovy9742/CustomGroovyClassLoader.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.bugs.groovy9742;
+
+import groovy.lang.GroovyClassLoader;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+public class CustomGroovyClassLoader extends ClassLoader {
+ public int loadedCount = 0;
+ public CustomGroovyClassLoader(ClassLoader parent) {
+ super(parent);
+ groovyClassLoader = new GroovyClassLoader(this);
+ }
+ private static final File srcDir;
+
+ static {
+ try {
+ srcDir = new
File(CustomGroovyClassLoader.class.getResource("/").toURI());
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final GroovyClassLoader groovyClassLoader;
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws
ClassNotFoundException {
+ synchronized (getClassLoadingLock(name)) {
+ Class<?> c = doFindClass(name);
+ if (c != null) {
+ if (resolve) {
+ resolveClass(c);
+ }
+ return c;
+ }
+ }
+ return super.loadClass(name, resolve);
+ }
+ private Class<?> doFindClass(String name) {
+ File classFile = new File(srcDir, name.replace('.', '/') + ".groovy");
+ if (classFile.exists()) {
+ try {
+// System.out.println("PARSE\t: " + name);
+ Class<?> clz = groovyClassLoader.parseClass(classFile);
+ loadedCount++;
+// System.out.println("PARSED\t: " + clz);
+ return clz;
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+}
+
diff --git a/src/test/groovy/bugs/groovy9742/Foo.groovy
b/src/test/groovy/bugs/groovy9742/Foo.groovy
new file mode 100644
index 0000000000..49b7738fff
--- /dev/null
+++ b/src/test/groovy/bugs/groovy9742/Foo.groovy
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.bugs.groovy9742
+
+import groovy.transform.CompileStatic
+
+@CompileStatic
+class Foo implements Bar {}
diff --git a/src/test/groovy/bugs/groovy9742/Groovy9742.groovy
b/src/test/groovy/bugs/groovy9742/Groovy9742.groovy
new file mode 100644
index 0000000000..6747a0e6ec
--- /dev/null
+++ b/src/test/groovy/bugs/groovy9742/Groovy9742.groovy
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.bugs.groovy9742
+
+import org.junit.jupiter.api.Test
+
+import java.util.concurrent.Callable
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
+
+class Groovy9742 {
+ @Test
+ void testDeadLock() {
+ ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
+ Future future = fixedThreadPool.submit((Callable<Class<?>>) () -> {
+ CustomGroovyClassLoader ccl = new
CustomGroovyClassLoader(Groovy9742.class.getClassLoader())
+ Class<?> clz = ccl.loadClass("groovy.bugs.groovy9742.Foo")
+ assert ccl.loadedCount == 2
+ return clz
+ })
+ Class c = future.get(3000, TimeUnit.MILLISECONDS)
+ assert c instanceof Class
+ fixedThreadPool.shutdownNow()
+ }
+}