You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Chiang Seng Chang (Jira)" <ji...@apache.org> on 2020/09/14 21:50:00 UTC

[jira] [Commented] (GROOVY-9742) Groovy 3.0.5 GroovyClassLoader.parseClass() StampedCommonCache.getAndPut() hang

    [ https://issues.apache.org/jira/browse/GROOVY-9742?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17195747#comment-17195747 ] 

Chiang Seng Chang commented on GROOVY-9742:
-------------------------------------------

Our workaround is to use reflection to get the GroovyClassLoader.doParseClass() method and invoke that to parse the groovy files bypassing its sourceCache.

May be the simple fix is to expose doParseClass() as public for such a use case?

> 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
>            Priority: Major
>         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)