bertramdev / asset-pipeline

The core implementation of the asset pipeline for the jvm
193 stars 92 forks source link

Startup sometimes fails when used together with resources plugin #148

Closed stefanrother closed 6 years ago

stefanrother commented 7 years ago

Problem

We are migrating our Grails 2.2.2 application from the resources plugin to the asset-pipeline plugin. Some views are already migrated but most of them still use the resources plugin.

Sometimes the application does not start and produces the following stack trace on initialization of the asset-pipeline: | 2017-01-27 09:16:25,342 [localhost-startStop-1] ERROR org.grails.plugin.resource.ResourceProcessor - Unable to load resources groovy.lang.MissingMethodException: No signature of method: asset.pipeline.grails.CachingLinkGenerator.appendMapKey() is applicable for argument types: (java.lang.StringBuilder, java.util.LinkedHashMap) values: [resource, [plugin:jquery, dir:js/jquery, ...]] at asset.pipeline.grails.CachingLinkGenerator.makeKey(CachingLinkGenerator.groovy:55) at asset.pipeline.grails.CachingLinkGenerator.resource(CachingLinkGenerator.groovy:24) at org.grails.plugin.resource.ResourceProcessor.buildLinkToOriginalResource(ResourceProcessor.groovy:485) at org.grails.plugin.resource.ResourceModule$_closure1.doCall(ResourceModule.groovy:62) at org.grails.plugin.resource.ResourceModule.<init>(ResourceModule.groovy:58) at org.grails.plugin.resource.ResourceProcessor.defineModuleFromBuilder(ResourceProcessor.groovy:681) at org.grails.plugin.resource.ResourceProcessor$_loadModules_closure19.doCall(ResourceProcessor.groovy:802) at org.grails.plugin.resource.ResourceProcessor.loadModules(ResourceProcessor.groovy:802) at org.grails.plugin.resource.ResourceProcessor.reloadAll(ResourceProcessor.groovy:1075) at ResourcesGrailsPlugin$_closure3.doCall(ResourcesGrailsPlugin.groovy:172) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745)

The source of the problem seems to be buried in a bug in Groovy (GROOVY-3073 - Private inheritance bug: Closure accessing private method) and the fact that the class asset.pipeline.grails.CachingLinkGenerator extends org.codehaus.groovy.grails.web.mapping.CachingLinkGenerator and accesses the private method appendMapKey() from the super class via its makeKey() method.

We were surprised that this is possible (We did not expect the access to private methods of the super class to work) and that this sometimes fails.

The problem appears on our development machines (iMacs with OS X 10.11.6 and JDK 1.7.0_75) and our staging and production systems (Debian 8 with Tomcat 7.0.73 and JDK 1.7.0_111)

Workaround

Our workaround is to inject a modified copy of asset.pipeline.grails.CachingLinkGenerator that has copies of the private methods of the super class that were accessed by makeKey(). Via resources.groovy we inject the class the same way it is done in AssetPipelineGrailsPlugin.

davydotcom commented 7 years ago

Good find and thank you for reporting!. I’ll look into getting this resolved in a later build.

On Feb 22, 2017, at 6:49 AM, Stefan Rother-Stübs notifications@github.com wrote:

Problem

We are migrating our Grails 2.2.2 application from the resources plugin to the asset-pipeline plugin. Some views are already migrated but most of them still use the resources plugin.

