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 + } +}
