All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gradle.execution.plan.Node Maven / Gradle / Ivy

There is a newer version: 8.11.1
Show newest version
/*
 * Copyright 2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.execution.plan;

import com.google.common.annotations.VisibleForTesting;
import org.gradle.api.Action;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.tasks.VerificationException;
import org.gradle.execution.plan.edges.DependencyNodesSet;
import org.gradle.execution.plan.edges.DependentNodesSet;
import org.gradle.internal.resources.ResourceLock;

import javax.annotation.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Consumer;

/**
 * A node in the execution graph that represents some executable code with potential dependencies on other nodes.
 */
public abstract class Node {
    @VisibleForTesting
    enum ExecutionState {
        // Node is not scheduled to run in any plan
        // Nodes may be moved back into this state when the execution plan is cancelled or aborted due to a failure
        NOT_SCHEDULED,
        // Node has been scheduled in an execution plan and should run if possible (depending on failures in other nodes)
        SHOULD_RUN,
        // Node is currently executing
        EXECUTING,
        // Node has been executed, and possibly failed, in an execution plan (not necessarily the current)
        EXECUTED,
        // Node cannot be executed because of a failed dependency
        FAILED_DEPENDENCY
    }

    public enum DependenciesState {
        // Still waiting for dependencies to complete
        NOT_COMPLETE,
        // All dependencies complete, can run this node
        COMPLETE_AND_SUCCESSFUL,
        // All dependencies complete, but cannot run this node due to failure
        COMPLETE_AND_NOT_SUCCESSFUL,
        // All dependencies complete, but this node does not need to run
        COMPLETE_AND_CAN_SKIP
    }

    private ExecutionState state = ExecutionState.NOT_SCHEDULED;
    private boolean dependenciesProcessed;
    private DependenciesState dependenciesState = DependenciesState.NOT_COMPLETE;
    private Throwable executionFailure;
    private boolean filtered;
    private int index;
    private DependencyNodesSet dependencyNodes = DependencyNodesSet.EMPTY;
    private DependentNodesSet dependentNodes = DependentNodesSet.EMPTY;
    private final MutationInfo mutationInfo = new MutationInfo();
    private NodeGroup group = NodeGroup.DEFAULT_GROUP;

    @VisibleForTesting
    ExecutionState getState() {
        return state;
    }

    String healthDiagnostics() {
        if (isComplete()) {
            return this + " (state=" + state + ")";
        } else {
            StringBuilder specificState = new StringBuilder();
            dependencyNodes.healthDiagnostics(specificState);
            nodeSpecificHealthDiagnostics(specificState);
            return this + " (state=" + state
                + ", dependencies=" + dependenciesState
                + ", group=" + group
                + ", " + specificState + " )";
        }
    }

    public static String formatNodes(Iterable nodes) {
        StringBuilder builder = new StringBuilder();
        builder.append('[');
        boolean first = true;
        for (Node node : nodes) {
            if (first) {
                first = false;
            } else {
                builder.append(", ");
            }
            builder.append(node).append(" (").append(node.getState()).append(")");
        }
        builder.append(']');
        return builder.toString();
    }

    protected void nodeSpecificHealthDiagnostics(StringBuilder builder) {
    }

    public NodeGroup getGroup() {
        return group;
    }

    public void setGroup(NodeGroup group) {
        if (this.group != group) {
            this.group.removeMember(this);
            this.group = group;
            this.group.addMember(this);
        }
    }

    @Nullable
    public OrdinalGroup getOrdinal() {
        return group.asOrdinal();
    }

    /**
     * Potentially update the ordinal group of this node when it is reachable from the given group.
     */
    public void maybeInheritOrdinalAsDependency(@Nullable OrdinalGroup candidateOrdinal) {
        if (group == candidateOrdinal || candidateOrdinal == null) {
            // Ignore candidate groups that have no ordinal value
            return;
        }
        OrdinalGroup currentOrdinal = group.asOrdinal();
        if (currentOrdinal == null || candidateOrdinal.getOrdinal() < currentOrdinal.getOrdinal()) {
            // Currently has no ordinal value or candidate has a smaller ordinal value - merge the candidate into the current group
            setGroup(group.reachableFrom(candidateOrdinal));
        }
    }

    /**
     * Maybe update the group for this node when it is a dependency of one or more finalizers.
     *
     * 

When this method is called, the group of each node that depends on this node has been updated.

*/ public void maybeInheritFinalizerGroups() { NodeGroup newGroup = group; for (Node predecessor : getDependencyPredecessors()) { if (predecessor.getGroup() instanceof HasFinalizers) { newGroup = maybeInheritGroupAsFinalizerDependency((HasFinalizers) predecessor.getGroup(), newGroup); } } if (newGroup != group) { setGroup(newGroup); } } private static HasFinalizers maybeInheritGroupAsFinalizerDependency(HasFinalizers finalizers, NodeGroup current) { if (current == finalizers || current == NodeGroup.DEFAULT_GROUP) { return finalizers; } if (current instanceof OrdinalGroup) { return CompositeNodeGroup.mergeInto((OrdinalGroup) current, finalizers); } else { return CompositeNodeGroup.mergeInto((HasFinalizers) current, finalizers); } } /** * Maybe update the group for this node when its dependencies are in groups with higher ordinal. * * That way we can ensure that the node is executed after all its dependencies, and we get rid of potential cycles. */ public void maybeUpdateOrdinalGroup() { OrdinalGroup ordinal = getGroup().asOrdinal(); OrdinalGroup newOrdinal = ordinal; for (Node successor : getHardSuccessors()) { OrdinalGroup successorOrdinal = successor.getGroup().asOrdinal(); if (successorOrdinal != null && (newOrdinal == null || successorOrdinal.getOrdinal() > newOrdinal.getOrdinal())) { newOrdinal = successorOrdinal; } } if (newOrdinal != ordinal) { setGroup(getGroup().withOrdinalGroup(newOrdinal)); } } @Nullable public FinalizerGroup getFinalizerGroup() { return group.asFinalizer(); } public boolean isRequired() { return state == ExecutionState.SHOULD_RUN; } public boolean isDoNotIncludeInPlan() { return filtered || state == ExecutionState.NOT_SCHEDULED || isCannotRunInAnyPlan(); } public boolean isCannotRunInAnyPlan() { return state == ExecutionState.EXECUTED || state == ExecutionState.FAILED_DEPENDENCY; } /** * Is this node ready to execute? Note: does not consider the dependencies of the node. */ public boolean isReady() { return state == ExecutionState.SHOULD_RUN; } public boolean isCanCancel() { return group.isCanCancel(); } public boolean isInKnownState() { return state != ExecutionState.NOT_SCHEDULED; } public boolean isExecuting() { return state == ExecutionState.EXECUTING; } /** * Is it possible for this node to run in the current plan? Returns {@code true} if this node definitely will not run, {@code false} if it is still possible for the node to run. * *

A node may be complete for several reasons, for example:

*
    *
  • when its actions have been executed, or when its outputs have been considered up-to-date or loaded from the build cache
  • *
  • when it cannot run due to a failure in a dependency
  • *
  • when it is cancelled due to a failure in some other node and not running with --continue
  • *
  • when it is a finalizer of tasks that have all completed but did not run
  • *
*/ public boolean isComplete() { return state == ExecutionState.EXECUTED || state == ExecutionState.FAILED_DEPENDENCY || state == ExecutionState.NOT_SCHEDULED || filtered; } public boolean isSuccessful() { return filtered || (state == ExecutionState.EXECUTED && !isFailed()); } /** * Whether this node failed with a verification failure. * * @return true if failed and threw {@link VerificationException}, false otherwise */ public boolean isVerificationFailure() { return getNodeFailure() != null && getNodeFailure().getCause() instanceof VerificationException; } public boolean isFailed() { return getNodeFailure() != null || getExecutionFailure() != null; } public boolean isExecuted() { return state == ExecutionState.EXECUTED; } /** * Returns true when this node should be executed as soon as its dependencies are ready, rather than at its default point in * the execution plan. Does not affect the dependencies of this node. * *

Use sparingly, and only for fast work that requires access to some project or other resource.

*/ public boolean isPriority() { return false; } /** * Returns any error that happened during the execution of the node itself, * i.e. a task action has thrown an exception. */ @Nullable public abstract Throwable getNodeFailure(); public void startExecution(Consumer nodeStartAction) { assert state == ExecutionState.SHOULD_RUN && allDependenciesComplete() && allDependenciesSuccessful(); state = ExecutionState.EXECUTING; Set finalizers = getFinalizers(); if (!finalizers.isEmpty()) { for (Node finalizer : finalizers) { finalizer.getGroup().onNodeStart(finalizer, this); } } nodeStartAction.accept(this); } public void finishExecution(Consumer completionAction) { assert state == ExecutionState.EXECUTING; state = ExecutionState.EXECUTED; completionAction.accept(this); } public void markFailedDueToDependencies(Consumer completionAction) { assert state == ExecutionState.SHOULD_RUN; state = ExecutionState.FAILED_DEPENDENCY; completionAction.accept(this); } public void cancelExecution(Consumer completionAction) { if (state != ExecutionState.SHOULD_RUN && state != ExecutionState.NOT_SCHEDULED) { throw new IllegalStateException("Cannot cancel node " + this); } state = ExecutionState.NOT_SCHEDULED; completionAction.accept(this); } public void require() { if (isCannotRunInAnyPlan()) { return; } if (state != ExecutionState.SHOULD_RUN) { // When the state changes to `SHOULD_RUN`, the dependencies need to be reprocessed since they also may be required now. dependenciesProcessed = false; state = ExecutionState.SHOULD_RUN; } } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } /** * Mark this node as filtered from the current plan. The node will be considered complete and successful. */ public void filtered() { if (isCannotRunInAnyPlan()) { return; } filtered = true; } /** * Discards any plan specific state for this node, so that it can potentially be added to another execution plan. */ public void reset() { group = NodeGroup.DEFAULT_GROUP; index = 0; if (!isCannotRunInAnyPlan()) { filtered = false; dependenciesProcessed = false; state = ExecutionState.NOT_SCHEDULED; dependenciesState = DependenciesState.NOT_COMPLETE; } } public void setExecutionFailure(Throwable failure) { assert state == ExecutionState.EXECUTING; this.executionFailure = failure; } /** * Returns any error that happened in the execution engine while processing this node, * i.e. there was a {@link NullPointerException} in the {@link ExecutionPlan} code. * Always leads to the abortion of the build. */ @Nullable public Throwable getExecutionFailure() { return this.executionFailure; } public SortedSet getDependencyPredecessors() { return dependentNodes.getDependencyPredecessors(); } public Set getDependencySuccessors() { return dependencyNodes.getDependencySuccessors(); } public void addDependencySuccessor(Node toNode) { dependencyNodes = dependencyNodes.addDependency(toNode); toNode.addDependencyPredecessor(this); } void addDependencyPredecessor(Node fromNode) { dependentNodes = dependentNodes.addDependencyPredecessors(fromNode); mutationInfo.addConsumer(fromNode); } void addMustPredecessor(TaskNode fromNode) { dependentNodes = dependentNodes.addMustPredecessor(fromNode); } protected DependencyNodesSet getDependencyNodes() { return dependencyNodes; } protected void updateDependencyNodes(DependencyNodesSet newDependencies) { dependencyNodes = newDependencies; } /** * Called when a node that this node may be waiting for has completed. */ public void onNodeComplete(Node node) { dependencyNodes.onNodeComplete(this, node); updateAllDependenciesComplete(); } @OverridingMethodsMustInvokeSuper protected DependenciesState doCheckDependenciesComplete() { DependenciesState state = dependencyNodes.getState(this); if (state == DependenciesState.NOT_COMPLETE || state == DependenciesState.COMPLETE_AND_NOT_SUCCESSFUL) { return state; } // All dependencies are complete and successful, delegate to the group return group.checkSuccessorsCompleteFor(this); } /** * Returns if all dependencies completed, but have not been completed in the last check. */ public void updateAllDependenciesComplete() { if (dependenciesState == DependenciesState.NOT_COMPLETE) { forceAllDependenciesCompleteUpdate(); } } public void forceAllDependenciesCompleteUpdate() { dependenciesState = doCheckDependenciesComplete(); } /** * Is this node ready to execute or discard (eg because a dependency has failed)? */ public boolean allDependenciesComplete() { return state == ExecutionState.SHOULD_RUN && dependenciesState != DependenciesState.NOT_COMPLETE; } /** * Can this node execute or should it be discarded? Should only be called when {@link #allDependenciesComplete()} returns true. */ public boolean allDependenciesSuccessful() { return dependenciesState == DependenciesState.COMPLETE_AND_SUCCESSFUL; } /** * Should this node be cancelled or marked as failed? Should only be called when {@link #allDependenciesSuccessful()} returns false. */ public boolean shouldCancelExecutionDueToDependencies() { return dependenciesState == DependenciesState.COMPLETE_AND_CAN_SKIP; } /** * This {@link Node} may continue execution if the successor Node was successful, or if non-successful when two specific criteria are met: *
    *
  1. The successor node failure is a "verification failure"
  2. *
  3. The relationship to the successor Node is via task output/task input wiring, not an explicit dependsOn relationship (which are discouraged)
  4. *
* * @param dependency a successor node in the execution plan * @return true if the successor task was successful, or failed but a "recoverable" verification failure and this Node may continue execution; false otherwise * @see gradle/gradle#18912 */ public boolean shouldContinueExecution(Node dependency) { return dependency.isSuccessful() || (dependency.isVerificationFailure() && !dependsOnOutcome(dependency)); } /** * Can be overridden to indicate the relationship between this {@link Node} and a successor Node. * * @param dependency a non-successful successor node in the execution plan * @return Always returns false unless overridden. */ protected boolean dependsOnOutcome(Node dependency) { return false; } /** * Visits all nodes whose {@link #allDependenciesComplete()} state depends in some way on the completion of this node. * Should visit the nodes in a deterministic order, but the order can be whatever best makes sense for the node implementation. */ protected void visitAllNodesWaitingForThisNode(Consumer visitor) { dependentNodes.visitAllNodes(visitor); } /** * Called prior to attempting to schedule a node. */ public void prepareForScheduling() { ExecutionState initialState = getInitialState(); if (initialState != null) { state = initialState; } } @Nullable protected Node.ExecutionState getInitialState() { return null; } /** * Called when the graph containing this node is about to start execution. * * @param monitor An action that should be called when this node is ready to execute, when the dependencies for this node are executed outside * the work graph that contains this node (for example, when the node represents a task in an included build). */ public void prepareForExecution(Action monitor) { } public abstract void resolveDependencies(TaskDependencyResolver dependencyResolver); public boolean getDependenciesProcessed() { return dependenciesProcessed; } public void dependenciesProcessed() { dependenciesProcessed = true; } @OverridingMethodsMustInvokeSuper public Iterable getAllSuccessors() { return getHardSuccessors(); } /** * Returns all the nodes which are hard successors, i.e. which have a non-removable relationship to the current node. * * For example, for tasks `shouldRunAfter` isn't a hard successor while `mustRunAfter` is. */ @OverridingMethodsMustInvokeSuper public Iterable getHardSuccessors() { return dependencyNodes.getDependencySuccessors(); } /** * Iterates over the nodes which are hard successors applying a visitor, i.e. which have a non-removable relationship to the current node. * * This can be a more efficient way to iterate over the successors than {@link #getHardSuccessors()}. */ @OverridingMethodsMustInvokeSuper public void visitHardSuccessors(Consumer visitor) { dependencyNodes.getDependencySuccessors().forEach(visitor); } public SortedSet getFinalizers() { return dependentNodes.getFinalizers(); } public void addFinalizer(Node finalizer) { dependentNodes = dependentNodes.addFinalizer(finalizer); } public Set getFinalizingSuccessors() { return Collections.emptySet(); } /** * Visits the "pre-execution" nodes of this node. These nodes should be treated as though they are dependencies of this node. * This method is called when this node is ready to execute and its other dependencies are complete, * allowing some dependencies of this node to be defined dynamically. * *

Note: there is currently no cycle detection applied to these dynamically added nodes or their dependencies. * Support for this is not implemented yet and will be added later. */ public void visitPreExecutionNodes(Consumer visitor) { } public boolean hasPendingPreExecutionNodes() { return false; } /** * Visits the "post-execution" nodes of this node. These nodes should be treated as though they also produce the outputs or * results of this node. That is, all nodes that depend on this node should also depend on these nodes. This method is called when * this node has executed successfully and before any of its dependents are started, allowing some work of this node to be dynamically split * up into other nodes that can run in parallel or with different resource requirements. * *

Note: there is currently no cycle detection applied to these dynamically added nodes or their dependencies. * Support for this is not implemented yet and will be added later. * *

Note: mustRunAfter or finalizedBy relationship on this node is not honored for these dynamically added nodes or their dependencies. * Support for this is not implemented yet and will be added later. */ public void visitPostExecutionNodes(Consumer visitor) { } public MutationInfo getMutationInfo() { return mutationInfo; } public boolean isPublicNode() { return false; } /** * Returns the project state that this node requires mutable access to, if any. */ @Nullable public ResourceLock getProjectToLock() { return null; } /** * Returns the project which this node belongs to, and requires access to the execution services of. * Returning non-null does not imply that the project must be locked when this node executes. Use {@link #getProjectToLock()} instead for that. * * TODO - this should return some kind of abstract 'action context' instead of a mutable project. */ @Nullable public ProjectInternal getOwningProject() { return null; } /** * Returns the resources which should be locked before starting this node. * * This operation should complete quickly, * must not run user code, and * should not need to acquire additional locks. */ public List getResourcesToLock() { return Collections.emptyList(); } @Override public abstract String toString(); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy