defmodule SApp.BlockStore do @moduledoc """ A module that implements a content-adressable storage (blocks, or pages, identified by the hash of their contents). This is not a shard, it is a side process that a shard may use to store its data. TODO: WIP """ use GenServer @enforce_keys [:pid] defstruct [:pid, :prefer_ask] defmodule State do defstruct [:shard_id, :path, :store, :reqs, :retries] end def start_link(shard_id, path) do GenServer.start_link(__MODULE__, [shard_id, path]) end def init([shard_id, path]) do Shard.Manager.dispatch_to(shard_id, path, self()) {:ok, %State{shard_id: shard_id, path: path, store: %{}, reqs: %{}, retries: %{}}} end def handle_call({:get, key, prefer_ask}, from, state) do case state.store[key] do nil -> case prefer_ask do [_ | _] -> for peer <- prefer_ask do Shard.Manager.send(peer, {state.shard_id, state.path, {:get, key}}) end _ -> ask_random_peers(state, key) end reqs_key = case state.reqs[key] do nil -> MapSet.put(MapSet.new(), from) ms -> MapSet.put(ms, from) end state = put_in(state.reqs[key], reqs_key) {:noreply, state} v -> {:reply, v, state} end end def handle_call({:put, val}, _from, state) do hash = SData.term_hash val state = %{state | store: Map.put(state.store, hash, val)} {:reply, hash, state} end def handle_cast({:msg, peer_id, _shard_id, _path, msg}, state) do state = case msg do {:get, key} -> case state.store[key] do nil -> Shard.Manager.send(peer_id, {state.shard_id, state.path, {:not_found, key}}) v -> Shard.Manager.send(peer_id, {state.shard_id, state.path, {:info, key, v}}) end state {:info, hash, value} -> if SData.term_hash value == hash do reqs = case state.reqs[hash] do nil -> state.reqs pids -> for pid <- pids do GenServer.reply(pid, value) end Map.delete(state.reqs, hash) end state = %{state | retries: Map.delete(state.retries, hash)} %{state | store: Map.put(state.store, hash, value), reqs: reqs} else state end {:not_found, key} -> if state.reqs[key] != nil and state.store[key] == nil do nretry = case state.retries[key] do nil -> 1 n -> n+1 end if nretry < 3 do ask_random_peers(state, key) %{state | retries: Map.put(state.retries, key, nretry)} else for pid <- state.reqs[key] do GenServer.reply(pid, nil) end state = %{state | reqs: Map.delete(state.reqs, key)} state = %{state | retries: Map.delete(state.retries, key)} state end else state end end {:noreply, state} end def ask_random_peers(state, key) do peers = :ets.lookup(:shard_peer_db, state.shard_id) |> Enum.shuffle |> Enum.take(3) for {_, peer} <- peers do Shard.Manager.send(peer, {state.shard_id, state.path, {:get, key}}) end end defimpl SData.PageStore do def put(store, page) do hash = GenServer.call(store.pid, {:put, page}) { hash, store } end def get(store, hash) do try do GenServer.call(store.pid, {:get, hash, store.prefer_ask}) catch :exit, {:timeout, _} -> nil end end def copy(store, other_store, hash) do page = SData.PageStore.get(other_store, hash) refs = SData.Page.refs(page) for ref <- refs do copy(store, other_store, ref) end GenServer.call(store.pid, {:put, page}) store end def free(store, _hash) do store ## DO SOMETHING??? end end end