# `Lockstep.Strategy`
[🔗](https://github.com/b-erdem/lockstep/blob/v0.1.0/lib/lockstep/strategy.ex#L1)

Behaviour for scheduling strategies.

A strategy is a pure module + state. The controller threads the state
through every interaction.

Built-in strategies:

  * `Lockstep.Strategy.Random` — uniform random over the ready set.
  * `Lockstep.Strategy.PCT`    — Burckhardt et al., ASPLOS 2010
    (Probabilistic Concurrency Testing).
  * `Lockstep.Strategy.FairPCT` — PCT then random, for liveness.
  * `Lockstep.Strategy.POS`    — Yuan et al., OOPSLA 2025 (Partial-
    Order Sampling).
  * `Lockstep.Strategy.IDPCT`  — Iterative-deepening PCT: cycles
    `bug_depth` across iterations so unknown-depth bugs are
    covered without manual tuning.
  * `Lockstep.Strategy.Replay` — picks per a recorded trace.

## Choosing a strategy

Different races are most reliably caught by different strategies.
In our experience:

* **`:pct` (the default)** is the all-purpose choice. PCT samples
  schedules according to a bug-depth bound *d*: a bug requiring at
  most *d* well-placed priority swaps is found with probability
  `1 / (n * k^(d-1))` per iteration, where *n* is the step count
  and *k* the number of procs. Strong on classic data races
  (lost-update, TOCTOU) and partial-order-shaped bugs.

* **`:pos`** uniformly samples partial orders. Better than PCT
  when the bug requires the strategy to consistently pick a
  *specific* proc over several consecutive sync points — PCT's
  priority-shuffle under-explores those long runs, but POS's
  uniform sampling hits them. The leader-follower async
  replication race in `test/leader_follower_register_test.exs` is
  a real example: PCT didn't find it in 100 iterations, POS found
  it on iteration 1.

  **Rule of thumb**: if PCT can't find your race, try POS before
  cranking iteration counts.

* **`:idpct`** runs PCT but cycles `bug_depth` across iterations
  (default `[2, 6]`). Use when you don't know how deep your bug
  is. Iterations at depth 2 are fast and find shallow bugs
  quickly; iterations at depth 6 catch deeper races. Costs
  nothing extra per iteration vs. fixed-depth PCT, but amortizes
  coverage across the depth range. Particularly nice for runs of
  1000+ iterations.

* **`:fair_pct`** is PCT for the first K steps, then a fair
  random walk. Use when your test has long-running infrastructure
  (heartbeats, timers, supervisors) that would otherwise dominate
  the schedule under pure PCT.

* **`:random`** is the baseline — uniform random over ready procs
  each step. Useful as a sanity check or for stress workloads
  where the bug is high-probability under random scheduling.

* **`:replay`** isn't really for hunting bugs — it follows a
  saved trace exactly, with optional random fallback for partial
  schedules. Used by the shrinker.

Strategies are deterministic given a seed, so any iteration that
finds a bug is reproducible (and shrinkable).

# `pid_set`

```elixir
@type pid_set() :: MapSet.t(pid())
```

# `state`

```elixir
@type state() :: term()
```

# `init`

```elixir
@callback init(opts :: keyword()) :: state()
```

# `on_register`

```elixir
@callback on_register(pid(), state()) :: state()
```

# `pick`

```elixir
@callback pick(pid_set(), state()) :: {pid(), state()}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
