Resiliency.AllSettled (Resiliency v0.6.0)

Copy Markdown View Source

Run all functions concurrently and collect every result, regardless of failures.

When to use

  • Running a batch of independent jobs where partial failure is acceptable and you need to know which succeeded — e.g., sending notifications to multiple channels.
  • Executing health checks or non-critical side effects in parallel and inspecting each outcome individually.

How it works

All functions are spawned concurrently as monitored processes. The caller waits for every task to complete. Results are collected into an Erlang :array indexed by input position, so the output order always matches the input order regardless of completion order. Each slot is {:ok, value} or {:error, reason}. Tasks exceeding the timeout receive {:error, :timeout}.

Algorithm Complexity

TimeSpace
O(n) spawns + O(n) result collectionO(n) — :array of results + monitored processes

Examples

iex> Resiliency.AllSettled.run([fn -> 1 end, fn -> 2 end])
[{:ok, 1}, {:ok, 2}]

iex> Resiliency.AllSettled.run([])
[]

Mixed successes and failures:

iex> [{:ok, 1}, {:error, {%RuntimeError{message: "boom"}, _}}, {:ok, 3}] =
...>   Resiliency.AllSettled.run([
...>     fn -> 1 end,
...>     fn -> raise "boom" end,
...>     fn -> 3 end
...>   ])

With a timeout — completed tasks return their results, timed-out tasks get {:error, :timeout}:

Resiliency.AllSettled.run([
  fn -> quick_work() end,
  fn -> slow_work() end
], timeout: 1_000)
# => [{:ok, result}, {:error, :timeout}]

Telemetry

All events are emitted in the caller's process via :telemetry.span/3. See Resiliency.Telemetry for the complete event catalogue.

[:resiliency, :all_settled, :run, :start]

Emitted before tasks are spawned.

Measurements

KeyTypeDescription
system_timeintegerSystem.system_time() at emission time

Metadata

KeyTypeDescription
countintegerNumber of functions submitted

[:resiliency, :all_settled, :run, :stop]

Emitted after all tasks complete (or timeout).

Measurements

KeyTypeDescription
durationintegerElapsed native time units (System.monotonic_time/0 delta)

Metadata

KeyTypeDescription
countintegerTotal number of tasks
ok_countintegerNumber of tasks that returned {:ok, _}
error_countintegerNumber of tasks that returned {:error, _}

[:resiliency, :all_settled, :run, :exception]

Emitted if run/2 raises or exits unexpectedly.

Measurements

KeyTypeDescription
durationintegerElapsed native time units

Metadata

KeyTypeDescription
countintegerNumber of functions submitted
kindatomException kind (:error, :exit, or :throw)
reasontermThe exception or exit reason
stacktracelistStack at the point of the exception

Summary

Functions

Run all functions concurrently. Wait for all to complete and return results in input order.

Types

task_fun()

@type task_fun() :: (-> any())

Functions

run(funs, opts \\ [])

@spec run(
  [task_fun()],
  keyword()
) :: [ok: any(), error: any()]

Run all functions concurrently. Wait for all to complete and return results in input order.

Unlike Task.await_many/2, this never crashes the caller. Each result is {:ok, value} or {:error, reason}. Results are always in the same order as the input list, regardless of which tasks finish first. Tasks that exceed the timeout get {:error, :timeout}.

Returns [] for an empty list.

Parameters

  • funs -- a list of zero-arity functions to execute concurrently.
  • opts -- keyword list of options. Defaults to [].
    • :timeout -- milliseconds or :infinity. Defaults to :infinity.

Returns

A list of {:ok, value} or {:error, reason} tuples in the same order as the input list. Tasks that exceed the timeout produce {:error, :timeout}. An empty input list returns [].

Examples

iex> Resiliency.AllSettled.run([fn -> 1 end, fn -> 2 end])
[{:ok, 1}, {:ok, 2}]

iex> Resiliency.AllSettled.run([])
[]