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 9e29fc6..672966a 100644 --- a/lib/todo/cache.ex +++ b/lib/todo/cache.ex @@ -1,32 +1,25 @@ defmodule Todo.Cache do - use GenServer + use DynamicSupervisor - @impl GenServer - def init(_init_args) do - Todo.Database.start() - {:ok, %{}} + def start_link(init_arg) do + IO.puts("Starting #{__MODULE__}") + + 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(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() do - GenServer.start(__MODULE__, nil) - end - - def server_process(pid, name) do - GenServer.call(pid, {: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 b67dfb8..f7112bb 100644 --- a/lib/todo/database.ex +++ b/lib/todo/database.ex @@ -1,67 +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 - 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) + defp choose_worker(key) do + :erlang.phash2(key, @pool_size) + 1 end end diff --git a/lib/todo/database_worker.ex b/lib/todo/database_worker.ex index 8a2e265..1ffd956 100644 --- a/lib/todo/database_worker.ex +++ b/lib/todo/database_worker.ex @@ -1,48 +1,57 @@ 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 - {:ok, db_folder} + def init({db_folder, worker_id} = state) do + IO.puts("Starting #{__MODULE__} #{worker_id} with db folder #{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} -> :erlang.binary_to_term(contents) - _ -> + {:error, :enoent} -> nil end - {:reply, data, db_folder} + {:reply, data, state} end def file_name({db_folder, key}) do Path.join(db_folder, to_string(key)) end - end 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 2688873..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(name) do - GenServer.start(__MODULE__, name) + def start_link(name) do + 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 @@ -23,6 +27,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..4955c93 --- /dev/null +++ b/lib/todo/system.ex @@ -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 diff --git a/persist/a b/persist/a deleted file mode 100644 index dca63f3..0000000 Binary files a/persist/a and /dev/null differ