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