# Interruption

Interruption is how Effect cancels work it no longer needs: the loser of a
[race](https://effect.plants.sh/concurrency/racing-and-timeouts/), every child of a fiber that just
failed, or a request that hit its timeout. The crucial guarantee is that
interruption is **safe** — when a fiber is interrupted, all of its finalizers run
and every [scoped resource](https://effect.plants.sh/resource-management/) is released before the fiber
is considered done. You cancel work without leaking it.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  yield* Effect.log("start")
  yield* Effect.sleep("2 seconds")

  // Explicitly interrupt the current fiber. Nothing after this line runs.
  yield* Effect.interrupt

  yield* Effect.log("never reached")
})

Effect.runFork(program)
// Logs "start", then the fiber terminates with an Interrupt cause —
// "never reached" is never logged.
```

## The interruption model

A naive cancellation scheme lets one fiber forcibly kill another at any
instruction. That risks leaving shared state half-modified. Effect avoids this
with **asynchronous interruption**: a fiber is sent an interruption *signal*, and
the runtime acts on it only at safe points between operations — never in the
middle of an atomic step. Crucially, you don't have to manually poll a flag; the
runtime handles the signaling, and you mark the *few* regions that must not be
cut short as uninterruptible.

A fiber that is interrupted finishes with an [`Exit`](https://effect.plants.sh/data-types/) whose
[`Cause`](https://effect.plants.sh/data-types/) contains an `Interrupt`, recording the id of the fiber
that requested the cancellation.

## Running cleanup on interruption

`Effect.onInterrupt` registers a finalizer that runs **only** when the effect is
interrupted — not on success, not on ordinary failure. It's the right hook for
cancellation-specific cleanup, like rolling back a half-applied change or
emitting a "cancelled" metric.

```ts
import { Effect, Fiber } from "effect"

const guarded = Effect.gen(function*() {
  yield* Effect.log("doing work")
  yield* Effect.sleep("1 second")
}).pipe(
  // The set of interruptor fiber ids is passed in, if you need it.
  Effect.onInterrupt(() => Effect.log("interrupted: cleaning up"))
)

const program = Effect.gen(function*() {
  const fiber = yield* Effect.forkChild(guarded)
  yield* Effect.sleep("100 millis")
  yield* Fiber.interrupt(fiber)
})

Effect.runFork(program)
// doing work
// interrupted: cleaning up
```
**Tip:** `onInterrupt` fires *only* for interruption. If you want cleanup that runs no
  matter how the effect ends — success, failure, or interruption — use
  `Effect.onExit` (it receives the full `Exit`), or acquire the resource through
  a [`Scope`](https://effect.plants.sh/resource-management/) so its finalizer is guaranteed.

## Uninterruptible regions

Sometimes a sequence must run to completion once started — claiming a lock and
recording that you did, committing then acknowledging. Wrap such a critical
section in `Effect.uninterruptible` so an interruption signal is *deferred* until
the region finishes.

```ts
import { Effect } from "effect"

const transfer = Effect.uninterruptible(
  Effect.gen(function*() {
    yield* Effect.log("debit account A")
    yield* Effect.log("credit account B")
    // Even if an interrupt arrives mid-transfer, both steps complete first;
    // the interruption takes effect only after this block returns.
  })
)
```

For finer control, `Effect.uninterruptibleMask` makes a region uninterruptible
but hands you a `restore` function to re-open interruptibility for the parts that
*should* be cancellable — typically the blocking wait in the middle.

```ts
import { Effect } from "effect"

const acquireUseRelease = Effect.uninterruptibleMask((restore) =>
  Effect.gen(function*() {
    yield* Effect.log("acquire lock") // protected — must not be skipped

    // `restore` runs the long, blocking part interruptibly so a cancellation
    // request isn't stuck waiting for the whole region.
    yield* restore(Effect.sleep("10 seconds"))

    yield* Effect.log("release lock") // protected
  })
)
```

`Effect.interruptible` is the inverse: it re-enables interruption inside an
otherwise uninterruptible region.
**Caution:** Uninterruptible regions delay cancellation, so keep them short. A long
  uninterruptible block makes timeouts and races unresponsive — only the work
  that truly must be atomic belongs inside one.

## Interruption of concurrent effects

When you run effects concurrently and one is interrupted (or fails), the others
that are still running are interrupted too. The resulting `Cause` records each
fiber that was interrupted, combined in parallel.

```ts
import { Effect } from "effect"

const program = Effect.forEach(
  [1, 2, 3],
  (n) =>
    Effect.gen(function*() {
      yield* Effect.log(`start #${n}`)
      yield* Effect.sleep(`${n} seconds`)
      // The slower siblings get interrupted when this one cancels everything.
      if (n > 1) {
        yield* Effect.interrupt
      }
      yield* Effect.log(`done #${n}`)
    }).pipe(Effect.onInterrupt(() => Effect.log(`interrupted #${n}`))),
  { concurrency: "unbounded" }
)

Effect.runFork(program)
// start #1 / start #2 / start #3
// done #1
// interrupted #2 / interrupted #3
```

## Inspecting interruption

Because an interrupted fiber's outcome is an `Exit` carrying a `Cause`, you can
distinguish interruption from real failure when you handle the result. `Cause`
provides predicates like `Cause.hasInterrupts` (the cause contains *any*
interruption) and `Cause.hasInterruptsOnly` (the cause is interruption only, with
no genuine failures):

```ts
import { Cause, Effect, Exit, Fiber } from "effect"

const program = Effect.gen(function*() {
  const fiber = yield* Effect.forkChild(Effect.never)
  yield* Effect.sleep("50 millis")
  yield* Fiber.interrupt(fiber)

  // `Fiber.await` returns the Exit without re-raising the interruption.
  const exit = yield* Fiber.await(fiber)
  if (Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)) {
    yield* Effect.log("fiber was cancelled, not a real error")
  }
})
```

This distinction matters for retries and error reporting: an interruption is a
deliberate cancellation, not a fault, so you usually don't want to retry it or
alert on it. See [Error Management](https://effect.plants.sh/error-management/) for working with `Cause`
in depth.