/** * Propagates the failure to the workflow by passing an exception */ @CpsVmThreadOnly private void propagateErrorToWorkflow(Throwable t) { // it's not obvious which thread to blame, so as a heuristics, pick up the last one, // as that's the ony more likely to have caused the problem. // TODO: when we start tracking which thread is just waiting for the body, then // that information would help. or maybe we should just remember the thread that has run the last time Map.Entry<Integer,CpsThread> lastEntry = threads.lastEntry(); if (lastEntry != null) { lastEntry.getValue().resume(new Outcome(null,t)); } else { LOGGER.log(Level.WARNING, "encountered error but could not pass it to the flow", t); } }
@Override public void run() { CpsThread t = g.addThread(new Continuable(s,createInitialEnv()),h,null); t.resume(new Outcome(null, null)); f.set(g); }
@Override public void onSuccess(CpsThreadGroup g) { CpsThread t = g.addThread( new Continuable(new ThrowBlock(new ConstantBlock( problem instanceof AbortException ? problem : new IOException("Failed to load build state", problem)))), head_, null ); t.resume(new Outcome(null,null)); } @Override public void onFailure(Throwable t) {
/** * Stops the execution of this thread. If it's paused to wait for the completion of {@link StepExecution}, * call {@link StepExecution#stop(Throwable)} to give it a chance to clean up. * * <p> * If the execution is not inside a step (meaning it's paused in a safe point), then have the CPS thread * throw a given {@link Throwable} to break asap. */ @CpsVmThreadOnly public void stop(Throwable t) { StepExecution s = getStep(); // this is the part that should run in CpsVmThread if (s == null) { // if it's not running inside a StepExecution, we need to set an interrupt flag // and interrupt at an earliest convenience Outcome o = new Outcome(null, t); if (resumeValue==null) { resume(o); } else { // this thread was already resumed, so just overwrite the value with a Throwable resumeValue = o; } return; } try (Timeout timeout = Timeout.limit(30, TimeUnit.SECONDS)) { s.stop(t); } catch (Exception e) { t.addSuppressed(e); s.getContext().onFailure(t); } }
thread.resume(getOutcome());
t.resume(new Outcome(null, stopped)); assert this.thread==null; this.thread = t;
/** * The code getting evaluated must also get CPS transformation. */ @Ignore("TODO usually future == null, perhaps because CpsThread.resume is intended to be @CpsVmThreadOnly (so assumes that the promise is just set is not cleared by runNextChunk) yet we are calling it from the test thread; extremely dubious test design, should probably be using SemaphoreStep to be more realistic") @Test public void evaluateShallBeCpsTransformed() throws Exception { CpsFlowDefinition flow = new CpsFlowDefinition("evaluate('1+com.cloudbees.groovy.cps.Continuable.suspend(2+3)')", false); createExecution(flow); exec.start(); exec.waitForSuspension(); // TODO: can't we assert that the suspend() ended with value 5? // this should have paused at suspend, so we are going to resume it by having it return a value we control assertFalse(dumpError(), exec.isComplete()); ListenableFuture<CpsThreadGroup> pp = exec.programPromise; assertNotNull(pp); Future<Object> future = pp.get().getThread(0).resume(new Outcome(7,null)); assertNotNull(future); assertEquals(8, future.get()); exec.waitForSuspension(); assertTrue(dumpError(), exec.isComplete()); assertEquals(dumpError(), Result.SUCCESS, exec.getResult()); }