# How Caddy's Context Cancellation Works: Three-Stage Cleanup Explained

> Understand Caddy's context cancellation with its three-stage cleanup process. Learn how it handles reloads and shutdowns effectively. Discover Caddy's robust mechanism for managing operations.

- Repository: [Caddy/caddy](https://github.com/caddyserver/caddy)
- Tags: internals
- Published: 2026-03-03

---

**Caddy triggers context cancellation through a three-stage process that executes standard context cancellation, registered `OnCancel` callbacks, and module-specific `Cleanup()` methods whenever configurations reload or servers shut down.**

Caddy's context cancellation mechanism provides deterministic resource cleanup during configuration reloads and graceful shutdowns. This architecture, implemented in the `caddyserver/caddy` repository, extends Go's standard `context.Context` to handle the complex lifecycle of modular server components. Understanding how Caddy's context cancellation works is essential for developing custom modules that require proper teardown logic and resource management.

## The Architecture of Caddy's Context System

Caddy builds its own **`caddy.Context`** type on top of the standard library's `context.Context`. When provisioning a new configuration, the `NewContext` function in [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go) creates a cancelable context using `context.WithCancel`, deriving it from a parent context.

The implementation stores the returned **`CancelFunc`** directly on the `Config` struct as `cfg.cancelFunc` ([`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go) lines 95-104). This design allows the server to trigger cancellation later by invoking the stored function, ensuring that all downstream operations derived from that context receive the cancellation signal.

## The Three-Stage Cancellation Process

When the stored `cancelFunc` is invoked—typically during a configuration reload or server shutdown—the system executes three coordinated cleanup stages defined in [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go):

- **Standard Context Cancellation (lines 66-68):** The underlying `context.CancelFunc` fires immediately, unblocking any goroutine waiting on `ctx.Done()`.

- **OnCancel Callback Execution (lines 71-73):** The system executes all functions registered via `ctx.OnCancel(f)`. This Caddy-specific hook allows modules to register custom teardown code at runtime.

- **Module Cleanup Iteration (lines 75-84):** The context iterates over all modules loaded within its scope and, if they implement the **`CleanerUpper`** interface, invokes their `Cleanup()` method to release resources like file handles and network sockets.

## Configuration Lifecycle and Cancellation Triggers

The cancellation lifecycle follows the configuration provisioning flow in [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go). During the provisioning phase (lines 95-104), `provisionContext` creates a fresh `caddy.Context` and saves the cancel function to `newCfg.cancelFunc`.

When a new configuration loads or the server initiates shutdown, the previous configuration's `cancelFunc` is explicitly invoked. This occurs in the `run` function (lines 418-424) and during error handling in `provisionContext` (lines 730-740), ensuring that resources from the old configuration are released before or during the transition to the new state.

## Practical Implementation Examples

### Registering Cleanup Hooks with ctx.OnCancel()

Modules can register custom teardown logic using the `OnCancel` method during the provisioning phase. This approach is ideal for closing database connections, flushing buffers, or releasing temporary resources.

```go
func (m *MyModule) Provision(ctx caddy.Context) error {
    // Perform module initialization...
    
    ctx.OnCancel(func() {
        // Module-specific teardown logic
        m.db.Close()
        m.cache.Flush()
    })
    
    return nil
}

```

This callback mechanism is implemented in [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go) (lines 91-94), which appends the function to an internal slice executed during the second cancellation stage.

### Implementing the CleanerUpper Interface

For modules that require structured cleanup logic, implement the `CleanerUpper` interface. The system automatically detects and invokes this method during the third cancellation stage.

```go
type MyModule struct {
    listener net.Listener
}

// Cleanup is called automatically when the context is cancelled
func (m *MyModule) Cleanup() error {
    if m.listener != nil {
        return m.listener.Close()
    }
    return nil
}

```

### Handling QUIC Listener Cancellation

Subsystems often wrap the standard cancel function to perform additional bookkeeping. In [`listeners.go`](https://github.com/caddyserver/caddy/blob/main/listeners.go), the `sharedQUICState.addState` method (lines 53-71) creates its own `context.WithCancel` and wraps the cancel function to remove TLS configuration entries from internal maps.

When `fakeCloseQuicListener.Close` is called (lines 38-44), it invokes the stored `contextCancel` function, which triggers both the standard context cancellation and the TLS config cleanup simultaneously.

```go
func (l *fakeCloseQuicListener) Close() error {
    if atomic.CompareAndSwapInt32(&l.closed, 0, 1) {
        // Cancel the QUIC state context, removing TLS config entries
        l.contextCancel()
    }
    return nil
}

```

## Summary

- **Caddy's context system** wraps `context.Context` using `NewContext` in [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go) to create cancelable contexts for each configuration.
- **Three-stage cancellation** executes standard context cancellation, `OnCancel` callbacks (lines 71-73), and `CleanerUpper.Cleanup()` methods (lines 75-84) to ensure complete resource release.
- **Cancel function storage** occurs in `Config.cancelFunc` during provisioning ([`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go) lines 95-104) and is triggered during reloads (lines 418-424) or shutdowns.
- **Module integration** supports both callback-based cleanup via `ctx.OnCancel()` and interface-based cleanup via `CleanerUpper`.
- **Subsystem extensions** like QUIC listeners in [`listeners.go`](https://github.com/caddyserver/caddy/blob/main/listeners.go) demonstrate how to wrap cancel functions for domain-specific resource management.

## Frequently Asked Questions

### What triggers context cancellation in Caddy?

Context cancellation triggers when Caddy reloads configurations or initiates server shutdown. The `run` function in [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go) (lines 418-424) invokes `currentCfg.cancelFunc()` when replacing an active configuration, and similar invocations occur during error handling in `provisionContext` (lines 730-740).

### How do modules register custom cleanup code during cancellation?

Modules call **`ctx.OnCancel(f)`** during their `Provision` method, registering a function that executes during the second cancellation stage. Alternatively, modules can implement the **`CleanerUpper`** interface, which provides a formal `Cleanup()` method invoked during the third stage (lines 75-84 of [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go)).

### What is the difference between OnCancel callbacks and the CleanerUpper interface?

**`OnCancel`** accepts arbitrary functions registered at runtime, suitable for ad-hoc cleanup logic specific to a particular instance. **`CleanerUpper`** defines a structured interface (`Cleanup() error`) that types implement to guarantee cleanup is called for all instances of that module type during context cancellation.

### How does Caddy ensure QUIC listeners clean up TLS configurations?

The QUIC subsystem in [`listeners.go`](https://github.com/caddyserver/caddy/blob/main/listeners.go) creates a wrapped cancel function (lines 53-71) that, when invoked by `fakeCloseQuicListener.Close` (lines 38-44), deletes the associated TLS configuration from the internal `tlsConfs` map before executing the standard context cancellation.