groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Alain Stalder <astal...@span.ch>
Subject Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode
Date Mon, 27 Feb 2017 21:36:27 GMT
Tonight I got this to load the EmailException class a few times without 
any changes to the source - so it must be a race condition.

I guess the static initializer at the bottom is a call to 
Grape#grab(Map<String, Object> dependency) with the effect of adding the 
commons-email JAR to the classpath.

And I guess the race condition is with the VM trying to load classes in 
the script in parallel, including EmailException.class which is only 
available after the commons-email JAR has been added to the classpath...

(For additional verification, instead of calling newInstance(), I added 
an empty static init() method in the demoScriptText and called it in 
order to run static initializers in the script, and again 
NoClassDefFoundError.)

Is this known behavior? Is there a reported issue for it? Any ideas for 
a workaround?

As a "stupid workaround", I fed the following script text to loader2 
before loading the script from the class file, and that worked:

def demoStupidWorkaroundText = """\
@Grab('org.apache.commons:commons-email:1.3.3')
import org.apache.commons.mail.*
"""

I guess if I had a defined set of scripts and remembered all all grabs 
during compilation, I could feed the GroovyClassLoader before loading 
the classes from bytecode, but this would be quite ugly...

Without previous initialization of the GroovyClassLoader, I guess any 
grabbed class that is used in a Groovy source would have to be wrapped 
during compilation into something that loads it dynamically by name, 
which sounds very hairy in practice...

Any ideas?

Alain

On 27.02.17 13:06, Alain Stalder wrote:
> PS: Below is what I get when I look at the Demo.class with the "JD" 
> decompiler...
>
> Is this maybe some kind of Grape concurrency issue similar to 
> GROOVY-7407?
>
>   https://issues.apache.org/jira/browse/GROOVY-7407
>
> (I still get the same result when I am applying the workaround listed 
> there - which I wrote myself, by the way - but maybe the workaround 
> does not cover all possibilities?)
>
>
> import groovy.grape.Grape;
> import groovy.lang.Binding;
> import groovy.lang.Script;
> import org.apache.commons.mail.EmailException;
> import org.apache.commons.mail.SimpleEmail;
> import org.codehaus.groovy.runtime.InvokerHelper;
> import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
> import org.codehaus.groovy.runtime.callsite.CallSite;
>
> public class Demo extends Script
> {
>   public Demo()
>   {
>     Demo this;
>     CallSite[] arrayOfCallSite = $getCallSiteArray();
>   }
>
>   public Demo(Binding context)
>   {
>     super(context);
>   }
>
>   public static void main(String[] args)
>   {
>     CallSite[] arrayOfCallSite = $getCallSiteArray();
>     arrayOfCallSite[0].call(InvokerHelper.class, Demo.class, args);
>   }
>
>   public Object run()
>   {
>     CallSite[] arrayOfCallSite = $getCallSiteArray();
>     try { try { 
> arrayOfCallSite[1].call(arrayOfCallSite[2].callConstructor(SimpleEmail.class));
>       } catch (EmailException e)
>       {
>         return Boolean.valueOf(true);
>       } } finally {  }
>
>     return Boolean.valueOf(false); return null;
>   }
>
>   static
>   {
>     $getCallSiteArray()[3].callStatic(Grape.class, 
> ScriptBytecodeAdapter.createMap(new Object[0]), 
> ScriptBytecodeAdapter.createMap(new Object[] { "group", 
> "org.apache.commons", "module", "commons-email", "version", "1.3.3" }));
>   }
> }
>
>
> On 27.02.17 10:51, Alain Stalder wrote:
>> Maybe someone can point me in the right direction regarding the 
>> following issue with Grape...
>>
>> Issue in code (in words further below):
>>
>> -- 
>> import org.codehaus.groovy.control.CompilerConfiguration
>>
>> def demoScriptText = """\
>> @Grab('org.apache.commons:commons-email:1.3.3')
>> import org.apache.commons.mail.*
>>
>> try {
>>   new SimpleEmail().send()
>> } catch (EmailException e) {
>>   // expected
>>   return true
>> }
>> return false
>> """
>>
>> // setup/clean target directory for script class
>> File targetDir = new File('demo-target')
>> if (!targetDir.exists()) {
>>   assert targetDir.mkdir()
>> }
>> File classFile = new File(targetDir, 'Demo.class')
>> if (classFile.exists()) {
>>   assert classFile.delete()
>> }
>>
>> // parse+run script with first GroovyClassLoader
>> def config = new CompilerConfiguration()
>> config.setTargetDirectory(targetDir)
>> def loader1 = new 
>> GroovyClassLoader(Thread.currentThread().contextClassLoader, config)
>> def script1 = loader1.parseClass(demoScriptText, 
>> 'Demo.groovy').newInstance()
>> assert script1.run()
>>
>> // load compiled script class with second GroovyClassLoader
>> def loader2 = new GroovyClassLoader()
>> loader2.addClasspath(targetDir.path)
>> try {
>>   def script2 = loader2.loadClass('Demo').newInstance()
>>   assert false
>> } catch (NoClassDefFoundError e) {
>>   println 'Failed as expected:'
>>   println()
>>   println e
>>   println()
>>   println e.stackTrace
>> }
>> -- 
>>
>> Issue in words:
>>
>> - Compile a script that uses Grape to load a dependency and use a class
>>   that extends java.lang.Exception from that dependency in the script.
>> - Loading and running that script with the GroovyClassLoader that 
>> compiled
>>   it, works fine.
>> - Then try to load the class from its class file (bytecode) with a new
>>    GroovyClassLoader; this fails with a NoClassDefFoundError.
>> - (Apparently has nothing specifically to do with commons-email, same 
>> effect
>>   with Commons httpclient and an Exception class declared there.)
>>
>> The strange thing about this is that in the example above, 
>> EmailException is
>> not found, but SimpleEmail - which is in the same grabbed JAR - is 
>> found!
>> In fact, if I change the catch in the script to "catch (Exception 
>> e)", I can
>> load and run the script from its class file without any problems.
>>
>> Question:
>>
>> Any ideas what could cause this, or where to look closer or maybe how to
>> circumvent this by compiling the class differently?
>>
>> (My concrete use case is with Grengine, where I do not compile to a 
>> class
>> in the file system, but to cached bytecode.)
>>
>> Alain
>>
>> -- 
>>
>> Here is the output I get (Groovy 2.4.8, JDK 1.8.0_121, Mac):
>>
>> Failed as expected:
>>
>> java.lang.NoClassDefFoundError: org/apache/commons/mail/EmailException
>>
>> [java.lang.Class.forName0(Native Method), 
>> java.lang.Class.forName(Class.java:348), 
>> org.codehaus.groovy.runtime.callsite.CallSiteArray$1.run(CallSiteArray.java:68),

>> org.codehaus.groovy.runtime.callsite.CallSiteArray$1.run(CallSiteArray.java:65),

>> java.security.AccessController.doPrivileged(Native Method), 
>> org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallStaticSite(CallSiteArray.java:65),

>> org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:162),

>> org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48),

>> org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113),

>> org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117),

>> demo.run(demo.groovy:37), 
>> groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:263), 
>> groovy.lang.GroovyShell.run(GroovyShell.java:518), 
>> groovy.lang.GroovyShell.run(GroovyShell.java:497), 
>> groovy.lang.GroovyShell.run(GroovyShell.java:170), 
>> groovy.lang.GroovyShell$run$1.call(Unknown Source), 
>> groovy.ui.Console$_runScriptImpl_closure16.doCall(Console.groovy:1005), 
>> groovy.ui.Console$_runScriptImpl_closure16.doCall(Console.groovy), 
>> sun.reflect.GeneratedMethodAccessor296.invoke(Unknown Source), 
>> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43),

>> java.lang.reflect.Method.invoke(Method.java:498), 
>> org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93), 
>> groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325), 
>> org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294),

>> groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1027), 
>> groovy.lang.Closure.call(Closure.java:414), 
>> groovy.lang.Closure.call(Closure.java:408), 
>> groovy.lang.Closure.run(Closure.java:495), 
>> java.lang.Thread.run(Thread.java:745)]
>>
>> .
>>
>
> .
>


Mime
View raw message