groovy
  1. groovy
  2. GROOVY-4955

GroovyClassloader can not reload modified script

    Details

    • Type: Bug Bug
    • Status: Closed Closed
    • Priority: Major Major
    • Resolution: Won't Fix
    • Affects Version/s: 1.8.1
    • Fix Version/s: None
    • Component/s: GroovyScriptEngine
    • Labels:
      None
    • Environment:
      groovy 1.8.1

      Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
      Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)
    • Number of attachments :
      0

      Description

      I found GroovyClassloader can not reload modified script.
      I am using following code:

      final CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
      compilerConfiguration.setRecompileGroovySource(true);
      cl = new GroovyClassLoader(Thread.currentThread().getContextClassLoader (), compilerConfiguration);
      cl.loadClass(applicationClassName, true, false);

      and I study the GroovyClassloader source code,and I found following code:

      Class cls = getClassCacheEntry(name);
      .....
      Class parentClassLoaderClass = super.loadClass(name, resolve);
      // always return if the parent loader was successful
      if (cls != parentClassLoaderClass) return parentClassLoaderClass;

      and I found super.loadClass always return old parsed Class instance.

      another testcase:

      GroovyScriptEngine engine = new GroovyScriptEngine(root,Thread.currentThread().getContextClassLoader());
      engine.loadScriptByName(scriptName);

      If I modified the script soruce,and I found using

      Class.forName("myClassName", true, engine.getGroovyClassLoader());

      and

      engine.getGroovyClassLoader().loadClass("myClassName")

      will return different Class instance.

        Activity

        Hide
        blackdrag blackdrag added a comment -

        GroovyClassLoader (GCL) can reload scripts, but only if it did load them in the first place. This is by design to avoid some problems with duplicated classes. The usage story for GroovyScriptEngine (GSE) is different, which is why we think we can maybe make that there and not in GCL.

        Show
        blackdrag blackdrag added a comment - GroovyClassLoader (GCL) can reload scripts, but only if it did load them in the first place. This is by design to avoid some problems with duplicated classes. The usage story for GroovyScriptEngine (GSE) is different, which is why we think we can maybe make that there and not in GCL.
        Hide
        L.J.W added a comment -

        Hi jochen,I am not understand the groovyclassloader logic.

        1. what's your meaning about 'first place'?and how to avoid duplicated classes?

        2. as I asked:
        the following code:
        GroovyScriptEngine engine = new GroovyScriptEngine(root,Thread.currentThread().getContextClassLoader());
        engine.loadScriptByName(scriptName);

        If I modified the script soruce,and I found using

        Class.forName("myClassName", true, engine.getGroovyClassLoader());
        and
        engine.getGroovyClassLoader().loadClass("myClassName")
        will return different class instance.
        and I think this will confuse most people.

        3.I want to implement reloading Class defined in groovy script,and the instance of the loaded class will be serialized to memcached(using java memcached client),and when I retrieve back the object from memcached,I will get a exception,event if the object is the instance of the new-loading class.. and I found this is difficult thing.any groovy's official solution?

        Show
        L.J.W added a comment - Hi jochen,I am not understand the groovyclassloader logic. 1. what's your meaning about 'first place'?and how to avoid duplicated classes? 2. as I asked: the following code: GroovyScriptEngine engine = new GroovyScriptEngine(root,Thread.currentThread().getContextClassLoader()); engine.loadScriptByName(scriptName); If I modified the script soruce,and I found using Class.forName("myClassName", true, engine.getGroovyClassLoader()); and engine.getGroovyClassLoader().loadClass("myClassName") will return different class instance. and I think this will confuse most people. 3.I want to implement reloading Class defined in groovy script,and the instance of the loaded class will be serialized to memcached(using java memcached client),and when I retrieve back the object from memcached,I will get a exception,event if the object is the instance of the new-loading class.. and I found this is difficult thing.any groovy's official solution?
        Hide
        L.J.W added a comment -

        please ref my comment.

        Show
        L.J.W added a comment - please ref my comment.
        Hide
        blackdrag blackdrag added a comment -

        I may have misunderstood you... that
        Class.forName("myClassName", true, engine.getGroovyClassLoader());
        and
        engine.getGroovyClassLoader().loadClass("myClassName")
        return different classes without GCL having class recompilation enabled should not happen, since these lines are normally really almost equal.

        As for memcache. I strongly assume it keeps references to the classes and therefore cannot handle a situation with redefined classes. The ClassLoaders of the JVM are quite limited, we can do always only so much, and depending on the implementation of memcache, this might be a case in which we can do nothing.

        Show
        blackdrag blackdrag added a comment - I may have misunderstood you... that Class.forName("myClassName", true, engine.getGroovyClassLoader()); and engine.getGroovyClassLoader().loadClass("myClassName") return different classes without GCL having class recompilation enabled should not happen, since these lines are normally really almost equal. As for memcache. I strongly assume it keeps references to the classes and therefore cannot handle a situation with redefined classes. The ClassLoaders of the JVM are quite limited, we can do always only so much, and depending on the implementation of memcache, this might be a case in which we can do nothing.
        Hide
        L.J.W added a comment -

        Can you give me more clear answer to the three question?am I describe the question clearly?
        1. what's your meaning about 'first place'?and how to avoid duplicated classes?
        2. After I modified the script source,I find Class.forName and classloader.loadClass will return different result.
        3. After I modified the script source,I reload and get the Class instance(MyClass@1298).then, create a object instance of this new reload Class(MyClass@1298),then serialized the object to memcached,then retrieve the object back,I will get a Exception. I study memcached source,and find the cause is Class.forName("className",true,GCL) will return Class instance(MyClass@1132),not the reload Class instance(MyClass@1298).

        Show
        L.J.W added a comment - Can you give me more clear answer to the three question?am I describe the question clearly? 1. what's your meaning about 'first place'?and how to avoid duplicated classes? 2. After I modified the script source,I find Class.forName and classloader.loadClass will return different result. 3. After I modified the script source,I reload and get the Class instance(MyClass@1298).then, create a object instance of this new reload Class(MyClass@1298),then serialized the object to memcached,then retrieve the object back,I will get a Exception. I study memcached source,and find the cause is Class.forName("className",true,GCL) will return Class instance(MyClass@1132),not the reload Class instance(MyClass@1298).
        Hide
        blackdrag blackdrag added a comment -

        After investigating the issue here again today I found a possible explanation as of why you see different behaviours. Class#forName will associate a class loader with a certain class with the result of for example findLoadedClasses returning true for that loader then (if it is the defining loader). loadClass does this normally not. Class#forName uses the VM internal structures instead of our always calling loadClass. That's why they could return different classes. Of course it can still result in calling loadClass, if findLoadedClasses wasn't successful.

        Now if we do loadClass("myClassName") we return what is in the class cache. After a recompilation this may differ from what is in the VM structures. There is nothing we can do about this, since this is about native VM structures. I can only advice not to use Class#forName if you want a changed class.

        Show
        blackdrag blackdrag added a comment - After investigating the issue here again today I found a possible explanation as of why you see different behaviours. Class#forName will associate a class loader with a certain class with the result of for example findLoadedClasses returning true for that loader then (if it is the defining loader). loadClass does this normally not. Class#forName uses the VM internal structures instead of our always calling loadClass. That's why they could return different classes. Of course it can still result in calling loadClass, if findLoadedClasses wasn't successful. Now if we do loadClass("myClassName") we return what is in the class cache. After a recompilation this may differ from what is in the VM structures. There is nothing we can do about this, since this is about native VM structures. I can only advice not to use Class#forName if you want a changed class.
        Hide
        blackdrag blackdrag added a comment -

        I realize that this conclusion does not help with the memcache issue, but I really think there is nothing we can do here

        Show
        blackdrag blackdrag added a comment - I realize that this conclusion does not help with the memcache issue, but I really think there is nothing we can do here

          People

          • Assignee:
            blackdrag blackdrag
            Reporter:
            L.J.W
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: