# How Jenkins Handles Concurrent Build Execution Using Executor and RunnerStack

> Discover how Jenkins master concurrent build execution with Executor threads and RunnerStack's thread mapping for efficient CI/CD pipelines. Learn the core mechanics.

- Repository: [Jenkins/jenkins](https://github.com/jenkinsci/jenkins)
- Tags: internals
- Published: 2026-07-28

---

**Jenkins handles concurrent build execution by assigning each build to a dedicated `Executor` thread on a node, while `RunnerStack` maintains a per-thread mapping so the system can always identify which `RunExecution` is active on the current thread.**

Jenkins, as implemented in the `jenkinsci/jenkins` repository, scales build throughput by parallelizing work across multiple executor threads rather than running jobs sequentially. Understanding how **Jenkins concurrent build execution** actually works requires examining the interaction between the `Executor` class that hosts build threads and the `RunnerStack` utility that tracks them.

## Executor Lifecycle and Thread Assignment

When a node is created, the number of executors it can host is stored in `Computer.numExecutors`. For each configured slot, a `hudson.model.Executor` object is instantiated, but the underlying Java thread is **lazy-started** only when the queue assigns a `WorkUnit` to it via `Executor.start(WorkUnit)`.

### Inside Executor.run() and State Locking

Once started, the executor thread enters `Executor.run()`. According to the Jenkins source code in [`core/src/main/java/hudson/model/Executor.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/model/Executor.java), this method performs the following steps:

1. Acquires the internal `ReadWriteLock` referred to as `lock` to protect mutable state such as `executable`, `workUnit`, and `asynchronousExecution`.
2. Asks the queue to `callWithLock` so that the transition from *idle* to *building* is atomic.
3. Creates the `Executable` by invoking `createExecutable()` and stores it.
4. Calls `queue.execute(executable, task)`, which finally delegates to the build’s `RunExecution.run()` method.

The executor therefore isolates each build in its own Java thread, while the **Queue** guarantees that two executors never pick the same `WorkUnit`. Concurrency is limited only by the number of executors configured on the node.

## RunnerStack: Per-Thread RunExecution Bookkeeping

While a build is running, Jenkins must know **which `RunExecution` is associated with the current thread**—for example, when handling checkpoints, processing interruptions, or rendering the “who is building this?” UI. The `hudson.model.RunnerStack` class solves this by keeping a `WeakHashMap<Executor, Stack<RunExecution>>`.

### Pushing the Current RunExecution on Build Start

When a build starts, `Run.onStartBuilding()` pushes the current `RunExecution` onto the stack:

```java
// Run.java
protected void onStartBuilding() {
    …
    if (runner != null)
        RunnerStack.INSTANCE.push(runner);
    …
}

```

(source: [`Run.java`](https://github.com/jenkinsci/jenkins/blob/main/Run.java#L67-L73)

### Popping the Stack on Build Completion

When the build ends, `Run.onEndBuilding()` pops the stack, finalizes checkpoint bookkeeping, and clears the runner reference:

```java
protected void onEndBuilding() {
    …
    if (runner != null) {
        runner.checkpoints.allDone();   // finish checkpoint bookkeeping
        runner = null;
        RunnerStack.INSTANCE.pop();
    }
    …
}

```

(source: [`Run.java`](https://github.com/jenkinsci/jenkins/blob/main/Run.java#L79-L88)

### Querying the Current Build Context

`RunnerStack.peek()` is used throughout Jenkins core—such as in checkpoint handling inside `Run.reportCheckpoint`—to obtain the **currently executing build** for the calling thread:

```java
// Run.java
Run<?, ?>.RunExecution exec = RunnerStack.INSTANCE.peek();

```

(source: [`Run.java`](https://github.com/jenkinsci/jenkins/blob/main/Run.java#L34-L42)

## Concurrency Safety Mechanisms

All mutable fields in `Executor` are guarded by its internal `ReadWriteLock`. The queue interacts with the executor under `Queue.callWithLock`, ensuring that only one thread can modify an executor’s state at a time.

`RunnerStack` itself is a synchronized structure, and because the underlying map uses weak references, finished executors do not leak memory. These layers ensure that **Jenkins concurrent build execution** remains safe without requiring plugins to manage their own locking.

## Querying and Controlling Executors in Practice

### Query an Executor’s State via the Remote API

You can inspect whether an executor is idle or running a build using a Groovy script:

```groovy
import jenkins.model.Jenkins

def node = Jenkins.instance.getComputer('my‑agent')
node.getExecutors().each { exec ->
    println "Executor #${exec.getNumber()} on ${node.getName()}: " +
            (exec.isIdle() ? 'idle' : "running ${exec.getCurrentExecutable().getFullDisplayName()}")
}

```

*Uses*: `Executor.isIdle()`, `Executor.getCurrentExecutable()` — see [`Executor.java`](https://github.com/jenkinsci/jenkins/blob/main/Executor.java#L17-L25).

### Using RunnerStack Inside a Plugin to Find the Current Build

Plugins can discover the active build for the current thread without explicit reference passing:

```java
import hudson.model.RunnerStack;
import hudson.model.Run;

Run<?, ?>.RunExecution exec = RunnerStack.INSTANCE.peek();
if (exec != null) {
    Run<?,?> build = exec.getBuild();
    // e.g. log the job name
    listener.getLogger().println("Current build: " + build.getFullDisplayName());
}

```

*Reference*: `RunnerStack.peek()` implementation — see [`RunnerStack.java`](https://github.com/jenkinsci/jenkins/blob/main/RunnerStack.java#L64-L72).

### Manual Creation of a WorkUnit for Testing

For integration tests or advanced automation, you can manually create a `WorkUnit` and assign it to an idle executor:

```java
import hudson.model.Executor;
import hudson.model.Queue;
import hudson.model.Queue.WorkUnit;

// Assume 'computer' is a Computer instance with at least one idle executor
Executor exec = computer.getExecutors().stream()
                       .filter(Executor::isIdle)
                       .findFirst()
                       .orElseThrow(() -> new IllegalStateException("No idle executor"));

WorkUnit wu = new WorkUnit(/* task */, /* actions */, /* actions */);
exec.start(wu);   // executor thread will be started on demand

```

*Key method*: `Executor.start(WorkUnit)` — see [`Executor.java`](https://github.com/jenkinsci/jenkins/blob/main/Executor.java#L110-L118).

## Summary

- **Executor threads** are created lazily via `Executor.start(WorkUnit)` and run builds in isolated Java threads, with state protected by a `ReadWriteLock`.
- The **Queue** assigns `WorkUnit`s atomically using `Queue.callWithLock`, ensuring no two executors claim the same work.
- **RunnerStack** provides a synchronized, per-thread stack of `RunExecution` instances, enabling components to query the current build via `peek()`.
- Builds push their `RunExecution` onto the stack in `Run.onStartBuilding()` and pop it in `Run.onEndBuilding()`, preventing stale context.
- Concurrency is bounded only by `Computer.numExecutors`, making scalability a matter of node configuration.

## Frequently Asked Questions

### What limits how many builds Jenkins can run concurrently on one node?

The hard limit is `Computer.numExecutors`, which defines how many `hudson.model.Executor` instances (and therefore Java threads) are created for that node. The Queue will not assign more `WorkUnit`s than there are idle executors available.

### How does Jenkins prevent race conditions when an executor picks up a new build?

Jenkins uses `Queue.callWithLock` to perform the idle-to-building transition atomically. Inside `Executor.run()`, the executor acquires its internal `ReadWriteLock` before mutating state, so the Queue and the executor thread never corrupt shared data.

### Why does Jenkins use RunnerStack instead of passing RunExecution references directly?

`RunnerStack` provides a global, thread-local lookup via `WeakHashMap<Executor, Stack<RunExecution>>`. This design lets internal systems—such as checkpoint reporting and the “who is building this?” UI—discover the current execution context without threading explicit references through every API boundary.

### Can a plugin safely call RunnerStack.peek() during a build?

Yes. As implemented in the Jenkins source code, `RunnerStack` is synchronized and intended for this exact purpose. If the calling thread is an executor running a build, `peek()` returns the active `RunExecution`; otherwise it returns `null`.