Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -154,7 +154,7 @@
cob.print(listener);
}
}
}, 15, TimeUnit.SECONDS);
}, Main.isUnitTest ? 5 : 15, TimeUnit.SECONDS);

Check warning on line 157 in src/main/java/org/jenkinsci/plugins/workflow/support/steps/ExecutorStepExecution.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 157 is only partially covered, one branch is missing
return false;
}

Expand Down Expand Up @@ -508,8 +508,8 @@

private Object readResolve() {
RunningTasks.add(context);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(FINE, null, new Exception("deserializing previously scheduled " + this));
if (LOGGER.isLoggable(Level.FINER)) {

Check warning on line 511 in src/main/java/org/jenkinsci/plugins/workflow/support/steps/ExecutorStepExecution.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 511 is only partially covered, one branch is missing
LOGGER.log(FINER, null, new Exception("deserializing previously scheduled " + this));

Check warning on line 512 in src/main/java/org/jenkinsci/plugins/workflow/support/steps/ExecutorStepExecution.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 512 is not covered by tests
}
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,31 @@
import hudson.model.Label;
import hudson.model.Queue;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.slaves.DumbSlave;
import hudson.slaves.RetentionStrategy;

import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;

import jenkins.model.InterruptedBuildAction;

import org.jenkinci.plugins.mock_slave.MockCloud;
import org.jenkinsci.plugins.durabletask.executors.ContinuedTask;
import org.jenkinsci.plugins.durabletask.executors.OnceRetentionStrategy;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionList;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import org.junit.ClassRule;
import org.junit.Rule;
Expand All @@ -60,6 +71,8 @@
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsSessionRule;
import org.jvnet.hudson.test.LoggerRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.DataBoundConstructor;

public class ExecutorStepDynamicContextTest {

Expand Down Expand Up @@ -249,4 +262,64 @@ private void commonSetup() {
assertThat(iba.getCauses(), contains(isA(ExecutorStepExecution.RemovedNodeCause.class)));
});
}

@Test public void tardyResume() throws Throwable {
commonSetup();
logging.record(ContinuedTask.class, Level.FINER);
sessions.then(j -> {
j.createSlave("remote", "contended", null);
var prompt = j.createProject(WorkflowJob.class, "prompt");
prompt.setDefinition(new CpsFlowDefinition("node('contended') {semaphore 'prompt'}", true));
var tardy = j.createProject(WorkflowJob.class, "tardy");
tardy.setDefinition(new CpsFlowDefinition("slowToResume {node('contended') {semaphore 'tardy'}}", true));
SemaphoreStep.waitForStart("tardy/1", tardy.scheduleBuild2(0).waitForStart());
j.waitForMessage("Still waiting to schedule task", prompt.scheduleBuild2(0).waitForStart());
});
sessions.then(j -> {
var promptB = j.jenkins.getItemByFullName("prompt", WorkflowJob.class).getBuildByNumber(1);
j.waitForMessage("Ready to run", promptB);
var tardyB = j.jenkins.getItemByFullName("tardy", WorkflowJob.class).getBuildByNumber(1);
j.waitForMessage("Ready to run", tardyB);
SemaphoreStep.success("tardy/1", null);
j.assertBuildStatusSuccess(j.waitForCompletion(tardyB));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

java.lang.AssertionError: 
unexpected build status; build log was:
------
Started
[Pipeline] Start of Pipeline
[Pipeline] slowToResume
[Pipeline] {
[Pipeline] node
Running on remote in …/agent-work-dirs/remote/workspace/tardy
[Pipeline] {
[Pipeline] semaphore
Resuming build at Mon Aug 18 19:22:29 EDT 2025 after Jenkins restart
Will resume outer step…
…resumed.
Waiting for reconnection of remote before proceeding with build
remote has been removed for 15 sec; assuming it is not coming back, and terminating node step
Ready to run at Mon Aug 18 19:22:47 EDT 2025
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // slowToResume
[Pipeline] End of Pipeline
Timeout waiting for agent to come back
org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: 03aac892-2c78-4c34-b72f-f4fc12d3c117
Finished: ABORTED

------

Expected: is <SUCCESS>
     but: was <ABORTED>

SemaphoreStep.waitForStart("prompt/1", promptB);
SemaphoreStep.success("prompt/1", null);
j.assertBuildStatusSuccess(j.waitForCompletion(promptB));
});
}
public static final class SlowToResumeStep extends Step {
@DataBoundConstructor public SlowToResumeStep() {}
@Override public StepExecution start(StepContext context) throws Exception {
return new Execution(context);
}
private static final class Execution extends StepExecution {
Execution(StepContext context) {
super(context);
}
@Override public boolean start() throws Exception {
getContext().newBodyInvoker().withCallback(BodyExecutionCallback.wrap(getContext())).start();
return false;
}
@Override public void onResume() {
try {
getContext().get(TaskListener.class).getLogger().println("Will resume outer step…");
Thread.sleep(3_000);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passes without this delay.

getContext().get(TaskListener.class).getLogger().println("…resumed.");
} catch (Exception x) {
throw new RuntimeException(x);
}
}
}
@TestExtension("tardyResume") public static class DescriptorImpl extends StepDescriptor {
@Override public String getFunctionName() {
return "slowToResume";
}
@Override public boolean takesImplicitBlockArgument() {
return true;
}
@Override public Set<? extends Class<?>> getRequiredContext() {
return Set.of(TaskListener.class);
}
}
}
}