Chiang Seng Chang created GROOVY-9742:
-----------------------------------------

             Summary: Groovy 3.0.5 GroovyClassLoader.parseClass() 
StampedCommonCache.getAndPut() hang
                 Key: GROOVY-9742
                 URL: https://issues.apache.org/jira/browse/GROOVY-9742
             Project: Groovy
          Issue Type: Bug
          Components: groovy-runtime
    Affects Versions: 3.0.5
         Environment: Java version AdoptOpenJDK (build 25.262-b10, mixed mode)
Gradle version 6.6.1
Groovy version 3.0.5 
            Reporter: Chiang Seng Chang
         Attachments: g3cl.tar.gz

We have an IDE-like app which allows our coders to develop groovy scripts and 
classes.
 Essentially, the coders would write groovy sources and test them in the app.

We made a CustomClassLoader which is created and destroyed for each test run, 
so that the app need no be restarted every time there is code change.

In Groovy 3.0.5, the GroovyClassLoader's cache is refactored to use 
StampedCommonCache, which does not support recursion.

This is the distilled simplified version of our CustomClassLoader:
{noformat}
public class CustomGroovyClassLoader extends ClassLoader {
    public CustomGroovyClassLoader(ClassLoader parent) {
        super(parent);
        groovyClassLoader = new GroovyClassLoader(this);
    }
    private final File srcDir = new File("./src/main/groovy");
    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);
                System.out.println("PARSED\t: " + clz);
                return clz;
            }
            catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }
}
{noformat}
Essentially, it uses the GroovyClassLoader.parseClass() for our test classes.

Here are the 2 classes to demonstrate the issue:
{noformat}
package foo
import groovy.transform.CompileStatic
@CompileStatic
interface Bar {}
{noformat}
{noformat}
package foo
import groovy.transform.CompileStatic
@CompileStatic
class Foo implements Bar {}
{noformat}
And the test harness:
{noformat}
package foo;
public class TestHarness {
    public static void main(String[] args) throws Exception {
        ClassLoader pcl = TestHarness.class.getClassLoader();
        CustomGroovyClassLoader ccl = new CustomGroovyClassLoader(pcl);
        Class<?> clz = ccl.loadClass("foo.Foo");
        System.out.println("DONE\t: " + clz);
    }
}
{noformat}
The harness attempt to load Foo.groovy.

The sequence of events would be:
 # CustomClassLoader.loadClass(Foo)
 # GroovyClassLoader.parseClass(Foo.groovy)
 # sourceCache.getAndPut(Foo)
 # Since Foo implements Bar, CustomClassLoader is called to load Bar
 # which in turn calls GroovyClassLoader.parseClass(Bar.groovy)
 # and sourceCache.getAndPut(Bar)
 # Since StampedCommonCache does not support recursion, the loading hangs.

The attached project can be run using ./gradlew run to demonstrate the hanging.

It seems to me that the GroovyClassLoader needs to support recursion for this 
use case.

Or perhaps the CustomClassLoader is implemented wrongly?

Groovy 2.5.12 does not have this issue because its GroovyClassLoader uses 
ConcurrentCommonCache.

 

 

 



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

Reply via email to