# Background tasks

Some work has no interface to call — it just needs to *run*: a metrics flusher,
a cache warmer, a queue consumer, a heartbeat. You want it to start with the
application and stop cleanly when the application shuts down, without exposing a
service that anyone yields. `Layer.effectDiscard` is built for exactly this. It
turns an `Effect` into a `Layer` that provides **no** services — its only job is
the side effect it runs while the layer is alive.

```ts
import { NodeRuntime } from "@effect/platform-node"
import { Effect, Layer } from "effect"

// `Layer.effectDiscard` runs an effect during layer construction and provides
// nothing. We use it to launch a background loop.
const BackgroundTask = Layer.effectDiscard(
  Effect.gen(function*() {
    yield* Effect.logInfo("Starting background task...")

    yield* Effect.gen(function*() {
      while (true) {
        yield* Effect.sleep("5 seconds")
        yield* Effect.logInfo("Background task running...")
      }
    }).pipe(
      // Observe shutdown so we can log (and clean up) when interrupted.
      Effect.onInterrupt(() =>
        Effect.logInfo("Background task interrupted: layer scope closed")
      ),
      // `forkScoped` runs the loop on a child fiber attached to the layer's
      // scope. When that scope closes, the fiber is interrupted automatically.
      Effect.forkScoped
    )
  })
)

// `Layer.launch` builds the layer and keeps it running forever. The background
// fiber is interrupted when the program is stopped (e.g. Ctrl+C / SIGTERM).
BackgroundTask.pipe(Layer.launch, NodeRuntime.runMain)
```

## Why `forkScoped` and not a plain loop

If you wrote the infinite loop directly inside `effectDiscard` without forking,
the layer would never finish constructing — `Layer.launch` would block on the
loop forever and the rest of your layer graph would never come up. Forking moves
the loop onto its own fiber so construction returns immediately while the work
runs concurrently.

`Effect.forkScoped` is the key choice for lifecycle safety:

- It attaches the new fiber to the **layer's scope**, not to the calling fiber.
- When that scope closes — at shutdown, or when the layer is torn down — the
  fiber is **interrupted automatically**. No leaked background fibers.
- Your `Effect.onInterrupt` handler then runs, giving you a place to flush
  buffers, close connections, or log a clean exit.
**forkScoped vs. fork:** A plain `Effect.forkChild` ties the child fiber to the *current* fiber's
  lifetime, and `Effect.forkDetach` detaches it to live globally (it will
  outlive the block that started it). For background work owned by a layer you
  almost always
  want `forkScoped`, so the fiber's lifetime is exactly the layer's lifetime.

## Composing background tasks with the rest of your app

Because it is just a `Layer`, a background task composes with your service
layers like anything else. Merge it into your application layer and it starts
and stops alongside your services — typically you also `Layer.provide` it any
services it depends on.

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

// Pretend these come from elsewhere in the app.
declare const MetricsService: Layer.Layer<never>
declare const flushMetrics: Effect.Effect<void>

// A background flusher that depends on no exposed interface of its own.
const MetricsFlusher = Layer.effectDiscard(
  Effect.gen(function*() {
    yield* Effect.gen(function*() {
      while (true) {
        yield* Effect.sleep("30 seconds")
        yield* flushMetrics
      }
    }).pipe(
      Effect.onInterrupt(() => Effect.logInfo("Flushing metrics one last time")),
      Effect.forkScoped
    )
  })
)

// The flusher starts when `AppLayer` is built and is interrupted when it is
// torn down. `mergeAll` runs both layers; provide it the metrics service.
const AppLayer = Layer.mergeAll(MetricsService, MetricsFlusher)
```
**Need to expose something too?:** `Layer.effectDiscard` is specifically for tasks with *no* service interface.
  If the background work should also publish state or handlers that other code
  can yield, build a normal service with `Context.Service` + `Layer.effect`,
  fork the loop with `Effect.forkScoped` during construction, and return the
  interface — see
  [acquire / release](https://effect.plants.sh/resource-management/acquire-release/) and
  [Services &amp; Layers](https://effect.plants.sh/services-and-layers/).