Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -732,51 +732,54 @@ private synchronized String getHeadsAsString() {
@GuardedBy("this")
void createPlaceholderNodes(Throwable failureReason) throws Exception {
synchronized (this) {
this.done = true;

if (this.owner != null) {
// Ensure that the Run is marked as completed (failed) if it isn't already so it won't show as running
Queue.Executable ex = owner.getExecutable();
if (ex instanceof Run) {
Result res = ((Run) ex).getResult();
setResult(res != null ? res : Result.FAILURE);
try {
if (this.owner != null) {
// Ensure that the Run is marked as completed (failed) if it isn't already so it won't show as
// running
Queue.Executable ex = owner.getExecutable();
if (ex instanceof Run) {
Result res = ((Run) ex).getResult();
setResult(res != null ? res : Result.FAILURE);
}
}
}

programPromise =
Futures.immediateFailedFuture(new IllegalStateException("Failed loading heads", failureReason));
LOGGER.log(Level.INFO, "Creating placeholder flownodes for execution: " + this);
if (this.owner != null) {
try {
owner.getListener()
.getLogger()
.println("Creating placeholder flownodes because failed loading originals.");
} catch (Exception ex) {
// It's okay to fail to log
programPromise =
Futures.immediateFailedFuture(new IllegalStateException("Failed loading heads", failureReason));
LOGGER.log(Level.INFO, "Creating placeholder flownodes for execution: " + this);
if (this.owner != null) {
try {
owner.getListener()
.getLogger()
.println("Creating placeholder flownodes because failed loading originals.");
} catch (Exception ex) {
// It's okay to fail to log
}
}
}

// Switch to fallback storage so we don't delete original node data
this.storageDir = (this.storageDir != null) ? this.storageDir + "-fallback" : "workflow-fallback";
this.storage = createStorage(); // Empty storage

// Clear out old start nodes and heads
this.startNodes = new Stack<>();
FlowHead head = new FlowHead(this);
this.heads = new TreeMap<>();
heads.put(head.getId(), head);
FlowStartNode start = new FlowStartNode(this, iotaStr());
head.newStartNode(start);

// Create end
FlowNode end = new FlowEndNode(
this,
iotaStr(),
(FlowStartNode) startNodes.pop(),
result,
getCurrentHeads().toArray(new FlowNode[0]));
end.addAction(new ErrorAction(failureReason));
head.setNewHead(end);
// Switch to fallback storage so we don't delete original node data
this.storageDir = (this.storageDir != null) ? this.storageDir + "-fallback" : "workflow-fallback";
this.storage = createStorage(); // Empty storage

// Clear out old start nodes and heads
this.startNodes = new Stack<>();
FlowHead head = new FlowHead(this);
this.heads = new TreeMap<>();
heads.put(head.getId(), head);
FlowStartNode start = new FlowStartNode(this, iotaStr());
head.newStartNode(start);

// Create end
FlowNode end = new FlowEndNode(
this,
iotaStr(),
(FlowStartNode) startNodes.pop(),
result,
getCurrentHeads().toArray(new FlowNode[0]));
end.addAction(new ErrorAction(failureReason));
head.setNewHead(end);
} finally {
this.done = true;
}
}
saveOwner();
}
Expand Down Expand Up @@ -936,6 +939,11 @@ public void onSuccess(Unmarshaller u) {
PROGRAM_STATE_SERIALIZATION.set(CpsFlowExecution.this);
try {
CpsThreadGroup g = (CpsThreadGroup) u.readObject();
if (!g.getThreads().iterator().hasNext() && !isComplete()) {
LOGGER.log(Level.WARNING, "Loaded program for {0} contains no CPS threads", owner);
loadProgramFailed(new AbortException("Loaded program contains no CPS threads"), result);
return;
}
result.set(g);
pausedWhenLoaded = g.isPaused();
g.pause(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,22 @@
import com.google.common.util.concurrent.ListenableFuture;
import hudson.model.Queue;
import hudson.model.Result;
import hudson.model.Run;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.workflow.cps.nodes.StepEndNode;
import org.jenkinsci.plugins.workflow.flow.FlowDurabilityHint;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionList;
import org.jenkinsci.plugins.workflow.graph.FlowEndNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.graph.FlowStartNode;
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.job.properties.DurabilityHintJobProperty;
Expand Down Expand Up @@ -106,6 +111,12 @@ static void assertResultMatchExecutionAndRun(WorkflowRun run, Result[] execution
Assert.assertEquals(executionAndBuildResult[1], run.getResult());
}

private static void setField(Object target, Class<?> declaringClass, String name, Object value) throws Exception {
Field field = declaringClass.getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
}

/** Create and run a basic build before we mangle its persisted contents. Stores job number to jobIdNumber index 0. */
private static WorkflowRun runBasicBuild(JenkinsRule j, String jobName, int[] jobIdNumber, FlowDurabilityHint hint)
throws Exception {
Expand Down Expand Up @@ -231,6 +242,47 @@ public void completedButWrongDoneStatus() throws Exception {
});
}

@Test
public void inProgressWithStepEndHeadButNoCpsThreads() throws Exception {
final int[] build = new int[1];
story.thenWithHardShutdown(j -> {
WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, DEFAULT_JOBNAME);
job.setDefinition(new CpsFlowDefinition("stage('x') { echo 'doSomething' }", true));
WorkflowRun run = j.buildAndAssertSuccess(job);
build[0] = run.getNumber();
assertCompletedCleanly(run, true);
CpsFlowExecution cpsExec = (CpsFlowExecution) run.getExecution();
FlowNode stepEnd = new DepthFirstScanner().findFirstMatch(cpsExec, n -> n instanceof StepEndNode);
Assert.assertNotNull("expected completed build to contain a StepEndNode", stepEnd);

FlowHead head = cpsExec.getFirstHead();
Assert.assertNotNull("expected a persisted FlowHead", head);
head.head = stepEnd;
cpsExec.done = false;
CpsThreadGroup emptyProgram = new CpsThreadGroup(cpsExec);
try {
emptyProgram
.runner
.submit(() -> {
emptyProgram.saveProgram(cpsExec.getProgramDataFile());
return null;
})
.get();
} finally {
emptyProgram.shutdown();
}
setField(run, WorkflowRun.class, "completed", false);
setField(run, Run.class, "result", null);
run.save();
});
story.then(j -> {
WorkflowJob r = j.jenkins.getItemByFullName(DEFAULT_JOBNAME, WorkflowJob.class);
WorkflowRun run = r.getBuildByNumber(build[0]);
assertCompletedCleanly(run, false);
Assert.assertEquals(Result.FAILURE, run.getResult());
});
}

@Test
public void inProgressNormal() throws Exception {
final int[] build = new int[1];
Expand Down
Loading