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)
Environment
-
CloudBees CI (CloudBees Core) on modern cloud platforms - Managed controller
-
CloudBees CI (CloudBees Core) on modern cloud platforms - Operations Center
-
CloudBees CI (CloudBees Core) on traditional platforms - Client controller
-
CloudBees CI (CloudBees Core) on traditional platforms - Operations Center
-
CloudBees Jenkins Enterprise - Managed controller
-
CloudBees Jenkins Enterprise - Operations center
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