Compare commits

...

3 Commits

Author SHA1 Message Date
Moritz Böhme 7ba3b1cd4f
feat: extend supervision tree 2024-08-07 10:33:19 +02:00
Moritz Böhme 1c197381b7
feat: first supervision tree 2024-08-07 10:03:31 +02:00
Moritz Böhme 846d0fb16d
feat: add supervisor 2024-08-07 08:21:57 +02:00
8 changed files with 121 additions and 93 deletions

2
.gitignore vendored
View File

@ -14,4 +14,4 @@ erl_crash.dump
/config/*.secret.exs /config/*.secret.exs
.elixir_ls/ .elixir_ls/
### Elixir Patch ### persist

View File

@ -1,32 +1,25 @@
defmodule Todo.Cache do defmodule Todo.Cache do
use GenServer use DynamicSupervisor
@impl GenServer def start_link(init_arg) do
def init(_init_args) do IO.puts("Starting #{__MODULE__}")
Todo.Database.start()
{:ok, %{}} DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end end
@impl GenServer @impl DynamicSupervisor
def handle_call({:server_process, name}, _from, state) do def init(_) do
case Map.fetch(state, name) do DynamicSupervisor.init(strategy: :one_for_one)
{:ok, process} -> end
{:reply, process, state}
:error -> def server_process(todo_list_name) do
{:ok, new_process} = Todo.Server.start(name) case start_child(todo_list_name) do
{:ok, pid} -> pid
new_state = Map.put(state, name, new_process) {:error, {:already_started, pid}} -> pid
{:reply, new_process, new_state}
end end
end end
def start() do defp start_child(todo_list_name) do
GenServer.start(__MODULE__, nil) DynamicSupervisor.start_child(__MODULE__, {Todo.Server, todo_list_name})
end
def server_process(pid, name) do
GenServer.call(pid, {:server_process, name})
end end
end end

View File

@ -1,67 +1,50 @@
defmodule Todo.Database do defmodule Todo.Database do
alias Todo.DatabaseWorker use Supervisor
use GenServer
@db_folder "./persist" @db_folder "./persist"
@num_of_workers 3 @pool_size 3
def start do def start_link do
GenServer.start(__MODULE__, nil, name: __MODULE__) 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 end
def store(key, data) do def store(key, data) do
GenServer.cast(__MODULE__, {:store, key, data}) key
|> choose_worker()
|> Todo.DatabaseWorker.store(key, data)
end end
def get(key) do def get(key) do
GenServer.call(__MODULE__, {:get, key}) key
|> choose_worker()
|> Todo.DatabaseWorker.get(key)
end end
@impl GenServer defp choose_worker(key) do
def init(_) do :erlang.phash2(key, @pool_size) + 1
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()
IO.inspect(workers)
{:noreply, workers}
end
@impl GenServer
def handle_cast({:store, key, data}, workers) do
workers
|> get_worker(key)
|> DatabaseWorker.store(key, data)
{:noreply, workers}
end
@impl GenServer
def handle_call({:get, key}, _, workers) do
data =
workers
|> get_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)
end end
end end

View File

@ -1,48 +1,57 @@
defmodule Todo.DatabaseWorker do defmodule Todo.DatabaseWorker do
use GenServer use GenServer
def start(db_folder) do def start_link({_db_folder, worker_id} = state) do
GenServer.start(__MODULE__, db_folder) GenServer.start_link(__MODULE__, state, name: via_tuple(worker_id))
end end
def store(pid, key, data) do def store(worker_id, key, data) do
GenServer.cast(pid, {:store, key, data}) worker_id
|> via_tuple()
|> GenServer.cast({:store, key, data})
end end
def get(pid, key) do def get(worker_id, key) do
GenServer.call(pid, {:get, key}) worker_id
|> via_tuple()
|> GenServer.call({:get, key})
end
defp via_tuple(worker_id) do
Todo.ProcessRegistry.via_tuple({__MODULE__, worker_id})
end end
@impl GenServer @impl GenServer
def init(db_folder) do def init({db_folder, worker_id} = state) do
{:ok, db_folder} IO.puts("Starting #{__MODULE__} #{worker_id} with db folder #{db_folder}.")
{:ok, state}
end end
@impl GenServer @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} {db_folder, key}
|> file_name() |> file_name()
|> File.write!(:erlang.term_to_binary(data)) |> File.write!(:erlang.term_to_binary(data))
{:noreply, db_folder} {:noreply, state}
end end
@impl GenServer @impl GenServer
def handle_call({:get, key}, _, db_folder) do def handle_call({:get, key}, _, {db_folder, _worker_id} = state) do
data = data =
case File.read(file_name({db_folder, key})) do case File.read(file_name({db_folder, key})) do
{:ok, contents} -> {:ok, contents} ->
:erlang.binary_to_term(contents) :erlang.binary_to_term(contents)
_ -> {:error, :enoent} ->
nil nil
end end
{:reply, data, db_folder} {:reply, data, state}
end end
def file_name({db_folder, key}) do def file_name({db_folder, key}) do
Path.join(db_folder, to_string(key)) Path.join(db_folder, to_string(key))
end end
end end

View File

@ -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

View File

@ -1,8 +1,12 @@
defmodule Todo.Server do defmodule Todo.Server do
use GenServer use GenServer, restart: :temporary
def start(name) do def start_link(name) do
GenServer.start(__MODULE__, name) GenServer.start_link(__MODULE__, name, name: via_tuple(name))
end
def via_tuple(name) do
Todo.ProcessRegistry.via_tuple({__MODULE__, name})
end end
def add(pid, entry) do def add(pid, entry) do
@ -23,6 +27,8 @@ defmodule Todo.Server do
@impl GenServer @impl GenServer
def init(name) do def init(name) do
IO.puts("Starting #{__MODULE__} for #{name}.")
{:ok, {name, nil}, {:continue, :init}} {:ok, {name, nil}, {:continue, :init}}
end end

19
lib/todo/system.ex Normal file
View File

@ -0,0 +1,19 @@
defmodule Todo.System do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, nil)
end
@impl Supervisor
def init(_) do
Supervisor.init(
[
Todo.ProcessRegistry,
Todo.Database,
Todo.Cache
],
strategy: :one_for_one
)
end
end

BIN
persist/a

Binary file not shown.