• Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Major Major
    • job-dsl-plugin
    • None
    • Job DSL 1.39
      Jenkins 1.631

      We've been tracing some errors since upgrading to 1.39 where certain classes no longer appeared to exist.

      We have a jar file which we add via "additionalClasspath" on our seed job.

      On inspection of the jar, the closure files do exist.

      ```
      16:37:06 java.lang.NoClassDefFoundError: com/freelancer/jobdsl/dsl/helpers/FreelancerToxWorkflowContext$_init_closure2_closure3
      16:37:06 at com.freelancer.jobdsl.dsl.helpers.FreelancerToxWorkflowContext$_init_closure2.doCall(FreelancerToxWorkflowContext.groovy:60)
      ```

      The weird thing is that we tried make some changes to the source code, but the errors still point to the same location (says line 60) where it can't find the class.

      Furthermore, we tried to track down which commit this started happening, and found it started happening when trying to clean up the classloader: https://github.com/jenkinsci/job-dsl-plugin/commit/98ad52ac530dc58156807cd7dca29f323301042c?w=1

          [JENKINS-30832] Seed job encounters `NoClassDefFoundError`

          Can you create a small reproducer? Do you use meta programming aka monkey patching?

          Daniel Spilker added a comment - Can you create a small reproducer? Do you use meta programming aka monkey patching?

          Ray H added a comment - - edited

          Unfortunately we do We haven't figured out how to get the same succinctness without MonkeyPatching. This is roughly our classes involved, I've omitted imports for brevity:

          final class MonkeyPatcher {
              static {
                  ToxWorkflow.init()
              }
          
              // each DSL script calls this to ensure the monkey patching only happens once
              static void init() { }
          }
          
          class ToxWorkflow {
              static function init() {
                   Job.metaClass.fltox = { Closure null =>              
                       steps {                      // <— complains that this closure could not be found
                           virtualenv()
                           //..   
                       }
                       publishers {
                          //..
                       }
                   }
              }
          }
          

          DSL Script:

          MonkeyPatcher.init()
          
          freeStyleJob('test') {
              fltox()
          }
          

          ```

          I've added some prints inside MonkeyPatcher's static block, and I noticed that when the seed job runs successfully, the prints run for each job script that gets executed. When it fails, the prints don't show up (i.e. static block not being called)

          Ray H added a comment - - edited Unfortunately we do We haven't figured out how to get the same succinctness without MonkeyPatching. This is roughly our classes involved, I've omitted imports for brevity: final class MonkeyPatcher { static { ToxWorkflow.init() } // each DSL script calls this to ensure the monkey patching only happens once static void init() { } } class ToxWorkflow { static function init() { Job.metaClass.fltox = { Closure null => steps { // <— complains that this closure could not be found virtualenv() //.. } publishers { //.. } } } } DSL Script: MonkeyPatcher.init() freeStyleJob( 'test' ) { fltox() } ``` I've added some prints inside MonkeyPatcher's static block, and I noticed that when the seed job runs successfully, the prints run for each job script that gets executed. When it fails, the prints don't show up (i.e. static block not being called)

          Sam Gleske added a comment -

          Don't omit imports; they're relevant. If you're importing a class that is not built into Jenkins then you need a gradle task to download the dependencies and include them in the classpath for the seed job. See wiki documentation on this https://github.com/jenkinsci/job-dsl-plugin/wiki/User-Power-Moves#using-libraries

          Sam Gleske added a comment - Don't omit imports; they're relevant. If you're importing a class that is not built into Jenkins then you need a gradle task to download the dependencies and include them in the classpath for the seed job. See wiki documentation on this https://github.com/jenkinsci/job-dsl-plugin/wiki/User-Power-Moves#using-libraries

          Ray H added a comment -

          We already build a jar file and add it to the classpath via `additionalClasspath`. What I'm describing here is a regression we noticed. This worked for us in v1.38.

          There's only one import at the top (which is to import MonkeyPatcher).

          Ray H added a comment - We already build a jar file and add it to the classpath via `additionalClasspath`. What I'm describing here is a regression we noticed. This worked for us in v1.38. There's only one import at the top (which is to import MonkeyPatcher).

          The problem with meta programming is that patched Job DSL classes will reference classed loaded from the additional classpath and thus creating a classloader leak. Closing the classloader manifests this problem.

          You can try to de-register your patches, e.g. by using GroovySystem.metaClassRegistry.removeMetaClass(Job). Can you test that and report the result?

          Daniel Spilker added a comment - The problem with meta programming is that patched Job DSL classes will reference classed loaded from the additional classpath and thus creating a classloader leak. Closing the classloader manifests this problem. You can try to de-register your patches, e.g. by using GroovySystem.metaClassRegistry.removeMetaClass(Job) . Can you test that and report the result?

          Brandon Fryslie added a comment - - edited

          de-register your patches, e.g. by using GroovySystem.metaClassRegistry.removeMetaClass(Job)

          I can verify this fixed the problem for me. I'm monkey patching JobParent to add extensions that generate entire jobs, which was causing this error:

          java.lang.NoClassDefFoundError: com/rallydev/jenkins/jobdsl/GenerateImageBuildJob$_addCommands_closure1_closure2

          We add the bindings at the top of the DSL script and remove the bindings at the end of the DSL using the code Daniel posted.

          Using Jenkins 2.39 and job-dsl 1.55

          Brandon Fryslie added a comment - - edited de-register your patches, e.g. by using GroovySystem.metaClassRegistry.removeMetaClass(Job) I can verify this fixed the problem for me. I'm monkey patching JobParent to add extensions that generate entire jobs, which was causing this error: java.lang.NoClassDefFoundError: com/rallydev/jenkins/jobdsl/GenerateImageBuildJob$_addCommands_closure1_closure2 We add the bindings at the top of the DSL script and remove the bindings at the end of the DSL using the code Daniel posted. Using Jenkins 2.39 and job-dsl 1.55

            jamietanna Jamie Tanna
            rymndhng Ray H
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: