Packages
SuperWorker is a powerful Elixir library for working with supervisors and background jobs. It provides a much simpler approach than traditional supervisors. This library is currently under development and is unstable, so it is not recommended for production use.
Current section
Files
Jump to
Current section
Files
lib/config_loader/bootstrap.ex
defmodule SuperWorker.ConfigLoader.Bootstrap do
@moduledoc """
Bootstraps supervisors from parsed configurations.
This module takes validated and parsed configurations from the Parser module
and uses the SuperWorker.Supervisor API to start supervisors and their children.
"""
alias SuperWorker.Supervisor
require Logger
require SuperWorker.Log
@doc """
Starts a supervisor with the given parsed configuration.
## Parameters
* `config` - A parsed configuration map containing:
* `:options` - Supervisor options (must include `:id`)
* `:children` - List of child specifications
## Returns
* `{:ok, pid}` - Successfully started supervisor
* `{:error, reason}` - Failed to start supervisor
## Example
```elixir
config = %{
options: [id: :my_sup, num_partitions: 2, link: false],
children: [
%{
type: :group,
id: :my_group,
options: [restart_strategy: :one_for_one],
workers: [
%{mfa: {MyModule, :worker_fun, []}, options: [id: :worker1]}
]
}
]
}
Bootstrap.start_supervisor(config)
```
"""
@spec start_supervisor(map()) :: {:ok, pid()} | {:error, any()}
def start_supervisor(%{options: options, children: children}) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, starting supervisor with options: #{inspect(options)}"
end)
sup_id = Keyword.get(options, :id)
if sup_id == nil do
Logger.error("SuperWorker, Bootstrap, supervisor options must include :id")
{:error, :missing_supervisor_id}
else
with {:ok, pid} <- start_supervisor_process(options),
:ok <- add_children(sup_id, children) do
Logger.info("SuperWorker, Bootstrap, successfully started supervisor: #{inspect(sup_id)}")
{:ok, pid}
else
{:error, reason} = error ->
Logger.error(
"SuperWorker, Bootstrap, failed to start supervisor #{inspect(sup_id)}: #{inspect(reason)}"
)
Supervisor.stop(sup_id)
error
end
end
end
def start_supervisor(invalid) do
Logger.error("SuperWorker, Bootstrap, invalid configuration format: #{inspect(invalid)}")
{:error, :invalid_config_format}
end
# Starts the supervisor process with the given options
defp start_supervisor_process(options) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, starting supervisor process with: #{inspect(options)}"
end)
case Supervisor.start_with_config(options) do
{:ok, pid} ->
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, supervisor process started: #{inspect(pid)}"
end)
{:ok, pid}
{:error, reason} = error ->
Logger.error(
"SuperWorker, Bootstrap, failed to start supervisor process: #{inspect(reason)}"
)
error
end
end
# Adds all children to the supervisor
defp add_children(_sup_id, []) do
SuperWorker.Log.debug(fn -> "SuperWorker, Bootstrap, no children to add" end)
:ok
end
defp add_children(sup_id, children) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, adding #{length(children)} children to supervisor #{inspect(sup_id)}"
end)
results =
children
|> Enum.map(fn child ->
add_child(sup_id, child)
end)
errors = Enum.filter(results, fn result -> match?({:error, _}, result) end)
if Enum.empty?(errors) do
:ok
else
Logger.error("SuperWorker, Bootstrap, errors adding children: #{inspect(errors)}")
{:error, {:children_start_errors, errors}}
end
end
# Adds a single child (group, chain, or standalone worker)
defp add_child(sup_id, %{type: :group} = group) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, adding group #{inspect(group.id)}, options: #{inspect(group.options)}"
end)
group_options = Keyword.put(group.options, :id, group.id)
with {:ok, _} <- Supervisor.add_group(sup_id, group_options),
:ok <- add_group_workers(sup_id, group.id, group.workers) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, successfully added group: #{inspect(group.id)}"
end)
:ok
else
{:error, reason} = error ->
Logger.error(
"SuperWorker, Bootstrap, failed to add group #{inspect(group.id)}: #{inspect(reason)}"
)
error
end
end
defp add_child(sup_id, %{type: :chain} = chain) do
SuperWorker.Log.debug(fn -> "SuperWorker, Bootstrap, adding chain #{inspect(chain.id)}" end)
chain_options = Keyword.put(chain.options, :id, chain.id)
with {:ok, _} <- Supervisor.add_chain(sup_id, chain_options),
:ok <- add_chain_workers(sup_id, chain.id, chain.workers) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, successfully added chain: #{inspect(chain.id)}"
end)
:ok
else
{:error, reason} = error ->
Logger.error(
"SuperWorker, Bootstrap, failed to add chain #{inspect(chain.id)}: #{inspect(reason)}"
)
error
end
end
defp add_child(sup_id, %{type: :standalone, mfa: mfa, options: options}) do
SuperWorker.Log.debug(fn -> "SuperWorker, Bootstrap, adding standalone worker" end)
# Ensure restart_strategy is set for standalone workers (default: :permanent)
options_with_defaults =
if Keyword.has_key?(options, :restart_strategy) do
options
else
Keyword.put(options, :restart_strategy, :permanent)
end
case Supervisor.add_standalone_worker(sup_id, mfa, options_with_defaults) do
{:ok, worker_id} ->
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, successfully added standalone worker: #{inspect(worker_id)}"
end)
:ok
{:error, reason} = error ->
Logger.error(
"SuperWorker, Bootstrap, failed to add standalone worker: #{inspect(reason)}"
)
error
end
end
defp add_child(_sup_id, invalid) do
Logger.error("SuperWorker, Bootstrap, invalid child: #{inspect(invalid)}")
{:error, :invalid_child}
end
# Adds workers to a group
defp add_group_workers(_sup_id, _group_id, []) do
SuperWorker.Log.debug(fn -> "SuperWorker, Bootstrap, no workers to add to group" end)
:ok
end
defp add_group_workers(sup_id, group_id, workers) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, adding #{length(workers)} workers to group #{inspect(group_id)}"
end)
results =
workers
|> Enum.with_index()
|> Enum.map(fn {worker, index} ->
add_group_worker(sup_id, group_id, worker, index)
end)
errors = Enum.filter(results, fn result -> match?({:error, _}, result) end)
if Enum.empty?(errors) do
:ok
else
Logger.error(
"SuperWorker, Bootstrap, errors adding workers to group #{inspect(group_id)}: #{inspect(errors)}"
)
{:error, {:group_workers_start_errors, errors}}
end
end
defp add_group_worker(sup_id, group_id, %{mfa: mfa, options: options}, index) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, adding worker at index #{index} to group #{inspect(group_id)}, mfa: #{inspect(mfa)}, options: #{inspect(options)}"
end)
case Supervisor.add_group_worker(sup_id, group_id, mfa, options) do
{:ok, worker_id} ->
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, successfully added worker #{inspect(worker_id)} to group #{inspect(group_id)}"
end)
:ok
{:error, reason} = error ->
Logger.error(
"SuperWorker, Bootstrap, failed to add worker to group #{inspect(group_id)}: #{inspect(reason)}"
)
error
end
end
# Adds workers to a chain
defp add_chain_workers(_sup_id, _chain_id, []) do
SuperWorker.Log.debug(fn -> "SuperWorker, Bootstrap, no workers to add to chain" end)
:ok
end
defp add_chain_workers(sup_id, chain_id, workers) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, adding #{length(workers)} workers to chain #{inspect(chain_id)}"
end)
results =
workers
|> Enum.with_index()
|> Enum.map(fn {worker, index} ->
add_chain_worker(sup_id, chain_id, worker, index)
end)
errors = Enum.filter(results, fn result -> match?({:error, _}, result) end)
if Enum.empty?(errors) do
:ok
else
Logger.error(
"SuperWorker, Bootstrap, errors adding workers to chain #{inspect(chain_id)}: #{inspect(errors)}"
)
{:error, {:chain_workers_start_errors, errors}}
end
end
defp add_chain_worker(sup_id, chain_id, %{mfa: mfa, options: options}, index) do
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, adding worker at index #{index} to chain #{inspect(chain_id)}"
end)
case Supervisor.add_chain_worker(sup_id, chain_id, mfa, options) do
{:ok, worker_id} ->
SuperWorker.Log.debug(fn ->
"SuperWorker, Bootstrap, successfully added worker #{inspect(worker_id)} to chain #{inspect(chain_id)}"
end)
:ok
{:error, reason} = error ->
Logger.error(
"SuperWorker, Bootstrap, failed to add worker to chain #{inspect(chain_id)}: #{inspect(reason)}"
)
error
end
end
end