This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch GROOVY-9742
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit d86636af8d6abdfc032fa1a3d10fffb3807b8c3d
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  | 42 +++++++++++++
 5 files changed, 165 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..f6769cf276
--- /dev/null
+++ b/src/test/groovy/bugs/groovy9742/Groovy9742.groovy
@@ -0,0 +1,42 @@
+/*
+ *  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
+    }
+}

Reply via email to