Skip to content

Instantly share code, notes, and snippets.

@zoedsoupe
Created November 11, 2024 16:44
Show Gist options
  • Save zoedsoupe/6620d142801ac244f485a4283ec5249b to your computer and use it in GitHub Desktop.
Save zoedsoupe/6620d142801ac244f485a4283ec5249b to your computer and use it in GitHub Desktop.
minimal and simple supervisor with elixir from scracth
defmodule Sup do
@moduledoc "Very minimal supervisor implementation, only support 1-1 strategy! DO NOT USE IN PROD"
@doc """
Starts a new process to run the `init/1` function and links the new process
with the one that called the start_link
You could start a new process without linking it to the caller, with the `spawn/1` function
Many modules and BEAM abstractions allows you to start "fire and forget" process like the `Task.start/1` one
"""
def start_link(child_spec_list) do
pid = spawn_link(__MODULE__, :init, [child_spec_list])
{:ok, pid}
end
@doc """
Traps the exit message to handle it in a resilient way and start every child process
"""
def init(child_spec_list) do
Process.flag(:trap_exit, true)
children = start_children(child_spec_list, %{})
loop(children, child_spec_list)
end
defp start_children([], children), do: children
defp start_children([child_spec | rest], children) do
{:ok, pid} = start_child(child_spec)
children = Map.put(children, pid, child_spec)
start_children(rest, children)
end
defp start_child({module, args}) do
pid = spawn_link(module, :start_link, [args])
{:ok, pid}
end
# we should extend it to match DOWN messages too
defp loop(children, child_spec_list) do
receive do
{:EXIT, pid, reason} ->
IO.puts("Child process #{inspect(pid)} exited with reason: #{inspect(reason)}")
child_spec = Map.get(children, pid)
{:ok, new_pid} = start_child(child_spec)
children = Map.delete(children, pid)
children = Map.put(children, new_pid, child_spec)
loop(children, child_spec_list)
end
end
end
defmodule MyWorker do
def start_link(args) do
pid = spawn_link(__MODULE__, :init, [args])
{:ok, pid}
end
def init(_args) do
# Simulate some work and crash
:timer.sleep(:rand.uniform(2000))
exit(:crash)
end
@doc """
The magic behind of supervised children. that's what "use GenServer" or "use Supervisor"
injects on your module, a way to define how this worker/process should be initialised, restarter or
shut down and identified
https://hexdocs.pm/elixir/Supervisor.html#module-child-specification
"""
def child_spec do
%{
id: :my_worker,
start: {__MODULE__, :init, []},
type: :worker,
shutdown: 3_000
}
end
end
# Start the supervisor with a list of child specifications
{:ok, sup_pid} = Sup.start_link([
MyWorker,
MyWorker
])
# Keep the main process alive
Process.sleep(:infinity)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment