Uploaded image for project: 'Groovy'
  1. Groovy
  2. GROOVY-9742

Groovy 3.0.5 GroovyClassLoader.parseClass() StampedCommonCache.getAndPut() hang

    XMLWordPrintableJSON

    Details

    • Type: Bug
    • Status: Open
    • Priority: Major
    • Resolution: Unresolved
    • Affects Version/s: 3.0.5
    • Fix Version/s: None
    • Component/s: groovy-runtime
    • Labels:
      None
    • Environment:
      Java version AdoptOpenJDK (build 25.262-b10, mixed mode)
      Gradle version 6.6.1
      Groovy version 3.0.5

      Description

      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:

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

      Essentially, it uses the GroovyClassLoader.parseClass() for our test classes.

      Here are the 2 classes to demonstrate the issue:

      package foo
      import groovy.transform.CompileStatic
      @CompileStatic
      interface Bar {}
      
      package foo
      import groovy.transform.CompileStatic
      @CompileStatic
      class Foo implements Bar {}
      

      And the test harness:

      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);
          }
      }
      

      The harness attempt to load Foo.groovy.

      The sequence of events would be:

      1. CustomClassLoader.loadClass(Foo)
      2. GroovyClassLoader.parseClass(Foo.groovy)
      3. sourceCache.getAndPut(Foo)
      4. Since Foo implements Bar, CustomClassLoader is called to load Bar
      5. which in turn calls GroovyClassLoader.parseClass(Bar.groovy)
      6. and sourceCache.getAndPut(Bar)
      7. 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.

       

       

       

        Attachments

        1. g3cl.tar.gz
          56 kB
          Chiang Seng Chang

          Activity

            People

            • Assignee:
              Unassigned
              Reporter:
              chacs Chiang Seng Chang
            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated: