Checkpoint Restart causes Pipeline Deserialization/FlowHead Error

Article ID:360057119951
2 minute readKnowledge base

Issue

A Checkpoint restart on a Pipeline stage causes a Deserializatoin/FlowhHead Error with output such as below:

hudson.remoting.ProxyException: an exception which occurred:
    in object of type org.jenkinsci.plugins.workflow.cps.FlowHead
    in field org.jenkinsci.plugins.workflow.cps.CpsThread.head
    in object org.jenkinsci.plugins.workflow.cps.CpsThread@69f1cf1d
    ...
    Caused: hudson.remoting.ProxyException: java.lang.IllegalStateException: FlowHead loading problem at deserialize: Null FlowHead with id 22 in execution CpsFlowExecution
    at org.jenkinsci.plugins.workflow.cps.FlowHead.readResolve(FlowHead.java:196)

Description

This deserialization issue on checkpoints is possible when using the Pipeline Groovy/workflow-cps Plugin on Version 2.50+ and using nested parallel blocks before a checkpoint call.

Resolution

The fix involves switching from a nested parallel format (calling a parallel within a parallel) and instead moving to combining these parallels into a single parallel call.

For example, the code below shows a nested parallel which would trigger this deserialization issue if a checkpoint was used directly after:

def labels = ['node1', 'node2'] // labels for Jenkins node types we will build on
def environments = ['dev', 'prod'];
def outerParallel = [:]

// The following creates the Nested Parallels:
for (x in labels) {
    def label = x // Need to bind the label variable before the closure - can't do 'for (label in labels)'

    // Create a map to pass in to the 'parallel' step so we can fire all the builds at once
    outerParallel[label] = {
        def nestedParallel = [:]
        for (y in environments) {
            def env = y
            node(label) {
                nestedParallel[env] = {
                    echo "on label $label doing work in environment $env"
                }
            }
        }
        parallel nestedParallel
    }
}
parallel outerParallel

Below is the recommended method for converting this nested parallel to an unnested parallel using a single parallel call.

def singleBigParallel = [:]
for (x in labels) {
    for (y in environments) {
        // Closure binding
        def label = x
        def env = y
        node(label) {
            singleBigParallel["$label-$env"] = { // Creates a branch for each combination of label and environment
                echo "on label $label doing work in environment $env"
            }
        }
    }
}
parallel singleBigParallel
This article is part of our Knowledge Base and is provided for guidance-based purposes only. The solutions or workarounds described here are not officially supported by CloudBees and may not be applicable in all environments. Use at your own discretion, and test changes in a safe environment before applying them to production systems.