/** Save the owner that holds this execution. * Key note: to avoid deadlocks we need to ensure that we don't hold a lock on this CpsFlowExecution when running saveOwner * or pre-emptively lock the run before locking the execution and saving. */ void saveOwner() { try { if (this.owner != null && this.owner.getExecutable() instanceof Saveable) { // Null-check covers some anomalous cases we've seen Saveable saveable = (Saveable)(this.owner.getExecutable()); persistedClean = true; if (storage != null && storage.delegate != null) { // Defensively flush FlowNodes to storage try { storage.flush(); } catch (Exception ex) { LOGGER.log(Level.WARNING, "Error persisting FlowNodes for execution "+owner, ex); persistedClean = false; } } saveable.save(); } } catch (IOException ex) { LOGGER.log(Level.WARNING, "Error persisting Run "+owner, ex); persistedClean = false; } }
public CpsFlowExecution(@Nonnull String script, boolean sandbox, @Nonnull FlowExecutionOwner owner, @CheckForNull FlowDurabilityHint durabilityHint) throws IOException { this.owner = owner; this.script = script; this.sandbox = sandbox; this.durabilityHint = durabilityHint; Authentication auth = Jenkins.getAuthentication(); this.user = auth.equals(ACL.SYSTEM) ? null : auth.getName(); this.storage = createStorage(); this.storage.setAvoidAtomicWrite(!this.getDurabilityHint().isAtomicWrite()); }
@SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification="Storage does not actually NEED to be synchronized but the rest does.") protected synchronized void initializeStorage() throws IOException { storage = createStorage(); heads = new TreeMap<Integer,FlowHead>(); for (Map.Entry<Integer,String> entry : headsSerial.entrySet()) { FlowHead h = new FlowHead(this, entry.getKey()); FlowNode n = storage.getNode(entry.getValue()); if (n != null) { h.setForDeserialize(storage.getNode(entry.getValue())); heads.put(h.getId(), h); } else { throw new IOException("Tried to load head FlowNodes for execution "+this.owner+" but FlowNode was not found in storage for head id:FlowNodeId "+entry.getKey()+":"+entry.getValue()); } } headsSerial = null; startNodes = new Stack<BlockStartNode>(); for (String id : startNodesSerial) { FlowNode node = storage.getNode(id); if (node != null) { startNodes.add((BlockStartNode) storage.getNode(id)); } else { // TODO if possible, consider trying to close out unterminated blocks using heads, to keep existing graph history throw new IOException( "Tried to load startNode FlowNodes for execution "+this.owner+" but FlowNode was not found in storage for FlowNode Id "+id); } } startNodesSerial = null; }
/** Verifies all the universal post-build cleanup was done, regardless of pass/fail state. */ static void verifyCompletedCleanly(Jenkins j, WorkflowRun run) throws Exception { // Assert that we have the appropriate flow graph entries FlowExecution exec = run.getExecution(); List<FlowNode> heads = exec.getCurrentHeads(); Assert.assertEquals(1, heads.size()); verifyNoTasksRunning(j); Assert.assertEquals(0, exec.getCurrentExecutions(false).get().size()); if (exec instanceof CpsFlowExecution) { CpsFlowExecution cpsFlow = (CpsFlowExecution)exec; assert cpsFlow.getStorage() != null; Assert.assertFalse("Should always be able to retrieve script", StringUtils.isEmpty(cpsFlow.getScript())); Assert.assertNull("We should have no Groovy shell left or that's a memory leak", cpsFlow.getShell()); Assert.assertNull("We should have no Groovy shell left or that's a memory leak", cpsFlow.getTrustedShell()); Assert.assertTrue(cpsFlow.done); assert cpsFlow.isComplete(); assert cpsFlow.heads.size() == 1; Map.Entry<Integer, FlowHead> finalHead = cpsFlow.heads.entrySet().iterator().next(); assert finalHead.getValue().get() instanceof FlowEndNode; Assert.assertEquals(cpsFlow.storage.getNode(finalHead.getValue().get().getId()), finalHead.getValue().get()); } verifyExecutionRemoved(run); }
public List<Action> loadActions(FlowNode node) throws IOException { return storage.loadActions(node); }
void newStartNode(FlowStartNode n) throws IOException { if (execution.flowStartNodeActions != null) { for (Action a : execution.flowStartNodeActions) { if (a instanceof FlowNodeAction) { ((FlowNodeAction) a).onLoad(n); } n.addAction(a); } execution.flowStartNodeActions.clear(); } // may be unset from loadProgramFailed synchronized (execution) { this.head = execution.startNodes.push(n); } execution.storage.storeNode(head, false); }
@Override public FlowNode getNode(String id) throws IOException { return storage.getNode(id); }
public void saveActions(FlowNode node, List<Action> actions) throws IOException { storage.saveActions(node, actions); }
private TimingFlowNodeStorage createStorage() throws IOException { FlowNodeStorage wrappedStorage; FlowDurabilityHint hint = getDurabilityHint(); wrappedStorage = (hint.isPersistWithEveryStep()) ? new SimpleXStreamFlowNodeStorage(this, getStorageDir()) : new BulkFlowNodeStorage(this, getStorageDir()); return new TimingFlowNodeStorage(wrappedStorage); }