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

Job not completing for extended time despite completing all tasks due to 100,000s "ignored" class loaders in cleanUpGlobalClassValue

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Minor Minor
    • workflow-cps-plugin
    • None

      Due to a job-dsl script that runs every 5 minutes creating thousands of "temporary" class loaders, pipelines builds take longer and longer to complete, though they complete all tasks.

      Turning on FINEST logging for CpsFlowExecution showed these types of entries repeated endlessly:

      Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution
      ignoring class sfdc.factory.AppJobFactory$_addStopAllApps_closure16$_closure99 with loader groovy.lang.GroovyClassLoader$InnerLoader@19b1f70
      Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution
      ignoring class groovy.tmp.templates.StreamingTemplateScript324068 with loader groovy.lang.GroovyClassLoader$InnerLoader@7164c9ed
      Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution
      ignoring class groovy.tmp.templates.StreamingTemplateScript229000$_getTemplate_closure1$_closure3 with loader groovy.lang.GroovyClassLoader$InnerLoader@38a504f0

      I'm encountering this on release 3867.v535458ce43fd, though it seems that the latest code is going to behave in more or less the same way.

      ------------------

      This script gives an idea of the delay interval added to the end of every pipeline build by just iterating on the classloaders (and printing the count).

      import java.lang.ref.WeakReferencedef timeIterationOfGlobalClassValues(ClassLoader loader) {
      try {
      // Get the ClassInfo class
      def classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo")// Access the globalClassValue field
      def globalClassValueF = classInfoC.getDeclaredField("globalClassValue")
      globalClassValueF.setAccessible(true)
      def globalClassValue = globalClassValueF.get(null)// Check if GroovyClassValuePreJava7 is in use
      def groovyClassValuePreJava7C = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7")
      if (!groovyClassValuePreJava7C.isInstance(globalClassValue)){{{}

      { println("Not using GroovyClassValuePreJava7") return }

      {}}}// Access the map field
      def mapF = groovyClassValuePreJava7C.getDeclaredField("map")
      mapF.setAccessible(true)
      def map = mapF.get(globalClassValue)// Get map entries
      def groovyClassValuePreJava7Map = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7\$GroovyClassValuePreJava7Map")
      def entries = groovyClassValuePreJava7Map.getMethod("values").invoke(map)
      def removeM = groovyClassValuePreJava7Map.getMethod("remove", Object.class)// Get value from entries
      def entryC = Class.forName("org.codehaus.groovy.util.AbstractConcurrentMapBase\$Entry")
      def getValueM = entryC.getMethod("getValue")// To store classes to remove
      def toRemove = []
      def ignoredClassLoaderCount = 0 // Counter for ignored class loaders// Start timing the iteration
      def startTime = System.nanoTime()try {
      // For Groovy 2.4.8+
      def classRefF = classInfoC.getDeclaredField("classRef")
      classRefF.setAccessible(true)
      for (entry in entries){{{}

      { def value = getValueM.invoke(entry) def clazz = ((WeakReference<Class<?>>) classRefF.get(value)).get() if (clazz != null) toRemove << clazz }

      {}}}} catch (NoSuchFieldException e) {
      // For Groovy 2.4.7 and below
      def klazzF = classInfoC.getDeclaredField("klazz")
      klazzF.setAccessible(true)
      for (entry in entries){{{}

      { def value = getValueM.invoke(entry) def clazz = klazzF.get(value) if (clazz != null) toRemove << clazz }

      {}}}}// Iterate and remove classes not associated with the provided loader
      def it = toRemove.iterator()
      while (it.hasNext()) {
      def clazz = it.next()
      try {
      def encounteredLoader = clazz.getClassLoader()
      if (encounteredLoader != loader){{{}

      { it.remove() // Remove class if the loader does not match ignoredClassLoaderCount++ // Increment counter }

      {}}}} catch (Error e) {
      println("Error: ${e}")
      e.printStackTrace()
      }
      }// End timing
      def endTime = System.nanoTime()// Calculate the elapsed time in milliseconds
      def elapsedTimeInMs = (endTime - startTime) / 1_000_000
      def minutes = (elapsedTimeInMs / 60000) as int
      def seconds = ((elapsedTimeInMs.longValue() % 60000) as int) / 1000println("Total time for iteration: ${minutes} minute(s) and ${seconds} second(s)")
      println("Total ignored class loaders: ${ignoredClassLoaderCount}")
      } catch (Exception e) {
      println("Error: ${e}")
      e.printStackTrace()
      }
      }// Example usage in Jenkins Script Console
      def loader = Jenkins.instance.pluginManager.uberClassLoader
      timeIterationOfGlobalClassValues(loader)

            Unassigned Unassigned
            david_resnick David Resnick
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: