Skip to content

Commit

Permalink
Merge pull request #74 from jglick/timeout-JENKINS-26521+JEP-210
Browse files Browse the repository at this point in the history
[JENKINS-54078] JEP-210 compatibility
  • Loading branch information
dwnusbaum authored Oct 26, 2018
2 parents 41c62f3 + 066e961 commit aabd36f
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 15 deletions.
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>
<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>
<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" +
" }\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

0 comments on commit aabd36f

Please sign in to comment.