Skip to main content

README.md

# skuld_concurrency

<!-- nav:header:start -->
[Coroutine >](docs/effects/coroutine.md) | [Umbrella →](https://hexdocs.pm/skuld/architecture.html)
<!-- nav:header:end -->

Cooperative concurrency for the Skuld effect system — coroutines, a
fiber pool scheduler, streaming, and process bridging. Everything runs
in-process with structured concurrency; no GenServers, no supervision
trees, no message-passing overhead.

## What's included

- **`Skuld.Coroutine`** — a cooperative fiber primitive. Wrap a computation
  into a typed state machine that can be paused (via `Yield`), resumed, and
  cancelled. The building block everything else schedules.
- **`Skuld.Effects.FiberPool`** — a cooperative scheduler. Runs fibers
  concurrently within a single BEAM process, with `scope` for structured
  concurrency and deadlock detection. When fibers `await` each other,
  the scheduler automatically steps the dependency graph.
- **`Skuld.Effects.Channel`** — bounded channels with backpressure and
  error propagation. `put` suspends the fiber when full; `take` suspends
  when empty. Errors are sticky — a poisoned channel propagates to all
  consumers.
- **`Skuld.Effects.Brook`** — high-level streaming API built on channels.
  `map`, `filter`, `flat_map`, `reduce` — each combinator can run with
  configurable concurrency. Integrates transparently with query batching.
- **`Skuld.AsyncCoroutine`** — bridges effectful computations across
  processes via messages. The primary LiveView integration point: mount
  starts a runner, `handle_info` resumes it with user input.
- **`Skuld.Effects.Task`** — true BEAM-process parallelism. `Task.async`
  runs a computation in a separate process; the FiberPool scheduler
  tracks it alongside cooperative fibers.

## Installation

```elixir
def deps do
  [
    {:skuld_concurrency, "~> 0.32"}
  ]
end
```

## Example: concurrent stream processing

```elixir
use Skuld.Syntax
alias Skuld.Effects.{Yield, Channel, Brook, FiberPool}

# A stream of work items
source = 1..100 |> Enum.map(&"item-#{&1}")

comp do
  results <-
    Brook.from_enum(source)
    |> Brook.map(fn item ->
      comp do
        # Simulate async work on each item
        Yield.yield(item)
      end
    end, concurrency: 8)
    |> Brook.to_list()

  results
end
|> Yield.with_handler()
|> Channel.with_handler()
|> FiberPool.with_handler()
|> Comp.run!()
# 8 concurrent fibers, Channel provides backpressure, FiberPool schedules them all
```

## Further reading

- [Coroutine](https://hexdocs.pm/skuld_concurrency/coroutine.html) — fiber states and lifecycle
- [FiberPool](https://hexdocs.pm/skuld_concurrency/fiberpool.html) — scheduler and structured concurrency
- [Channel & Brook](https://hexdocs.pm/skuld_concurrency/channel-brook.html) — backpressure and streaming
- [AsyncCoroutine](https://hexdocs.pm/skuld_concurrency/async-coroutine.html) — LiveView integration
- [LiveView recipe](https://hexdocs.pm/skuld/liveview.html) — full LiveView example with AsyncCoroutine
- [Batch loading recipe](https://hexdocs.pm/skuld/batch-loading.html) — FiberPool + Query batching

See the [architecture guide](https://hexdocs.pm/skuld/architecture.html) for how this fits into the Skuld ecosystem.

<!-- nav:footer:start -->

---

[Coroutine >](docs/effects/coroutine.md) | [Umbrella →](https://hexdocs.pm/skuld/architecture.html)
<!-- nav:footer:end -->