use of org.jenkinsci.plugins.workflow.steps.StepExecution in project workflow-cps-plugin by jenkinsci.
the class DSL method invokeStep.
/**
* When {@link #invokeMethod(String, Object)} is calling a {@link StepDescriptor}
*/
protected Object invokeStep(StepDescriptor d, Object args) {
final NamedArgsAndClosure ps = parseArgs(args, d);
CpsThread thread = CpsThread.current();
FlowNode an;
// TODO: generalize the notion of Step taking over the FlowNode creation.
boolean hack = d instanceof ParallelStep.DescriptorImpl || d instanceof LoadStep.DescriptorImpl;
if (ps.body == null && !hack) {
an = new StepAtomNode(exec, d, thread.head.get());
// TODO: use CPS call stack to obtain the current call site source location. See JENKINS-23013
thread.head.setNewHead(an);
} else {
an = new StepStartNode(exec, d, thread.head.get());
thread.head.setNewHead(an);
}
final CpsStepContext context = new CpsStepContext(d, thread, handle, an, ps.body);
Step s;
boolean sync;
ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
try {
d.checkContextAvailability(context);
Thread.currentThread().setContextClassLoader(CpsVmExecutorService.ORIGINAL_CONTEXT_CLASS_LOADER.get());
s = d.newInstance(ps.namedArgs);
try {
// No point storing empty arguments, and ParallelStep is a special case where we can't store its closure arguments
if (ps.namedArgs != null && !(ps.namedArgs.isEmpty()) && isKeepStepArguments() && !(s instanceof ParallelStep)) {
// Get the environment variables to find ones that might be credentials bindings
Computer comp = context.get(Computer.class);
EnvVars allEnv = new EnvVars(context.get(EnvVars.class));
if (comp != null && allEnv != null) {
allEnv.entrySet().removeAll(comp.getEnvironment().entrySet());
}
an.addAction(new ArgumentsActionImpl(ps.namedArgs, allEnv));
}
} catch (Exception e) {
// Avoid breaking execution because we can't store some sort of crazy Step argument
LOGGER.log(Level.WARNING, "Error storing the arguments for step: " + d.getFunctionName(), e);
}
// Persist the node - block start and end nodes do their own persistence.
CpsFlowExecution.maybeAutoPersistNode(an);
StepExecution e = s.start(context);
thread.setStep(e);
sync = e.start();
} catch (Exception e) {
if (e instanceof MissingContextVariableException)
reportMissingContextVariableException(context, (MissingContextVariableException) e);
context.onFailure(e);
s = null;
sync = true;
} finally {
Thread.currentThread().setContextClassLoader(originalLoader);
}
if (sync) {
assert context.bodyInvokers.isEmpty() : "If a step claims synchronous completion, it shouldn't invoke body";
if (context.getOutcome() == null) {
context.onFailure(new AssertionError("Step " + s + " claimed to have ended synchronously, but didn't set the result via StepContext.onSuccess/onFailure"));
}
thread.setStep(null);
// we just move on accordingly
if (an instanceof StepStartNode) {
// no body invoked, so EndNode follows StartNode immediately.
thread.head.setNewHead(new StepEndNode(exec, (StepStartNode) an, an));
}
thread.head.markIfFail(context.getOutcome());
return context.replay();
} else {
// if it's in progress, suspend it until we get invoked later.
// when it resumes, the CPS caller behaves as if this method returned with the resume value
Continuable.suspend(new ThreadTaskImpl(context));
// so the execution will never reach here.
throw new AssertionError();
}
}
use of org.jenkinsci.plugins.workflow.steps.StepExecution in project workflow-cps-plugin by jenkinsci.
the class CpsBodyExecution method getCurrentExecutions.
@Override
public Collection<StepExecution> getCurrentExecutions() {
CpsThread t;
synchronized (this) {
t = thread;
if (t == null) {
return Collections.emptySet();
}
}
final SettableFuture<Collection<StepExecution>> result = SettableFuture.create();
t.getExecution().runInCpsVmThread(new FutureCallback<CpsThreadGroup>() {
@Override
public void onSuccess(CpsThreadGroup g) {
try {
List<StepExecution> executions = new ArrayList<>();
// cf. trick in CpsFlowExecution.getCurrentExecutions(true)
Map<FlowHead, CpsThread> m = new LinkedHashMap<>();
for (CpsThread t : g.threads.values()) {
m.put(t.head, t);
}
for (CpsThread t : m.values()) {
// TODO seems cumbersome to have to go through the flow graph to find out whether a head is a descendant of ours, yet FlowHead does not seem to retain a parent field
LinearBlockHoppingScanner scanner = new LinearBlockHoppingScanner();
scanner.setup(t.head.get());
for (FlowNode node : scanner) {
if (node.getId().equals(startNodeId)) {
// this head is inside this body execution
StepExecution execution = t.getStep();
if (execution != null) {
executions.add(execution);
}
break;
}
}
}
result.set(executions);
} catch (Exception x) {
result.setException(x);
}
}
@Override
public void onFailure(Throwable t) {
result.setException(t);
}
});
try {
return result.get(1, TimeUnit.MINUTES);
} catch (ExecutionException | InterruptedException | TimeoutException x) {
// TODO access to CpsThreadGroup.threads must be restricted to the CPS VM thread, but the API signature does not allow us to return a ListenableFuture or throw checked exceptions
throw new RuntimeException(x);
}
}
use of org.jenkinsci.plugins.workflow.steps.StepExecution in project selenium_java by sergueik.
the class Audit2DbStepTests method testStartWithStepContextReturnsAudit2DbExecution.
@Test
public void testStartWithStepContextReturnsAudit2DbExecution() throws Exception {
StepContext context = Mockito.mock(StepContext.class);
Audit2DbStep step = new Audit2DbStep();
StepExecution execution = step.start(context);
assertNotNull("Null StepExecution returned.", execution);
assertTrue("Expected Audit2DbExecution", Audit2DbExecution.class.isInstance(execution));
assertThat("StepContext should be injected into execution.", execution, hasProperty("context", sameInstance(context)));
assertThat("Step should be injected into execution.", execution, hasProperty("step", sameInstance(step)));
}
use of org.jenkinsci.plugins.workflow.steps.StepExecution in project workflow-cps-plugin by jenkinsci.
the class CpsStepContext method scheduleNextRun.
/**
* When this step context has completed execution (successful or otherwise), plan the next action.
*/
private void scheduleNextRun() {
if (syncMode) {
// plan the next action.
return;
}
try {
final FlowNode n = getNode();
final CpsFlowExecution flow = getFlowExecution();
final List<FlowNode> parents = new ArrayList<FlowNode>();
for (int head : bodyHeads) {
FlowHead flowHead = flow.getFlowHead(head);
if (flowHead != null) {
parents.add(flowHead.get());
} else {
LOGGER.log(Level.WARNING, "Could not find flow head #{0}", head);
}
}
flow.runInCpsVmThread(new FutureCallback<CpsThreadGroup>() {
@CpsVmThreadOnly
@Override
public void onSuccess(CpsThreadGroup g) {
g.unexport(body);
body = null;
CpsThread thread = getThread(g);
if (thread != null) {
CpsThread nit = thread.getNextInner();
if (nit != null) {
// can't mark this done until the inner thread is done.
// defer the processing until the inner thread is done
nit.addCompletionHandler(new ScheduleNextRun());
if (getOutcome().isFailure()) {
// if the step with a currently running body reported a failure,
// make some effort to try to interrupt the running body
StepExecution s = nit.getStep();
if (s != null) {
// TODO: ideally this needs to work like interrupt, in that
// if s==null the next StepExecution gets interrupted when it happen
FlowInterruptedException cause = new FlowInterruptedException(Result.FAILURE, new BodyFailed());
cause.initCause(getOutcome().getAbnormal());
try {
// TODO JENKINS-26148/JENKINS-34637 this is probably wrong: should interrupt the innermost execution
// (the “next” one could be block-scoped, and we would want to interrupt all parallel heads)
s.stop(cause);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Failed to stop the body execution in response to the failure of the parent");
}
}
}
return;
}
if (n instanceof StepStartNode) {
// if there's no body to invoke, we want the current thread to be the sole head
if (parents.isEmpty())
parents.add(thread.head.get());
// clear all the subsumed heads that are joining. thread that owns parents.get(0) lives on
for (int i = 1; i < parents.size(); i++) g.getExecution().subsumeHead(parents.get(i));
StepEndNode en = new StepEndNode(flow, (StepStartNode) n, parents);
thread.head.setNewHead(en);
CpsFlowExecution.maybeAutoPersistNode(en);
}
thread.head.markIfFail(getOutcome());
thread.setStep(null);
thread.resume(getOutcome());
}
}
/**
* Program state failed to load.
*/
@Override
public void onFailure(Throwable t) {
}
});
} catch (IOException x) {
LOGGER.log(Level.FINE, null, x);
}
}
use of org.jenkinsci.plugins.workflow.steps.StepExecution in project workflow-cps-plugin by jenkinsci.
the class CpsBodyExecutionTest method currentExecutions.
@Issue("JENKINS-34637")
@Test
public void currentExecutions() throws Exception {
WorkflowJob p = jenkins.createProject(WorkflowJob.class);
p.setDefinition(new CpsFlowDefinition("parallel main: {retainsBody {parallel a: {retainsBody {semaphore 'a'}}, b: {retainsBody {semaphore 'b'}}}}, aside: {semaphore 'c'}", true));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("a/1", b);
SemaphoreStep.waitForStart("b/1", b);
SemaphoreStep.waitForStart("c/1", b);
final RetainsBodyStep.Execution[] execs = new RetainsBodyStep.Execution[3];
StepExecution.applyAll(RetainsBodyStep.Execution.class, new Function<RetainsBodyStep.Execution, Void>() {
@Override
public Void apply(RetainsBodyStep.Execution exec) {
execs[exec.count] = exec;
return null;
}
}).get();
assertNotNull(execs[0]);
assertNotNull(execs[1]);
assertNotNull(execs[2]);
final Set<SemaphoreStep.Execution> semaphores = new HashSet<>();
StepExecution.applyAll(SemaphoreStep.Execution.class, new Function<SemaphoreStep.Execution, Void>() {
@Override
public Void apply(SemaphoreStep.Execution exec) {
if (exec.getStatus().matches("waiting on [ab]/1")) {
semaphores.add(exec);
}
return null;
}
}).get();
assertThat(semaphores, Matchers.<SemaphoreStep.Execution>iterableWithSize(2));
// A or B, does not matter
Collection<StepExecution> currentExecutions1 = execs[1].body.getCurrentExecutions();
assertThat(/* irritatingly, iterableWithSize does not show the collection in its mismatch message */
currentExecutions1.toString(), currentExecutions1, Matchers.<StepExecution>iterableWithSize(1));
Collection<StepExecution> currentExecutions2 = execs[2].body.getCurrentExecutions();
assertThat(currentExecutions2, Matchers.<StepExecution>iterableWithSize(1));
assertEquals(semaphores, Sets.union(Sets.newLinkedHashSet(currentExecutions1), Sets.newLinkedHashSet(currentExecutions2)));
// the top-level one
assertEquals(semaphores, Sets.newLinkedHashSet(execs[0].body.getCurrentExecutions()));
execs[0].body.cancel();
SemaphoreStep.success("c/1", null);
jenkins.assertBuildStatus(Result.ABORTED, jenkins.waitForCompletion(b));
}
Aggregations