Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-30832

Seed job encounters `NoClassDefFoundError`

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Open (View Workflow)
    • Priority: Major
    • Resolution: Unresolved
    • Component/s: job-dsl-plugin
    • Labels:
      None
    • Environment:
      Job DSL 1.39
      Jenkins 1.631
    • Similar Issues:

      Description

      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

        Attachments

          Activity

          Hide
          daspilker Daniel Spilker added a comment -

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

          Show
          daspilker Daniel Spilker added a comment - Can you create a small reproducer? Do you use meta programming aka monkey patching?
          Hide
          rymndhng 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)

          Show
          rymndhng 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)
          Hide
          sag47 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

          Show
          sag47 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
          Hide
          rymndhng 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).

          Show
          rymndhng 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).
          Hide
          daspilker 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?

          Show
          daspilker 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?
          Hide
          brandonfryslie 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

          Show
          brandonfryslie 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

            People

            • Assignee:
              daspilker Daniel Spilker
              Reporter:
              rymndhng Ray H
            • Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

              • Created:
                Updated: