Skip to content
Merged
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
47 changes: 39 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>3.19</version>
<version>3.25</version>
Copy link
Member Author

Choose a reason for hiding this comment

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

Various changes to pick up JEP-210 binaries.

<relativePath />
</parent>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
Expand Down Expand Up @@ -67,10 +67,12 @@
<jenkins.version>2.121.1</jenkins.version>
<java.level>8</java.level>
<workflow-step-api-plugin.version>2.15</workflow-step-api-plugin.version>
<workflow-cps-plugin.version>2.32</workflow-cps-plugin.version>
<workflow-support-plugin.version>2.14</workflow-support-plugin.version>
<workflow-api-plugin.version>2.28</workflow-api-plugin.version>
<workflow-cps-plugin.version>2.58</workflow-cps-plugin.version>
<workflow-support-plugin.version>2.21</workflow-support-plugin.version>
<workflow-api-plugin.version>2.30</workflow-api-plugin.version>
<useBeta>true</useBeta>
<git-plugin.version>4.0.0-beta3</git-plugin.version>
<scm-api-plugin.version>2.2.6</scm-api-plugin.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -98,7 +100,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>structs</artifactId>
<version>1.14</version>
<version>1.17</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand All @@ -121,13 +123,13 @@
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<version>2.10</version>
<version>2.26</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-durable-task-step</artifactId>
<version>2.3</version>
<version>2.24</version>
Copy link
Member Author

Choose a reason for hiding this comment

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

FTR this was to pick up the fix of JENKINS-28822.

<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -182,7 +184,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>script-security</artifactId>
<version>1.28</version>
<version>1.46</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -204,5 +206,34 @@
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
<version>${git-plugin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<version>${scm-api-plugin.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
<version>${git-plugin.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<version>${scm-api-plugin.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,25 @@
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.CauseOfInterruption;
import jenkins.security.SlaveToMasterCallable;
import jenkins.util.Timer;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
Expand All @@ -37,7 +45,7 @@ public class TimeoutStepExecution extends AbstractStepExecutionImpl {
private BodyExecution body;
private transient ScheduledFuture<?> killer;

private long timeout = 0;
private final long timeout;
private long end = 0;

/** Used to track whether this is timing out on inactivity without needing to reference {@link #step}. */
Expand All @@ -46,10 +54,15 @@ public class TimeoutStepExecution extends AbstractStepExecutionImpl {
/** whether we are forcing the body to end */
private boolean forcible;

/** Token for {@link #activity} callbacks. */
private final String id;

TimeoutStepExecution(TimeoutStep step, StepContext context) {
super(context);
this.step = step;
this.activity = step.isActivity();
id = activity ? UUID.randomUUID().toString() : null;
timeout = step.getUnit().toMillis(step.getTime());
}

@Override
Expand All @@ -62,13 +75,12 @@ public boolean start() throws Exception {
bodyInvoker = bodyInvoker.withContext(
BodyInvoker.mergeConsoleLogFilters(
context.get(ConsoleLogFilter.class),
new ConsoleLogFilterImpl(new ResetCallbackImpl())
new ConsoleLogFilterImpl2(id, timeout)
)
);
}

body = bodyInvoker.start();
timeout = step.getUnit().toMillis(step.getTime());
resetTimer();
return false; // execution is asynchronous
}
Expand Down Expand Up @@ -230,17 +242,136 @@ public String getShortDescription() {
}
}

private static final class ResetTimer extends SlaveToMasterCallable<Void, RuntimeException> {

private static final long serialVersionUID = 1L;

private final @Nonnull String id;

ResetTimer(String id) {
this.id = id;
}

@Override public Void call() throws RuntimeException {
StepExecution.applyAll(TimeoutStepExecution.class, e -> {
if (id.equals(e.id)) {
e.resetTimer();
}
return null;
});
return null;
}

}

private static class ConsoleLogFilterImpl2 extends ConsoleLogFilter implements /* TODO Remotable */ Serializable {
private static final long serialVersionUID = 1L;

private final @Nonnull String id;
private final long timeout;
private transient @CheckForNull Channel channel;

ConsoleLogFilterImpl2(String id, long timeout) {
this.id = id;
this.timeout = timeout;
}

private Object readResolve() {
channel = Channel.current();
return this;
}

@Override
public OutputStream decorateLogger(@SuppressWarnings("rawtypes") Run build, final OutputStream logger)
throws IOException, InterruptedException {
// TODO if channel == null, we can safely ResetTimer.call synchronously from eol and skip the Tick
AtomicBoolean active = new AtomicBoolean();
OutputStream decorated = new LineTransformationOutputStream() {
@Override
protected void eol(byte[] b, int len) throws IOException {
logger.write(b, 0, len);
active.set(true);
}

@Override
public void flush() throws IOException {
super.flush();
logger.flush();
}

@Override
public void close() throws IOException {
super.close();
logger.close();
}
};
new Tick(active, new WeakReference<>(decorated), timeout, channel, id).schedule();
return decorated;
}
}

private static final class Tick implements Runnable {
private final AtomicBoolean active;
private final Reference<?> stream;
private final long timeout;
private final @CheckForNull Channel channel;
private final @Nonnull String id;
Tick(AtomicBoolean active, Reference<?> stream, long timeout, Channel channel, String id) {
this.active = active;
this.stream = stream;
this.timeout = timeout;
this.channel = channel;
this.id = id;
}
@Override
public void run() {
if (stream.get() == null) {
// Not only idle but gone—stop the timer.
return;
}
boolean currentlyActive = active.getAndSet(false);
if (currentlyActive) {
Callable<Void, RuntimeException> resetTimer = new ResetTimer(id);
if (channel != null) {
try {
channel.call(resetTimer);
} catch (Exception x) {
LOGGER.log(Level.WARNING, null, x);
}
} else {
resetTimer.call();
}
schedule();
} else {
// Idle at the moment, but check well before the timeout expires in case new output appears.
schedule(timeout / 10);
}
}
void schedule() {
schedule(timeout / 2); // less than the full timeout, to give some grace period, but in the same ballpark to avoid overhead
}
private void schedule(long delay) {
Timer.get().schedule(this, delay, TimeUnit.MILLISECONDS);
}
}

/** @deprecated only here for serial compatibility */
@Deprecated
public interface ResetCallback extends Serializable {
void logWritten();
}

/** @deprecated only here for serial compatibility */
@Deprecated
private class ResetCallbackImpl implements ResetCallback {
private static final long serialVersionUID = 1L;
@Override public void logWritten() {
resetTimer();
}
}

/** @deprecated only here for serial compatibility */
@Deprecated
private static class ConsoleLogFilterImpl extends ConsoleLogFilter implements /* TODO Remotable */ Serializable {
private static final long serialVersionUID = 1L;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,18 @@ public class EnvStepTest {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"parallel a: {\n" +
" node {withEnv(['TOOL=aloc']) {semaphore 'a'; isUnix() ? sh('echo TOOL=$TOOL') : bat('echo TOOL=%TOOL%')}}\n" +
" node {withEnv(['TOOL=aloc']) {semaphore 'a'; isUnix() ? sh('echo a TOOL=$TOOL') : bat('echo a TOOL=%TOOL%')}}\n" +
"}, b: {\n" +
" node {withEnv(['TOOL=bloc']) {semaphore 'b'; isUnix() ? sh('echo TOOL=$TOOL') : bat('echo TOOL=%TOOL%')}}\n" +
" node {withEnv(['TOOL=bloc']) {semaphore 'b'; isUnix() ? sh('echo b TOOL=$TOOL') : bat('echo b TOOL=%TOOL%')}}\n" +
"}", true));
WorkflowRun b = p.scheduleBuild2(0).getStartCondition().get();
SemaphoreStep.waitForStart("a/1", b);
SemaphoreStep.waitForStart("b/1", b);
SemaphoreStep.success("a/1", null);
SemaphoreStep.success("b/1", null);
story.j.assertBuildStatusSuccess(story.j.waitForCompletion(b));
story.j.assertLogContains("[a] TOOL=aloc", b);
story.j.assertLogContains("[b] TOOL=bloc", b);
story.j.assertLogContains("a TOOL=aloc", b);
story.j.assertLogContains("b TOOL=bloc", b);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package org.jenkinsci.plugins.workflow.steps;

import hudson.Functions;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
Expand All @@ -33,6 +34,7 @@
import java.util.concurrent.TimeUnit;
import jenkins.model.CauseOfInterruption;
import jenkins.model.InterruptedBuildAction;
import jenkins.plugins.git.GitSampleRepoRule;
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode;
Expand All @@ -43,6 +45,7 @@
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import org.junit.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.*;
import org.junit.runners.model.Statement;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.Issue;
Expand All @@ -57,6 +60,8 @@ public class TimeoutStepTest extends Assert {

@Rule public RestartableJenkinsRule story = new RestartableJenkinsRule();

@Rule public GitSampleRepoRule git = new GitSampleRepoRule();

@Test public void configRoundTrip() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
Expand Down Expand Up @@ -239,6 +244,42 @@ public void evaluate() throws Throwable {
});
}

@Test
public void activityRemote() {
assumeFalse(Functions.isWindows()); // TODO create analogue using bat
story.then(r -> {
r.createSlave();
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("" +
"node('!master') {\n" +
" timeout(time:5, unit:'SECONDS', activity: true) {\n" +
" sh 'set +x; echo NotHere; sleep 3; echo NotHereYet; sleep 3; echo JustHere; sleep 10; echo ShouldNot'\n" +
Copy link
Member Author

Choose a reason for hiding this comment

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

Cannot use echo steps as in the other tests, because we need to demonstrate serialization of the ConsoleLogFilter impl, which only happens if a nested step actually remotes its TaskListener.

" }\n" +
"}\n", true));
WorkflowRun b = r.assertBuildStatus(Result.ABORTED, p.scheduleBuild2(0));
story.j.assertLogContains("JustHere", b);
story.j.assertLogNotContains("ShouldNot", b);
});
}

@Issue("JENKINS-54078")
@Test public void activityGit() {
story.then(r -> {
r.createSlave();
git.init();
git.write("file", "content");
git.git("commit", "--all", "--message=init");
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("" +
"node('!master') {\n" +
" timeout(time: 5, unit: 'MINUTES', activity: true) {\n" +
" git($/" + git + "/$)\n" +
" }\n" +
"}\n", true));
r.buildAndAssertSuccess(p);
});
}

@Issue("JENKINS-26163")
@Test
public void restarted() throws Exception {
Expand Down