diff --git a/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/BackgroundDurableTaskJoinStep.java b/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/BackgroundDurableTaskJoinStep.java
new file mode 100644
index 00000000..5ed864f4
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/BackgroundDurableTaskJoinStep.java
@@ -0,0 +1,80 @@
+package org.jenkinsci.plugins.workflow.steps.durable_task;
+
+import hudson.Extension;
+import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
+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.kohsuke.stapler.DataBoundConstructor;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Waits for the task forked in {@code sh(background:true, ...)} to complete
+ *
+ * TODO: native timeout support. in the mean time, combine with the timeout step
+ *
+ * @author Kohsuke Kawaguchi
+ * @see BackgroundTask
+ */
+public class BackgroundDurableTaskJoinStep extends Step {
+ private final BackgroundTask t;
+
+ @DataBoundConstructor
+ public BackgroundDurableTaskJoinStep(BackgroundTask t) {
+ this.t = t;
+ }
+
+ @Override
+ public StepExecution start(StepContext context) throws Exception {
+ return new Execution(context, t.getExecution());
+ }
+
+ static final class Execution extends AbstractStepExecutionImpl {
+ private DurableTaskStep.Execution task;
+ public Execution(StepContext context, DurableTaskStep.Execution t) {
+ super(context);
+ this.task = t;
+ }
+
+ @Override
+ public boolean start() throws Exception {
+ task.addCompletionHandler(getContext());
+ return false;
+ }
+
+ @Override
+ public void stop(@Nonnull Throwable cause) throws Exception {
+ // interrupting this step shouldn't cause the process to die
+ // DO NOT: task.stop(cause);
+ }
+ }
+
+ @Extension
+ public static class DescriptorImpl extends StepDescriptor {
+ @Override
+ public Set extends Class>> getRequiredContext() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public String getFunctionName() {
+ return "backgroundDurableTaskJoin";
+ }
+
+ /**
+ * Marking as advanced for now since this step is
+ * meant to be used in {@link BackgroundTask#join()}
+ *
+ * If we are to open this up to the general audience
+ * it should get a better name
+ */
+ @Override
+ public boolean isAdvanced() {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/BackgroundTask.java b/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/BackgroundTask.java
new file mode 100644
index 00000000..7c2d618f
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/BackgroundTask.java
@@ -0,0 +1,51 @@
+package org.jenkinsci.plugins.workflow.steps.durable_task;
+
+import hudson.AbortException;
+
+import java.io.Serializable;
+
+/**
+ * Represents an object that tracks background task
+ * forked off by {@code sh(background:true)}
+ *
+ *
+ * This object is serialized along with the pipeline program.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class BackgroundTask implements Serializable {
+ private final DurableTaskStep.Execution execution;
+
+ /*package*/ BackgroundTask(DurableTaskStep.Execution execution) {
+ this.execution = execution;
+ }
+
+ /*package*/ DurableTaskStep.Execution getExecution() {
+ return execution;
+ }
+
+ /**
+ * Suspends until the process is done.
+ *
+ * @see BackgroundDurableTaskJoinStep
+ */
+ public int join() {
+ // currently cannot be implemented as an instance method
+ // because this module doesn't depend on workflow-cps.
+ // use BackgroundDurableTaskJoinStep
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Immediately kills the task.
+ */
+ public void kill() throws Exception {
+ execution.stop(new AbortException());
+ }
+
+ /*
+ def proc = sh(background:true, script:'android something')
+
+ proc.join()
+ */
+}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/DurableTaskStep.java b/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/DurableTaskStep.java
index 0e0e7af1..fd9b1a10 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/DurableTaskStep.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/steps/durable_task/DurableTaskStep.java
@@ -25,6 +25,7 @@
package org.jenkinsci.plugins.workflow.steps.durable_task;
import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.FutureCallback;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.AbortException;
import hudson.EnvVars;
@@ -72,6 +73,7 @@ public abstract class DurableTaskStep extends Step {
private boolean returnStdout;
private String encoding = DurableTaskStepDescriptor.defaultEncoding;
private boolean returnStatus;
+ private boolean background;
protected abstract DurableTask task();
@@ -99,6 +101,14 @@ public boolean isReturnStatus() {
this.returnStatus = returnStatus;
}
+ public boolean isBackground() {
+ return background;
+ }
+
+ @DataBoundSetter public void setBackground(boolean background) {
+ this.background = background;
+ }
+
@Override public StepExecution start(StepContext context) throws Exception {
return new Execution(context, this);
}
@@ -119,9 +129,9 @@ public FormValidation doCheckEncoding(@QueryParameter boolean returnStdout, @Que
return FormValidation.ok();
}
- public FormValidation doCheckReturnStatus(@QueryParameter boolean returnStdout, @QueryParameter boolean returnStatus) {
- if (returnStdout && returnStatus) {
- return FormValidation.error("You may not select both returnStdout and returnStatus.");
+ public FormValidation doCheckReturnStatus(@QueryParameter boolean returnStdout, @QueryParameter boolean returnStatus, @QueryParameter boolean background) {
+ if ((returnStdout?1:0)+(returnStatus?1:0)+(background?1:0)>1) {
+ return FormValidation.error("You can only select one of returnStdout, returnStatus, or background.");
}
return FormValidation.ok();
}
@@ -152,6 +162,14 @@ static final class Execution extends AbstractStepExecutionImpl implements Runnab
private boolean returnStdout; // serialized default is false
private String encoding; // serialized default is irrelevant
private boolean returnStatus; // serialized default is false
+ private boolean background; // serialized default is false
+
+ /**
+ * In this class, the completion of the async task should be sent here,
+ * instead of {@code getContext()} like normal step is, in order for
+ * us to support background task execution. See {@link DurableTaskStep#background}
+ */
+ private FutureCallbackProxy