How Jenkins Implements Dependency Graph Tracking with DependencyGraph for Build Triggers
Jenkins maintains a bi-directional, immutable dependency graph via hudson.model.DependencyGraph that rebuilds when configurations change, with edges contributed by components implementing the DependencyDeclarer interface.
The jenkinsci/jenkins repository implements sophisticated dependency graph tracking to orchestrate complex build pipelines and trigger downstream jobs automatically. At the center of this mechanism is the DependencyGraph class, which stores relationships between AbstractProject instances and determines when downstream builds should fire based on upstream completion events.
Core Architecture of the DependencyGraph
Graph Construction and Immutability
When Jenkins starts or when Jenkins.rebuildDependencyGraph() is called, the system creates a fresh DependencyGraph instance. Inside DependencyGraph.build() (lines 85‑92 of core/src/main/java/hudson/model/DependencyGraph.java), the method enumerates all items of type AbstractProject and asks each project to contribute its edges:
for (AbstractProject p : Jenkins.get().allItems(AbstractProject.class))
p.buildDependencyGraph(this);
The AbstractProject.buildDependencyGraph(DependencyGraph) method delegates to the project's publishers, builders, wrappers, and triggers. Each component can implement jenkins.model.DependencyDeclarer to hook into this process and register dependencies.
Bidirectional Edge Storage
The graph stores relationships in two concurrent hash maps: forward (upstream to downstream) and backward (downstream to upstream). When a component calls graph.addDependency(Dependency), the method populates both maps simultaneously to enable efficient traversal in either direction:
public void addDependency(Dependency dep) {
if (built) throw new IllegalStateException();
add(forward, dep.getUpstreamProject(), dep);
add(backward, dep.getDownstreamProject(), dep);
}
The Dependency object records the upstream and downstream projects and defines shouldTriggerBuild, which determines whether a downstream build should start when the upstream finishes.
How Build Triggers Register Dependencies
The DependencyDeclarer Interface
Plugins and core components declare dependencies by implementing jenkins.model.DependencyDeclarer. This interface provides a single method, buildDependencyGraph(AbstractProject owner, DependencyGraph graph), which Jenkins calls during graph reconstruction. Implementations add edges by calling graph.addDependency() with a Dependency subclass that encapsulates custom trigger logic.
ReverseBuildTrigger Implementation
ReverseBuildTrigger in core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java (lines 72‑79) demonstrates how a trigger living on the downstream job registers a dependency on upstream completion:
@Override public void buildDependencyGraph(final AbstractProject downstream, DependencyGraph graph) {
for (AbstractProject upstream :
Items.fromNameList(downstream.getParent(), getUpstreamProjects(), AbstractProject.class)) {
graph.addDependency(new DependencyGraph.Dependency(upstream, downstream) {
@Override public boolean shouldTriggerBuild(AbstractBuild upstreamBuild,
TaskListener listener, List<Action> actions) {
return shouldTrigger(upstreamBuild, listener);
}
});
}
}
This trigger creates an anonymous Dependency subclass that overrides shouldTriggerBuild to check the upstream build result against configured thresholds.
Trigger Execution Flow
When an upstream build completes, the queue runner invokes Dependency.Dependency.shouldTriggerBuild. For ReverseBuildTrigger, the actual scheduling happens in ReverseBuildTrigger.RunListenerImpl.onCompleted (lines 72‑99):
if (trigger.shouldTrigger(r, listener)) {
ParameterizedJobMixIn.scheduleBuild2(trigger.job, -1,
new CauseAction(new Cause.UpstreamCause(r)));
}
The listener retrieves cached triggers for the completed build’s project, evaluates shouldTrigger, and schedules the downstream job via ParameterizedJobMixIn.scheduleBuild2 with an UpstreamCause.
Topological Ordering and Cycle Detection
After all projects contribute edges, DependencyGraph.build() finalizes the forward and backward maps, wraps them in unmodifiable collections, and executes Tarjan’s strongly connected components algorithm (lines 101‑135). This computes a stable topologicalOrder used for project comparison via DependencyGraph.compare() and for detecting circular dependencies in the job hierarchy.
Graph Rebuild Lifecycle
When and How the Graph Rebuilds
Reconstruction occurs in two scenarios:
- Manual request – Calling
Jenkins.get().rebuildDependencyGraph()(or the asynchronousrebuildDependencyGraphAsync()) - Configuration change – Most configurables invoke
rebuildDependencyGraphAsync()after persisting changes to disk
Because the graph is immutable after construction, Jenkins atomically replaces the old instance with a new one without requiring synchronization locks, making the rebuild process thread-safe and cheap.
Querying Dependencies at Runtime
Developers can access the current graph to inspect relationships programmatically:
// Force a rebuild (e.g., from a system script)
Jenkins.get().rebuildDependencyGraph(); // triggers a fresh DependencyGraph build
// Implement a simple DependencyDeclarer (e.g., a custom publisher)
public class MyPublisher extends Recorder implements DependencyDeclarer {
@Override
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
// add an edge from a static upstream job to the current job
Job<?,?> upstream = Jenkins.get().getItem("my-upstream", Job.class);
if (upstream != null) {
graph.addDependency(new DependencyGraph.Dependency(upstream, owner));
}
}
}
// Query the graph at runtime
DependencyGraph dg = Jenkins.get().getDependencyGraph();
List<AbstractProject> downstream = dg.getDownstream(someProject);
System.out.println("Downstream jobs: " + downstream);
Summary
- Jenkins uses an immutable
DependencyGraphstored inhudson.model.DependencyGraphto track bi-directional job relationships - The
DependencyDeclarerinterface injenkins.modelallows publishers, builders, and triggers to contribute edges during graph construction ReverseBuildTriggerexemplifies downstream trigger logic by implementing customshouldTriggerBuildlogic injenkins/triggers/ReverseBuildTrigger.java- Graph reconstruction happens atomically via
Jenkins.rebuildDependencyGraph()or asynchronously after configuration changes - Tarjan’s SCC algorithm provides topological sorting and cycle detection across the project hierarchy
Frequently Asked Questions
How does Jenkins detect circular dependencies between jobs?
The DependencyGraph class performs a topological sort using Tarjan’s strongly connected components algorithm during the build() method. Strongly connected components indicate cycles, which Jenkins handles by computing a stable topologicalOrder that enforces a consistent project ranking and prevents infinite trigger loops.
Can plugins declare custom dependencies without modifying core Jenkins code?
Yes. Plugins implement the jenkins.model.DependencyDeclarer interface on their extensions (such as publishers or triggers). During graph reconstruction, Jenkins automatically invokes buildDependencyGraph() on these components, allowing them to call graph.addDependency() with custom Dependency objects that define specific trigger conditions via shouldTriggerBuild().
What happens to running builds when the dependency graph is rebuilt?
Running builds remain unaffected because DependencyGraph is immutable after construction. When rebuildDependencyGraph() executes, it creates a completely new instance and atomically swaps the reference in the Jenkins singleton. Existing builds continue referencing the graph instance that existed at their start, ensuring thread safety without requiring synchronization locks.
How does ReverseBuildTrigger determine whether to fire a downstream build?
The trigger implements a custom shouldTriggerBuild() method that evaluates the upstream build result (e.g., checking if it matches Result.SUCCESS or other configured thresholds) and verifies user permissions. The RunListenerImpl invokes this check upon upstream completion in onCompleted(), and only calls ParameterizedJobMixIn.scheduleBuild2() if the method returns true.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →