1

I have a Jenkinspipeline written in Groovy looking like this:


@SuppressWarnings(["GroovyAssignabilityCheck", "unused"])
class AWSApplicationPipelines {

    Object context;
    Configuration configuration;
    Object environment;
    BuildJobDescriptor jobs;

    AWSApplicationPipelines(Object context, Configuration configuration, Object environment) {
        this.context = context
        this.configuration = configuration
        this.environment = environment
    }

    private void readJobs(String directory){
        this.context.dir(directory) {
            this.jobs = BuildJobDescriptor.fromJSONString(this.context.readFile(environment.JOB_FILE_NAME))
        }
    }

    void awsApplicationCreatePipeline() {
        try{
            this.readJobs(jobDirectory())
            this.context.parallel(this.prepareClusterCreationInParallel(BuildJobType.CREATE, jobs.free))
        } catch (Exception e) {
            this.context.currentBuild.result = "FAILURE"
            throw e
        } finally {
            Notifier.sendBuildStatusNotifications(context, environment)
        }
    }

    void awsApplicationDestroyPipeline() {
        try{
            this.readJobs(jobDirectory())
            this.context.parallel(this.prepareClusterDestructionInParallel(BuildJobType.DESTROY, jobs.free))
        } catch (Exception e) {
            this.context.currentBuild.result = "FAILURE"
            throw e
        } finally {
            Notifier.sendBuildStatusNotifications(context, environment)
        }
    }

    private Map<String, Closure> prepareClusterCreationInParallel(BuildJobType jobType, List<BuildJob> jobs){
        return jobs.collectEntries { job ->
            return [(jobType.capitalizedName() + " " + job.description): {
                executeOneJob(job, jobType)
                executeMultipleJobs(job, jobType)
            }]
        }
    }

    private Map<String, Closure> prepareClusterDestructionInParallel(BuildJobType jobType, List<BuildJob> jobs){
        return jobs.collectEntries { job ->
            return [(jobType.capitalizedName() + " " + job.description): {
                executeMultipleJobs(job, jobType)
                executeOneJob(asDestroyJob(job), jobType)
            }]
        }
    }

    private void preparePipeline(String directory, Closure<BuildJobDescriptor> pipeline) {
        try {
            this.context.dir(directory) {
                BuildJobDescriptor allJobs = BuildJobDescriptor.fromJSONString(this.context.readFile(environment.JOB_FILE_NAME))
                pipeline(allJobs)
            }
        } catch (Exception e) {
            this.context.currentBuild.result = "FAILURE"
            throw e
        } finally {
            Notifier.sendBuildStatusNotifications(context, environment)
        }
    }

    def executeOneJob = { BuildJob job, BuildJobType jobType ->
        executeJob(jobType, job.description, job)
    }

    private void executeJob(BuildJobType jobType, String stageName, BuildJob job) {
        this.context.stage(jobType.capitalizedName() + ' ' + stageName + ' Cluster') {
            this.context.build(job: job.name, parameters: job.parameters)
        }
    }

    def executeMultipleJobs = { BuildJob job, BuildJobType jobType ->
        executeJobsInParallel(job, jobType, ' services of ' + job.description + ' Cluster')
    }

    private void executeJobsInParallel(BuildJob job, BuildJobType jobType, String stageName) {
        this.context.stage(jobType.capitalizedName() + stageName) {
            List<BuildJob> dependantClusterServices = jobs.dependent.findAll { job.name == it.cluster }
            if (BuildJobType.CREATE == jobType) {
                this.context.parallel(this.executeJobs(jobType, dependantClusterServices))
            } else {
                this.context.parallel(this.executeJobs(jobType, asDestroyJobs(dependantClusterServices)))
            }
        }
    }

    private Map<String, Object> executeJobs(BuildJobType jobType, List<BuildJob> jobs) {
        return jobs.collectEntries { job ->
            return [(jobType.capitalizedName() + " " + job.description): {
                this.context.build(job: job.name, parameters: job.parameters)
            }]
        }
    }

    private List<BuildJob> asDestroyJobs(List<BuildJob> jobs) {
        return jobs.collect { it.toDestroyJob() }
    }

    private BuildJob asDestroyJob(BuildJob job) {
        return job.toDestroyJob()
    }

    private String jobDirectory() {
       return "AWS_" + this.configuration.profile
    }
}

It executes other Jenkinsjobs in parallel with dependencies, reading them from a Json File:

{"free": [
   {
     "name": "dev-rocketchat-cluster",
     "description": "RocketChat",
     "parameters": []
   }],
 "dependent": [
   {
     "name": "dev-rocketchat-notifier",
     "description": "RocketChat Notifier",
     "parameters": [],
     "cluster": "dev-rocketchat-cluster"
   }
]}

It worked perfectly to some point. After adding some more jobs to the json file it stopped working with the following exception:

an exception which occurred:
    in field com.cloudbees.groovy.cps.impl.FunctionCallEnv.locals
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@24a6e424
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@281b3516
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@2276d8cb
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@565ad816
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@1e527e65
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@59e54766
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.LoopBlockScopeEnv@7e3eb488
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@20b3a2d1
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.ClosureCallEnv@4de6f191
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@785dfa91
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@4faa3519
    in field com.cloudbees.groovy.cps.Continuable.e
    in object org.jenkinsci.plugins.workflow.cps.SandboxContinuable@15d40753
    in field org.jenkinsci.plugins.workflow.cps.CpsThread.program
    in object org.jenkinsci.plugins.workflow.cps.CpsThread@1346f429
    in field org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.threads
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@6e71d071
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@6e71d071
Also:   an exception which occurred:
    in field com.cloudbees.groovy.cps.impl.FunctionCallEnv.locals
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@57f53087
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@6b5718e7
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@64769870
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@10e07939
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.LoopBlockScopeEnv@ab4624b
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@3b8bd3fc
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.ClosureCallEnv@da4fd38
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@209c2b02
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@6808606f
    in field com.cloudbees.groovy.cps.Continuable.e
    in object org.jenkinsci.plugins.workflow.cps.SandboxContinuable@12596682
    in field org.jenkinsci.plugins.workflow.cps.CpsThread.program
    in object org.jenkinsci.plugins.workflow.cps.CpsThread@483849df
    in field org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.threads
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@6e71d071
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@6e71d071
    Also:   an exception which occurred:
    in field com.cloudbees.groovy.cps.impl.FunctionCallEnv.locals
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@6f2ff0a5
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@9ee4c72
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@49b65473
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@fe49e29
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@146757db
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@414099f6
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.LoopBlockScopeEnv@3bb32ed
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@1c66d08e
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.ClosureCallEnv@68c3185d
    in field com.cloudbees.groovy.cps.impl.ProxyEnv.parent
    in object com.cloudbees.groovy.cps.impl.BlockScopeEnv@48222852
    in field com.cloudbees.groovy.cps.impl.CallEnv.caller
    in object com.cloudbees.groovy.cps.impl.FunctionCallEnv@175f68a9
    in field com.cloudbees.groovy.cps.Continuable.e
    in object org.jenkinsci.plugins.workflow.cps.SandboxContinuable@12596682
    in field org.jenkinsci.plugins.workflow.cps.CpsThread.program
    in object org.jenkinsci.plugins.workflow.cps.CpsThread@483849df
    in field org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.threads
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@6e71d071
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@6e71d071
        Caused: java.io.NotSerializableException: java.lang.reflect.Field
            at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:926)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:568)
            at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
            at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
            at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50)
            at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179)
            at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:344)
            at java.util.HashMap.internalWriteEntries(HashMap.java:1793)
            at java.util.HashMap.writeObject(HashMap.java:1363)
            at sun.reflect.GeneratedMethodAccessor43.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.lang.reflect.Method.invoke(Method.java:498)
            at org.jboss.marshalling.reflect.JDKSpecific$SerMethods.callWriteObject(JDKSpecific.java:156)
            at org.jboss.marshalling.reflect.SerializableClass.callWriteObject(SerializableClass.java:191)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:1028)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:920)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1082)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:1040)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:920)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1082)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:1040)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:1019)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:920)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteFields(RiverMarshaller.java:1082)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:1040)
            at org.jboss.marshalling.river.RiverMarshaller.doWriteSerializableObject(RiverMarshaller.java:1019)

After some googleing i found it, there seems to be an issue with non-serializable objects. Though i wonder why it did work at all up to the point of failure. And how do i find out how to fix this?

The solutions seems to be to extract the non-serializable code into a @NonCPS annotated method, yet i have no clue where though.

Since JSONSlurper seems to be a problem, is there an issue with this part?

import groovy.json.JsonSlurper

class BuildJobDescriptor {

    static BuildJobDescriptor fromJSONString(String data) {
        Map raw =  new JsonSlurper().parse(data.getBytes())

        return new BuildJobDescriptor(raw)
    }

    List<BuildJob> free
    List<BuildJob> dependent

    private BuildJobDescriptor(Map jobDescriptions) {
        this.free = jobDescriptions.free.collect { new BuildJob(it as Map) }
        this.dependent = jobDescriptions.dependent.collect { new BuildJob(it as Map) }
    }
}

w.eric
  • 321
  • 5
  • 15
  • 1
    The `JsonSlurper` class does have issues in Jenkins Groovy sometimes with being non-serializable. For output, `JsonOutput` will work. For input, you have to search around on SO to find good solutions. Here would be one I found in a quick search: https://stackoverflow.com/a/44718808/5343387 – Matthew Schuchard Sep 02 '20 at 13:09
  • i replaced the JsonSlurper with the JenkinsUtil readJSON. Since the problem only appears when executing a lot of jobs in parallel (not reproducable for me) i will see if it works only by tomorrow. Thanks for now! – w.eric Sep 02 '20 at 14:14
  • 1
    You can also make sure your classes are Serializable themselves. If a class instance appears as a local variable in the pipeline, it will cause this error. – Patrice M. Sep 02 '20 at 16:35
  • yes, i checked this but didnt find a case :( the pipeline is also working perfectly with some JSON files, but not all, though i don't so any specific difference in the json files excepte the number of entries. – w.eric Sep 03 '20 at 06:36

0 Answers0