Sometimes the application does not start and produces the following stack trace on initialization of the asset-pipeline: | 2017-01-27 09:16:25,342 [localhost-startStop-1] ERROR org.grails.plugin.resource.ResourceProcessor - Unable to load resources groovy.lang.MissingMethodException: No signature of method: asset.pipeline.grails.CachingLinkGenerator.appendMapKey() is applicable for argument types: (java.lang.StringBuilder, java.util.LinkedHashMap) values: [resource, [plugin:jquery, dir:js/jquery, ...]] at asset.pipeline.grails.CachingLinkGenerator.makeKey(CachingLinkGenerator.groovy:55) at asset.pipeline.grails.CachingLinkGenerator.resource(CachingLinkGenerator.groovy:24) at org.grails.plugin.resource.ResourceProcessor.buildLinkToOriginalResource(ResourceProcessor.groovy:485) at org.grails.plugin.resource.ResourceModule$_closure1.doCall(ResourceModule.groovy:62) at org.grails.plugin.resource.ResourceModule.(ResourceModule.groovy:58) at org.grails.plugin.resource.ResourceProcessor.defineModuleFromBuilder(ResourceProcessor.groovy:681) at org.grails.plugin.resource.ResourceProcessor$_loadModules_closure19.doCall(ResourceProcessor.groovy:802) at org.grails.plugin.resource.ResourceProcessor.loadModules(ResourceProcessor.groovy:802) at org.grails.plugin.resource.ResourceProcessor.reloadAll(ResourceProcessor.groovy:1075) at ResourcesGrailsPlugin$_closure3.doCall(ResourcesGrailsPlugin.groovy:172) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745)

The source of the problem seems to be buried in a bug in Groovy (GROOVY-3073 - Private inheritance bug: Closure accessing private method https://issues.apache.org/jira/browse/GROOVY-3073) and the fact that the class asset.pipeline.grails.CachingLinkGenerator extends org.codehaus.groovy.grails.web.mapping.CachingLinkGenerator and accesses the private method appendMapKey() from the super class via its makeKey() method.

We were surprised that this is possible (We did not expect the access to private methods of the super class to work) and that this sometimes fails.

The problem appears on our development machines (iMacs with OS X 10.11.6 and JDK 1.7.0_75) and our staging and production systems (Debian 8 with Tomcat 7.0.73 and JDK 1.7.0_111)

Workaround

Our workaround is to inject a modified copy of asset.pipeline.grails.CachingLinkGenerator that has copies of the private methods of the super class that were accessed by makeKey(). Via resources.groovy we inject the class the same way it is done in AssetPipelineGrailsPlugin.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/bertramdev/asset-pipeline/issues/148, or mute the thread https://github.com/notifications/unsubscribe-auth/AABaEl17P65Wk-E_SI8JiDOJ8EE2uzZ8ks5rfC7ZgaJpZM4MImmO.

jglapa commented 7 years ago

Seeing the same upgrading to Grails 3.2.8 from 3.2.6.

2017-04-13 15:33:14,550 [http-nio-8080-exec-4] DEBUG o.g.w.s.v.GroovyPageView - Error processing GroovyPageView: [views/login/auth.gsp:30] Error executing tag <g:form>: No signature of method: asset.pipeline.grails.CachingLinkGenerator.appendMapKey() is applicable for argument types: (java.lang.StringBuilder, org.grails.taglib.GroovyPageAttributes) values: [link, [method:post, action:auth, id:loginForm]] org.grails.taglib.GrailsTagException: [views/login/auth.gsp:30] Error executing tag <g:form>: No signature of method: asset.pipeline.grails.CachingLinkGenerator.appendMapKey() is applicable for argument types: (java.lang.StringBuilder, org.grails.taglib.GroovyPageAttributes) values: [link, [method:post, action:auth, id:loginForm]]

jukin-jerry commented 7 years ago

I have the same problem as @jglapa

Stepping through in a debugger, i can see that this line: https://github.com/bertramdev/asset-pipeline/blob/master/asset-pipeline-grails/src/main/groovy/asset/pipeline/grails/CachingLinkGenerator.groovy#L56

calls this method: https://github.com/grails/grails-core/blob/v3.2.8/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/CachingLinkGenerator.java#L104

when the exception is thrown.

samuelzyx commented 7 years ago

Same problem in createLink function (Grails updagre 3.2.9 from 3.2.7)

protected String generateLink(String action, String token) {
        createLink(base: "$request.scheme://$request.serverName:$request.serverPort$request.contextPath",
                controller: 'user', action: action, params: [t:token])
    }

Error in log

Caused by: groovy.lang.MissingMethodException: No signature of method: asset.pipeline.grails.CachingLinkGenerator.appendMapKey() is applicable for argument types: (java.lang.StringBuilder, org.grails.taglib.GroovyPageAttributes) values: [link, [base:http://localhost:8080, controller:user, action:resetPassword, ...]]
        at asset.pipeline.grails.CachingLinkGenerator.makeKey(CachingLinkGenerator.groovy:56)
        at org.grails.web.mapping.CachingLinkGenerator.link(CachingLinkGenerator.java:79)
        at org.grails.plugins.web.taglib.ApplicationTagLib.doCreateLink(ApplicationTagLib.groovy:382)
        at org.grails.plugins.web.taglib.ApplicationTagLib$_closure9.doCall(ApplicationTagLib.groovy:359)
        at org.grails.taglib.TagOutput.captureTagOutput(TagOutput.java:64)
        at grails.artefact.gsp.TagLibraryInvoker$Trait$Helper.methodMissing(TagLibraryInvoker.groovy:88)
        at btask.UserController.generateLink(UserController.groovy:859)
        at btask.UserController.forgotPassword(UserController.groovy:662)
        ... 47 common frames omitted
jukin-jerry commented 7 years ago

I started to implement @stefanrother 's workaround in his report, but I found that by reimplementing the private method it depends on another ton of private stuff in the class and doesn't seem feasible. I think the simple fix is for the base class (org.codehaus.groovy.grails.web.mapping.CachingLinkGenerator) to not mark that method as private. Anyone have any pull with the devs of that module? I've never forked grails framework else I'd just do it and submit a PR. My workaround was to stop using g:link which isn't feasible in anything but the short term.

jglapa commented 7 years ago

I wonder why this started happening with just a minor Grails version update. From the release notes it looks like the version was build with a newer Groovy 2.4.10. They warn that the StaticCompile is more strict though. You can theoretically bypass the private access by using reflection but I'm not sure if this is such a good idea.

jukin-jerry commented 7 years ago

Downgrading to Groovy 2.4.7 in my Grails 3.2.9 webapp project clears up this problem for me. I did this by:

  1. Adding "groovyVersion=2.4.7" to gradle.properties
  2. I'm sure there is a better way, but here is how i updated my build.gradle to explicitly override all groovy packages versions:

    dependencies {
    provided "org.codehaus.groovy:groovy:${groovyVersion}"
    provided "org.codehaus.groovy:groovy-all:${groovyVersion}"
    provided "org.codehaus.groovy:groovy-ant:${groovyVersion}"
    provided "org.codehaus.groovy:groovy-groovydoc:${groovyVersion}"
    provided "org.codehaus.groovy:groovy-json:${groovyVersion}"
    provided "org.codehaus.groovy:groovy-sql:${groovyVersion}"
    provided "org.codehaus.groovy:groovy-templates:${groovyVersion}"
    provided "org.codehaus.groovy:groovy-test:${groovyVersion}"
    provided "org.codehaus.groovy:groovy-xml:${groovyVersion}"
    
    console "org.codehaus.groovy:groovy:${groovyVersion}"
    console "org.codehaus.groovy:groovy-console:${groovyVersion}"
    console "org.codehaus.groovy:groovy-groovysh:${groovyVersion}"
    console "org.codehaus.groovy:groovy-swing:${groovyVersion}"
    console "org.codehaus.groovy:groovy-templates:${groovyVersion}"
    console "org.codehaus.groovy:groovy-xml:${groovyVersion}"
    ....
    } 
  3. Checked work with
    ./grailsw dependency-report --plain-output
jglapa commented 7 years ago

FYI I've raised an issue in the grails core project https://github.com/grails/grails-core/issues/10660

jglapa commented 7 years ago

UPDATE the method visibility has been changed to protected with this https://github.com/grails/grails-core/commit/7e48044add7b0d489a48e6a72e1c860b45a23668

This should be available with the next Grails release.

davydotcom commented 7 years ago

nice thanks!