How Jenkins Manages Build Queue Scheduling and Load Balancing: A Deep Dive into the Core Queue Engine
Jenkins manages build queue scheduling and load balancing through the hudson.model.Queue class, which maintains four internal states (waiting, blocked, buildable, pending) and assigns tasks to executors using a consistent-hashing load balancer that can be customized via extension points.
The jenkinsci/jenkins repository contains a sophisticated scheduling engine that orchestrates millions of builds across distributed executor pools. Understanding how Jenkins build queue scheduling and load balancing works is essential for administrators optimizing CI/CD performance and developers writing custom plugins. At the heart of this system lies the hudson.model.Queue class, which coordinates thread-safe state transitions and executor assignment through a series of extensible extension points.
Understanding the Jenkins Queue Architecture
The core queue implementation in hudson/model/Queue.java organizes build tasks into four distinct internal collections, each representing a specific lifecycle stage. All state transitions are protected by a ReentrantLock (Queue.lock) to ensure thread safety, while a Queue.snapshot object provides the UI with consistent read-only views without blocking the scheduling thread.
Internal Queue States and Collections
When a build enters the system, it progresses through these states:
- waitingList – Items respecting their quiet period before becoming eligible for execution.
- blockedProjects – Items that cannot run due to upstream/downstream constraints, node-level limits, or a
QueueTaskDispatcherveto. - buildables – Items ready to be assigned to an idle executor.
- pendings – Items that have been offered to an executor but have not yet started running.
How the Build Queue Scheduling Flow Works
According to the jenkinsci/jenkins source code, the scheduling engine follows a precise six-step cycle driven by the MaintainTask periodic timer:
1. Task Entry and Quiet Periods
When a build is triggered via Queue.schedule(Task, int), the task is wrapped in a Queue.Item and inserted into the waitingList until its quiet period expires.
2. Maintenance Cycle Execution
A periodic MaintainTask (initialized in Queue's constructor) invokes Queue.maintain(). This method moves items from waiting → blocked → buildable based on resource availability and dispatcher checks.
3. Blocking Checks and Validation
The Queue.getCauseOfBlockageForItem method iterates over all registered QueueTaskDispatcher extensions (QueueTaskDispatcher.all()) and checks node-level constraints via Node.canTake. If any dispatcher returns a non-null CauseOfBlockage, the item remains in the blocked list.
4. Load-Balancing and Executor Assignment
Once an item is ready, the queue creates a MappingWorksheet containing all idle executors as ExecutorChunk objects. The LoadBalancer (default is LoadBalancer.CONSISTENT_HASH) decides which executor(s) will run the task by building a consistent hash of available executors and assigning the task greedily.
The default implementation weights executors by their capacity (ec.size() * 100) to distribute load proportionally:
// Simplified excerpt from LoadBalancer.CONSISTENT_HASH.map(...)
List<ConsistentHash<ExecutorChunk>> hashes = new ArrayList<>(ws.works.size());
for (int i = 0; i < ws.works.size(); i++) {
ConsistentHash<ExecutorChunk> hash = new ConsistentHash<>(ExecutorChunk::getName);
List<ExecutorChunk> chunks = ws.works(i).applicableExecutorChunks();
Map<ExecutorChunk,Integer> toAdd = Maps.newHashMapWithExpectedSize(chunks.size());
for (ExecutorChunk ec : chunks) {
toAdd.put(ec, ec.size() * 100); // weight = #executors
}
hash.addAll(toAdd);
hashes.add(hash);
}
// greedy assignment → Mapping
5. Sorting Buildable Items
Before items are offered to executors, the QueueSorter sorts the buildables list. The default sorter orders items by FIFO, but a custom sorter can be installed via Queue.setSorter.
// QueueSorter.sortBuildableItems (abstract)
public abstract void sortBuildableItems(List<Queue.BuildableItem> buildables);
6. Execution and State Transition
The selected Executor receives a JobOffer, calls start(workUnit), and the WorkUnit runs the Task. When execution begins, Queue.onStartExecuting moves the item from pendings to the LeftItem cache.
Customizing Jenkins Build Queue Behavior
The queue exposes three primary extension points for customizing Jenkins build queue scheduling and load balancing:
Implementing a Custom LoadBalancer
You can override the default consistent-hashing strategy by extending LoadBalancer and registering it with Queue.setLoadBalancer():
@Extension
public class MyLoadBalancer extends LoadBalancer {
@Override
public Mapping map(@NonNull Task task, MappingWorksheet ws) {
// Simple round-robin: pick the first idle executor for every work chunk
Mapping m = ws.new Mapping();
for (int i = 0; i < ws.works.size(); i++) {
ExecutorChunk ec = ws.works(i).applicableExecutorChunks().get(0);
m.assign(i, ec);
}
return m;
}
}
// Enable it at runtime
Queue q = Jenkins.get().getQueue();
q.setLoadBalancer(new MyLoadBalancer());
Creating a Priority-Based QueueSorter
To change the dispatch order from FIFO to priority-based, implement QueueSorter:
@Extension
public class PrioritySorter extends QueueSorter {
@Override
public void sortBuildableItems(List<Queue.BuildableItem> buildables) {
buildables.sort(Comparator.comparingInt(item -> {
// Assume each job has a "priority" parameter
return Integer.parseInt(item.task.getProperty("priority"));
}));
}
}
Using QueueTaskDispatcher for Access Control
Implement QueueTaskDispatcher to veto builds based on external conditions like maintenance windows or license availability:
@Extension
public class MaintenanceDispatcher extends QueueTaskDispatcher {
@Override
public CauseOfBlockage canRun(Queue.Item item) {
if (System.currentTimeMillis() < maintenanceEnd) {
return CauseOfBlockage.fromMessage("Jenkins is in maintenance mode");
}
return null; // allow execution
}
}
Scheduling Builds Programmatically
You can interact directly with the queue from Groovy scripts or management commands:
def job = Jenkins.instance.getItemByFullName('example-job')
def task = job.asJob() // implements Queue.Task
def queue = Jenkins.instance.queue
queue.schedule(task, 0) // 0s quiet period → immediate entry
This invokes Queue.schedule(Task,int) and immediately inserts the task into the scheduling pipeline.
Summary
- Jenkins build queue scheduling is orchestrated by
hudson.model.Queue, which maintains thread-safe state transitions across four collections:waitingList,blockedProjects,buildables, andpendings. - Load balancing uses a consistent-hashing algorithm by default (
LoadBalancer.CONSISTENT_HASH), weighting executors by their capacity to distribute tasks proportionally. - Extension points allow customization of scheduling behavior through
LoadBalancer,QueueSorter, andQueueTaskDispatcherimplementations. - State protection relies on
Queue.lock(aReentrantLock) andQueue.snapshotfor consistent UI updates without blocking the scheduler. - Programmatic access is available via
Queue.schedule()for automation and scripted pipeline management.
Frequently Asked Questions
How does Jenkins decide which executor runs a build?
Jenkins uses the LoadBalancer.CONSISTENT_HASH implementation by default, which creates a consistent hash ring of all idle ExecutorChunk objects (grouped by node). The algorithm assigns weights based on executor count (ec.size() * 100) and maps tasks to executors using greedy assignment. You can customize this behavior by implementing the LoadBalancer extension point and registering it via Queue.setLoadBalancer().
What is the difference between blocked and buildable states in the Jenkins queue?
Items in the blockedProjects list cannot run due to external constraints checked by QueueTaskDispatcher implementations or node-level limits (Node.canTake). Items in the buildables list have passed all blocking checks and are ready for immediate executor assignment. The Queue.maintain() method periodically moves items between these states based on current resource availability and dispatcher evaluations.
Can I change the default FIFO ordering of the Jenkins build queue?
Yes. While the default QueueSorter uses FIFO ordering, you can implement a custom sorter by extending hudson.model.queue.QueueSorter and overriding sortBuildableItems(List<Queue.BuildableItem>). Register your implementation with Queue.setSorter() to reorder builds based on priority, job duration estimates, or custom business logic before the load balancer assigns them to executors.
How do I prevent specific jobs from running during maintenance windows?
Implement a QueueTaskDispatcher extension that overrides canRun(Queue.Item). Return a CauseOfBlockage object when your maintenance condition is active, or return null to allow execution. Because Jenkins checks all registered dispatchers before moving items from blockedProjects to buildables, this effectively vetoes builds during the specified window without modifying job configurations.
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 →