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

    • 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)

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

          David Resnick created issue -
          David Resnick made changes -
          Description Original: 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

          This script gives an idea of the delay interval added to the end of every pipeline build.

          ```
          import java.lang.ref.WeakReference

          def 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) / 1000

                  println("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)
          ```
          New: 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

          This script gives an idea of the delay interval added to the end of every pipeline build.


          {{{}import java.lang.ref.WeakReference{}}}{{{}def 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) / 1000{}}}{{{}println("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)}}
          David Resnick made changes -
          Description Original: 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

          This script gives an idea of the delay interval added to the end of every pipeline build.


          {{{}import java.lang.ref.WeakReference{}}}{{{}def 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) / 1000{}}}{{{}println("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)}}
          New: 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.WeakReference{}}}{{{}def 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) / 1000{}}}{{{}println("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: