skip to content

How do you cancel an in-progress Gradle build started through the Tooling API, and what does an IDE need to wire up to support a Stop button?

level: seniorimportance: should knowfreq 38%

answer

  1. GradleConnector.newCancellationTokenSource()
  2. source.token() -> withCancellationToken(token)
  3. source.cancel() requests stop
  4. cooperative / best-effort, not kill -9
  5. ends in BuildCancelledException

basics

~10 s

Create a CancellationTokenSource, pass its token to the launcher via withCancellationToken(token), and call source.cancel() to request cancellation. The build then fails with a BuildCancelledException.

solid answer

~40 s

Cancellation uses a `CancellationTokenSource`. You create one with `GradleConnector.newCancellationTokenSource()`, obtain its `CancellationToken` via `token()`, and attach it to the operation with `withCancellationToken(token)` before running. When the user clicks Stop, you call `source.cancel()`; that signals the build, which stops cooperatively at the next safe point. The operation then fails — synchronously a `BuildCancelledException` is thrown, asynchronously it arrives in `onFailure`. Cancellation is best-effort and cooperative: tasks already executing may not stop instantly, and some work can't be interrupted, so it is not a guaranteed immediate kill. An IDE wires the token source to its Stop button, shares the token across the build (and any related operations it wants cancellable together), and treats `BuildCancelledException` as a normal user action rather than an error. The same token mechanism works for `BuildLauncher`, `ModelBuilder`, and `BuildActionExecuter`.

code

kotlin · 15 lines
kotlin
val source = GradleConnector.newCancellationTokenSource()
val launcher = connection.newBuild()
    .forTasks("build")
    .withCancellationToken(source.token())

// background: run it
launcher.run(object : ResultHandler<Void> {
    override fun onComplete(r: Void?) { ui.done() }
    override fun onFailure(e: GradleConnectionException) {
        if (e is BuildCancelledException) ui.cancelled() else ui.failed(e)
    }
})

// Stop button handler:
source.cancel()

go deeper

for a junior

Know that a CancellationTokenSource + withCancellationToken + cancel() is how you stop a build.

for a middle

Explain the three pieces (source/token/attach) and that cancellation surfaces as BuildCancelledException.

for a senior

Discuss cooperative/best-effort semantics, sharing a token across operations, and keeping the daemon warm.

for a principal

Design Stop/timeout governance: per-operation vs shared tokens, distinguishing cancel from failure in telemetry, and bounding runaway builds without killing daemons.

## The problem Builds are long. Any IDE that runs Gradle needs a **Stop** button. The Tooling API provides cooperative cancellation via a token, modeled closely on the JDK's cancellation idioms. ## The three pieces 1. **CancellationTokenSource** — the controller. Create it from the connector: ``` CancellationTokenSource source = GradleConnector.newCancellationTokenSource(); ``` 2. **CancellationToken** — the read-only handle the build observes. Get it with `source.token()`. 3. **withCancellationToken(token)** — attaches the token to a `LongRunningOperation` (so `BuildLauncher`, `ModelBuilder`, `BuildActionExecuter`, `TestLauncher` all accept it). ## Wiring it up ``` CancellationTokenSource source = GradleConnector.newCancellationTokenSource(); BuildLauncher launcher = connection.newBuild() .forTasks("build") .withCancellationToken(source.token()); // later, from the Stop button: source.cancel(); // the run() call (or async onFailure) ends with BuildCancelledException ``` ## Cooperative, not preemptive Calling `cancel()` *requests* cancellation; Gradle stops at the next safe checkpoint between/within tasks. Consequences: - Work in flight may finish before the build unwinds. - A task doing uninterruptible native work may not stop until it yields. - It is therefore **best-effort**: fast, but not an instantaneous kill -9. ## How cancellation surfaces - **Synchronous** `run()`: throws `BuildCancelledException` (a subtype of `GradleConnectionException`). - **Asynchronous** `run(ResultHandler)`: delivered to `onFailure(...)` as a `BuildCancelledException`. A good integration distinguishes this exception from real build failures and shows "Build cancelled" rather than an error. ## One source per cancellable unit A single `CancellationTokenSource` can be shared across multiple operations you want to cancel together (e.g. a model fetch followed by a build), or you can create one per operation for independent control. Once cancelled, a source is spent — create a new one for the next run. ## Daemon implications Cancellation tries to stop the build inside the existing daemon rather than killing the daemon, so the daemon stays warm for the next build — important for IDE responsiveness.

  • Is cancellation guaranteed to stop the build immediately?
    No. It is cooperative and best-effort — Gradle stops at the next safe point, so in-flight or uninterruptible work may continue briefly.
  • Can one CancellationTokenSource cancel several operations?
    Yes — share its token across multiple long-running operations to cancel them together; but once cancelled the source is spent, so create a fresh one per run.
  • How should an IDE treat BuildCancelledException?
    As a normal user-initiated outcome ("Build cancelled"), not as a build error — it's a subtype of GradleConnectionException but semantically distinct.

saying these in an interview costs you the question

  • Claiming cancel() forcibly kills the build/daemon instantly.
  • Reusing a CancellationTokenSource after it has already been cancelled.
  • Reporting BuildCancelledException as a build failure to the user.

context