Current section

Files

Jump to
hyperview lib hyparview messages shuffle.ex
Raw

lib/hyparview/messages/shuffle.ex

defmodule Hyparview.Messages.Shuffle do
@moduledoc """
SHUFFLE request abstraction module
"""
alias __MODULE__
alias Hyparview.Config
alias Hyparview.Utils
alias Hyparview.View
alias Hyparview.PeerManager
alias Hyparview.Messages.ShuffleReply
defstruct sender: Node.self(),
origin: Node.self(),
nodes: MapSet.new(),
ttl: Config.active_random_walk_length()
@type t :: %Shuffle{
sender: Node.t(),
origin: Node.t(),
nodes: MapSet.t(Node.t()),
ttl: non_neg_integer()
}
@typep new_options :: [
sender: Node.t(),
origin: Node.t(),
view: View.t(),
ttl: non_neg_integer()
]
@doc """
Make a SHUFFLE message that includes `nodes` that active_view
shuffle = Shuffle.new(view: view)
"""
@spec new(new_options()) :: Shuffle.t()
def new(options \\ []) do
%Shuffle{
sender: options[:sender] || Node.self(),
origin: options[:origin] || Node.self(),
nodes: to_nodes(options[:view]),
ttl: options[:ttl] || Config.active_random_walk_length()
}
end
@doc """
Send a `list` in a Shuffle request to a random neighbor of its active view.
list contains;
- p’s own identifier
- Ka nodes from its active view
- Kp nodes from its passive view
:ok = Shuffle.send!(shuffle, view)
"""
@spec send!(View.t()) :: :ok
def send!(%View{active: active} = view) do
active
|> Utils.choose_node()
|> PeerManager.send_message(new(view: view))
end
@doc """
if Shuffle.should_forward?(shuffle, view) do
:ok = Shuffle.forward!(shuffle, view)
else
:ok = ShuffleReply.new(passive: passive, shuffle: shuffle)
|> ShuffleReply.send!(shuffle.sender)
end
"""
@spec should_forward?(t(), View.t()) :: boolean()
def should_forward?(%Shuffle{ttl: 0}, _view), do: false
def should_forward?(%Shuffle{}, %View{} = view) do
view.active
|> MapSet.size()
|> Kernel.>(1)
end
@doc """
%View{} = Shuffle.handle(shuffle, state.view)
"""
@spec handle(t(), View.t()) :: View.t()
def handle(shuffle, view0) do
if should_forward?(shuffle, view0),
do: forward!(shuffle, view0),
else: send_shuffle_reply(view0, shuffle)
end
@doc """
If the time to live of the message is greater than zero and the number of nodes
in q’s active view is greater than 1, the node will select a random node
from its active view, different from the one he received this shuffle message from,
and simply forwards the Shuffle request.
"""
@spec forward!(t(), View.t()) :: View.t()
def forward!(%Shuffle{} = shuffle, %View{} = view) do
:ok =
view.active
|> exclude_sender_and_origin(shuffle)
|> Utils.choose_node()
|> PeerManager.send_message(%{shuffle | ttl: shuffle.ttl - 1, sender: Node.self()})
view
end
# private functions
@spec exclude_sender_and_origin(MapSet.t(), t()) :: MapSet.t(Node.t())
defp exclude_sender_and_origin(active, shuffle) do
active
|> Enum.filter(&without_sender_and_origin(&1, shuffle))
|> MapSet.new()
end
@spec without_sender_and_origin(Node.t(), t()) :: boolean()
defp without_sender_and_origin(node, %Shuffle{sender: sender, origin: origin}),
do: not (node in [sender, origin])
@spec to_nodes(View.t()) :: MapSet.t(Node.t())
defp to_nodes(%View{active: active, passive: passive}) do
active
|> MapSet.union(passive)
end
@spec send_shuffle_reply(View.t(), t()) :: View.t()
defp send_shuffle_reply(view0, shuffle) do
view = View.trim_and_add_to_passive(view0, shuffle.nodes)
[view: view, shuffle: shuffle]
|> ShuffleReply.new()
|> ShuffleReply.send!(shuffle.sender)
view
end
end