From 846d0fb16de3a343400e8b8ff96731a606b4271c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Wed, 7 Aug 2024 08:21:57 +0200 Subject: [PATCH 1/3] feat: add supervisor --- lib/todo/cache.ex | 11 +++++++---- lib/todo/database.ex | 7 ++++--- lib/todo/database_worker.ex | 3 ++- lib/todo/server.ex | 2 ++ lib/todo/system.ex | 14 ++++++++++++++ 5 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 lib/todo/system.ex diff --git a/lib/todo/cache.ex b/lib/todo/cache.ex index 9e29fc6..b774a1c 100644 --- a/lib/todo/cache.ex +++ b/lib/todo/cache.ex @@ -3,7 +3,10 @@ defmodule Todo.Cache do @impl GenServer def init(_init_args) do + IO.puts("Starting #{__MODULE__}") + Todo.Database.start() + {:ok, %{}} end @@ -22,11 +25,11 @@ defmodule Todo.Cache do end end - def start() do - GenServer.start(__MODULE__, nil) + def start_link(_) do + GenServer.start_link(__MODULE__, nil, name: __MODULE__) end - def server_process(pid, name) do - GenServer.call(pid, {:server_process, name}) + def server_process(name) do + GenServer.call(__MODULE__, {:server_process, name}) end end diff --git a/lib/todo/database.ex b/lib/todo/database.ex index b67dfb8..2660223 100644 --- a/lib/todo/database.ex +++ b/lib/todo/database.ex @@ -19,6 +19,8 @@ defmodule Todo.Database do @impl GenServer def init(_) do + IO.puts("Starting #{__MODULE__}") + File.mkdir_p!(@db_folder) {:ok, nil, {:continue, :init}} @@ -36,14 +38,13 @@ defmodule Todo.Database do end) |> Map.new() - IO.inspect(workers) {:noreply, workers} end @impl GenServer def handle_cast({:store, key, data}, workers) do workers - |> get_worker(key) + |> choose_worker(key) |> DatabaseWorker.store(key, data) {:noreply, workers} @@ -53,7 +54,7 @@ defmodule Todo.Database do def handle_call({:get, key}, _, workers) do data = workers - |> get_worker(key) + |> choose_worker(key) |> DatabaseWorker.get(key) {:reply, data, workers} diff --git a/lib/todo/database_worker.ex b/lib/todo/database_worker.ex index 8a2e265..4ad902a 100644 --- a/lib/todo/database_worker.ex +++ b/lib/todo/database_worker.ex @@ -15,6 +15,8 @@ defmodule Todo.DatabaseWorker do @impl GenServer def init(db_folder) do + IO.puts("Starting #{__MODULE__} with db folder #{db_folder}.") + {:ok, db_folder} end @@ -44,5 +46,4 @@ defmodule Todo.DatabaseWorker do def file_name({db_folder, key}) do Path.join(db_folder, to_string(key)) end - end diff --git a/lib/todo/server.ex b/lib/todo/server.ex index 2688873..a881bc6 100644 --- a/lib/todo/server.ex +++ b/lib/todo/server.ex @@ -23,6 +23,8 @@ defmodule Todo.Server do @impl GenServer def init(name) do + IO.puts("Starting #{__MODULE__} for #{name}.") + {:ok, {name, nil}, {:continue, :init}} end diff --git a/lib/todo/system.ex b/lib/todo/system.ex new file mode 100644 index 0000000..c9ff243 --- /dev/null +++ b/lib/todo/system.ex @@ -0,0 +1,14 @@ +defmodule Todo.System do + use Supervisor + + def start_link do + Supervisor.start_link(__MODULE__, nil) + end + + def init(_) do + Supervisor.init( + [Todo.Cache], + strategy: :one_for_one + ) + end +end From 1c197381b7f1d7351656a02e1bb26a75359a5aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Wed, 7 Aug 2024 10:03:31 +0200 Subject: [PATCH 2/3] feat: first supervision tree --- .gitignore | 2 +- lib/todo/cache.ex | 4 +- lib/todo/database.ex | 90 ++++++++++++++--------------------- lib/todo/database_worker.ex | 34 ++++++++----- lib/todo/process_registry.ex | 18 +++++++ lib/todo/server.ex | 4 +- lib/todo/system.ex | 7 ++- persist/a | Bin 198 -> 0 bytes 8 files changed, 85 insertions(+), 74 deletions(-) create mode 100644 lib/todo/process_registry.ex delete mode 100644 persist/a diff --git a/.gitignore b/.gitignore index 0aa57d7..47940df 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ erl_crash.dump /config/*.secret.exs .elixir_ls/ -### Elixir Patch ### +persist diff --git a/lib/todo/cache.ex b/lib/todo/cache.ex index b774a1c..32b48c8 100644 --- a/lib/todo/cache.ex +++ b/lib/todo/cache.ex @@ -5,8 +5,6 @@ defmodule Todo.Cache do def init(_init_args) do IO.puts("Starting #{__MODULE__}") - Todo.Database.start() - {:ok, %{}} end @@ -17,7 +15,7 @@ defmodule Todo.Cache do {:reply, process, state} :error -> - {:ok, new_process} = Todo.Server.start(name) + {:ok, new_process} = Todo.Server.start_link(name) new_state = Map.put(state, name, new_process) diff --git a/lib/todo/database.ex b/lib/todo/database.ex index 2660223..9d67f38 100644 --- a/lib/todo/database.ex +++ b/lib/todo/database.ex @@ -1,68 +1,50 @@ defmodule Todo.Database do - alias Todo.DatabaseWorker - use GenServer + use Supervisor @db_folder "./persist" - @num_of_workers 3 + @pool_size 3 - def start do - GenServer.start(__MODULE__, nil, name: __MODULE__) + def start_link do + Supervisor.start_link(__MODULE__, nil) + end + + @impl Supervisor + def init(_) do + IO.puts("Starting #{__MODULE__}.") + + File.mkdir_p!(@db_folder) + + children = Enum.map(1..@pool_size, &worker_spec/1) + + Supervisor.init(children, strategy: :one_for_one) + end + + defp worker_spec(worker_id) do + default_worker_spec = {Todo.DatabaseWorker, {@db_folder, worker_id}} + Supervisor.child_spec(default_worker_spec, id: worker_id) + end + + def child_spec(_) do + %{ + id: __MODULE__, + start: {__MODULE__, :start_link, []}, + type: :supervisor + } end def store(key, data) do - GenServer.cast(__MODULE__, {:store, key, data}) + key + |> choose_worker() + |> Todo.DatabaseWorker.store(key, data) end def get(key) do - GenServer.call(__MODULE__, {:get, key}) + key + |> choose_worker() + |> Todo.DatabaseWorker.get(key) end - @impl GenServer - def init(_) do - IO.puts("Starting #{__MODULE__}") - - File.mkdir_p!(@db_folder) - - {:ok, nil, {:continue, :init}} - end - - @impl GenServer - def handle_continue(:init, nil) do - File.mkdir_p!(@db_folder) - - workers = - 0..(@num_of_workers - 1) - |> Enum.map(fn i -> - {:ok, pid} = Todo.DatabaseWorker.start(@db_folder) - {i, pid} - end) - |> Map.new() - - {:noreply, workers} - end - - @impl GenServer - def handle_cast({:store, key, data}, workers) do - workers - |> choose_worker(key) - |> DatabaseWorker.store(key, data) - - {:noreply, workers} - end - - @impl GenServer - def handle_call({:get, key}, _, workers) do - data = - workers - |> choose_worker(key) - |> DatabaseWorker.get(key) - - {:reply, data, workers} - end - - defp choose_worker(workers, key) do - id = :erlang.phash2(key, @num_of_workers) - - Map.fetch!(workers, id) + defp choose_worker(key) do + :erlang.phash2(key, @pool_size) end end diff --git a/lib/todo/database_worker.ex b/lib/todo/database_worker.ex index 4ad902a..8005d0c 100644 --- a/lib/todo/database_worker.ex +++ b/lib/todo/database_worker.ex @@ -1,36 +1,44 @@ defmodule Todo.DatabaseWorker do use GenServer - def start(db_folder) do - GenServer.start(__MODULE__, db_folder) + def start_link({_db_folder, worker_id} = state) do + GenServer.start_link(__MODULE__, state, name: via_tuple(worker_id)) end - def store(pid, key, data) do - GenServer.cast(pid, {:store, key, data}) + def store(worker_id, key, data) do + worker_id + |> via_tuple() + |> GenServer.cast({:store, key, data}) end - def get(pid, key) do - GenServer.call(pid, {:get, key}) + def get(worker_id, key) do + worker_id + |> via_tuple() + |> GenServer.call({:get, key}) + end + + defp via_tuple(worker_id) do + Todo.ProcessRegistry.via_tuple({__MODULE__, worker_id}) end @impl GenServer - def init(db_folder) do - IO.puts("Starting #{__MODULE__} with db folder #{db_folder}.") + def init({db_folder, worker_id} = state) do + IO.puts("Starting #{__MODULE__} #{worker_id} with db folder #{db_folder}.") - {:ok, db_folder} + {:ok, state} end @impl GenServer - def handle_cast({:store, key, data}, db_folder) do + def handle_cast({:store, key, data}, {db_folder, _worker_id} = state) do {db_folder, key} |> file_name() |> File.write!(:erlang.term_to_binary(data)) - {:noreply, db_folder} + {:noreply, state} end @impl GenServer - def handle_call({:get, key}, _, db_folder) do + def handle_call({:get, key}, _, {db_folder, _worker_id} = state) do data = case File.read(file_name({db_folder, key})) do {:ok, contents} -> @@ -40,7 +48,7 @@ defmodule Todo.DatabaseWorker do nil end - {:reply, data, db_folder} + {:reply, data, state} end def file_name({db_folder, key}) do diff --git a/lib/todo/process_registry.ex b/lib/todo/process_registry.ex new file mode 100644 index 0000000..37e8060 --- /dev/null +++ b/lib/todo/process_registry.ex @@ -0,0 +1,18 @@ +defmodule Todo.ProcessRegistry do + def start_link do + IO.puts("Starting #{__MODULE__}.") + Registry.start_link(keys: :unique, name: __MODULE__) + end + + def via_tuple(key) do + {:via, Registry, {__MODULE__, key}} + end + + def child_spec(_) do + Supervisor.child_spec( + Registry, + id: __MODULE__, + start: {__MODULE__, :start_link, []} + ) + end +end diff --git a/lib/todo/server.ex b/lib/todo/server.ex index a881bc6..21b0dba 100644 --- a/lib/todo/server.ex +++ b/lib/todo/server.ex @@ -1,8 +1,8 @@ defmodule Todo.Server do use GenServer - def start(name) do - GenServer.start(__MODULE__, name) + def start_link(name) do + GenServer.start_link(__MODULE__, name) end def add(pid, entry) do diff --git a/lib/todo/system.ex b/lib/todo/system.ex index c9ff243..4955c93 100644 --- a/lib/todo/system.ex +++ b/lib/todo/system.ex @@ -5,9 +5,14 @@ defmodule Todo.System do Supervisor.start_link(__MODULE__, nil) end + @impl Supervisor def init(_) do Supervisor.init( - [Todo.Cache], + [ + Todo.ProcessRegistry, + Todo.Database, + Todo.Cache + ], strategy: :one_for_one ) end diff --git a/persist/a b/persist/a deleted file mode 100644 index dca63f37477f53429f5e68755f2497b2cc76cffd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198 zcmZ9G!3x4K5JaanrA04-|IwdN4}#!9?_t|rXdq1?U7mHF1L`lMbWBBp>eS1azM0O`kTwNb)FxQ|07EP}dH?_b From 7ba3b1cd4fa63d719db9a60792871b10679c8cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Wed, 7 Aug 2024 10:33:19 +0200 Subject: [PATCH 3/3] feat: extend supervision tree --- lib/todo/cache.ex | 34 +++++++++++++--------------------- lib/todo/database.ex | 2 +- lib/todo/database_worker.ex | 2 +- lib/todo/server.ex | 8 ++++++-- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/lib/todo/cache.ex b/lib/todo/cache.ex index 32b48c8..672966a 100644 --- a/lib/todo/cache.ex +++ b/lib/todo/cache.ex @@ -1,33 +1,25 @@ defmodule Todo.Cache do - use GenServer + use DynamicSupervisor - @impl GenServer - def init(_init_args) do + def start_link(init_arg) do IO.puts("Starting #{__MODULE__}") - {:ok, %{}} + DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) end - @impl GenServer - def handle_call({:server_process, name}, _from, state) do - case Map.fetch(state, name) do - {:ok, process} -> - {:reply, process, state} + @impl DynamicSupervisor + def init(_) do + DynamicSupervisor.init(strategy: :one_for_one) + end - :error -> - {:ok, new_process} = Todo.Server.start_link(name) - - new_state = Map.put(state, name, new_process) - - {:reply, new_process, new_state} + def server_process(todo_list_name) do + case start_child(todo_list_name) do + {:ok, pid} -> pid + {:error, {:already_started, pid}} -> pid end end - def start_link(_) do - GenServer.start_link(__MODULE__, nil, name: __MODULE__) - end - - def server_process(name) do - GenServer.call(__MODULE__, {:server_process, name}) + defp start_child(todo_list_name) do + DynamicSupervisor.start_child(__MODULE__, {Todo.Server, todo_list_name}) end end diff --git a/lib/todo/database.ex b/lib/todo/database.ex index 9d67f38..f7112bb 100644 --- a/lib/todo/database.ex +++ b/lib/todo/database.ex @@ -45,6 +45,6 @@ defmodule Todo.Database do end defp choose_worker(key) do - :erlang.phash2(key, @pool_size) + :erlang.phash2(key, @pool_size) + 1 end end diff --git a/lib/todo/database_worker.ex b/lib/todo/database_worker.ex index 8005d0c..1ffd956 100644 --- a/lib/todo/database_worker.ex +++ b/lib/todo/database_worker.ex @@ -44,7 +44,7 @@ defmodule Todo.DatabaseWorker do {:ok, contents} -> :erlang.binary_to_term(contents) - _ -> + {:error, :enoent} -> nil end diff --git a/lib/todo/server.ex b/lib/todo/server.ex index 21b0dba..15af6b1 100644 --- a/lib/todo/server.ex +++ b/lib/todo/server.ex @@ -1,8 +1,12 @@ defmodule Todo.Server do - use GenServer + use GenServer, restart: :temporary def start_link(name) do - GenServer.start_link(__MODULE__, name) + GenServer.start_link(__MODULE__, name, name: via_tuple(name)) + end + + def via_tuple(name) do + Todo.ProcessRegistry.via_tuple({__MODULE__, name}) end def add(pid, entry) do