Created
November 11, 2024 16:44
-
-
Save zoedsoupe/6620d142801ac244f485a4283ec5249b to your computer and use it in GitHub Desktop.
minimal and simple supervisor with elixir from scracth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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