Packages
Chatter is extracted from the ScaleSmall project as a standalone piece. This may be used independently to broadcast messages to a set of nodes. It uses a mixture of UDP multicast and TCP to deliver messages and tries to minimize network traffic while doing so.
Current section
Files
Jump to
Current section
Files
lib/gossip.ex
defmodule Chatter.Gossip do
require Record
require Chatter.BroadcastID
require Chatter.NetID
alias Chatter.BroadcastID
alias Chatter.NetID
Record.defrecord :gossip,
current_id: nil,
seen_ids: [],
other_ids: [],
distribution_list: [],
payload: nil
@type t :: record( :gossip,
current_id: BroadcastID.t,
seen_ids: list(BroadcastID.t),
other_ids: list(NetID.t),
distribution_list: list(NetID.t),
payload: tuple )
@spec new(NetID.t, tuple) :: t
def new(my_id, data)
when NetID.is_valid(my_id) and
is_tuple(data) and
tuple_size(data) > 1
do
gossip(current_id: BroadcastID.new(my_id)) |> gossip(payload: data)
end
@spec new(NetID.t, integer, tuple) :: t
def new(my_id, seqno, data)
when NetID.is_valid(my_id) and
is_integer(seqno) and
seqno >= 0 and
is_tuple(data) and
tuple_size(data) > 1
do
gossip(current_id: BroadcastID.new(my_id) |> BroadcastID.seqno(seqno))
|> gossip(payload: data)
end
defmacro is_valid(data) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) == 6 and
:erlang.element(1, unquote(data)) == :gossip and
# broadcast id
BroadcastID.is_valid(:erlang.element(2, unquote(data))) and
# seen ids
is_list(:erlang.element(3, unquote(data))) and
# other ids
is_list(:erlang.element(4, unquote(data))) and
# distribution list
is_list(:erlang.element(5, unquote(data))) and
# payload
is_tuple(:erlang.element(6, unquote(data))) and
tuple_size(:erlang.element(6, unquote(data))) > 1
end
false ->
quote bind_quoted: binding() do
is_tuple(data) and tuple_size(data) == 6 and
:erlang.element(1, data) == :gossip and
# broadcast id
BroadcastID.is_valid(:erlang.element(2, data)) and
# seen ids
is_list(:erlang.element(3, data)) and
# other ids
is_list(:erlang.element(4, data)) and
# distribution list
is_list(:erlang.element(5, data)) and
# payload
is_tuple(:erlang.element(6, data)) and
tuple_size(:erlang.element(6, data)) > 1
end
end
end
defmacro is_valid_relaxed(data) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) == 6 and
:erlang.element(1, unquote(data)) == :gossip and
# broadcast id
BroadcastID.is_valid(:erlang.element(2, unquote(data))) and
# seen ids
is_list(:erlang.element(3, unquote(data))) and
# other ids
is_list(:erlang.element(4, unquote(data))) and
# distribution list
is_list(:erlang.element(5, unquote(data)))
end
false ->
quote bind_quoted: binding() do
is_tuple(data) and tuple_size(data) == 6 and
:erlang.element(1, data) == :gossip and
# broadcast id
BroadcastID.is_valid(:erlang.element(2, data)) and
# seen ids
is_list(:erlang.element(3, data)) and
# other ids
is_list(:erlang.element(4, data)) and
# distribution list
is_list(:erlang.element(5, data))
end
end
end
@spec valid?(t) :: boolean
def valid?(data)
when is_valid(data)
do
true
end
def valid?(_), do: false
@spec current_id(t) :: BroadcastID.t
def current_id(g)
when is_valid(g)
do
gossip(g, :current_id)
end
@spec seen_ids(t, list(BroadcastID.t)) :: t
def seen_ids(g, ids)
when is_valid(g) and
is_list(ids)
do
:ok = BroadcastID.validate_list!(ids)
gossip(g, seen_ids: ids)
end
@spec seen_ids(t) :: list(BroadcastID.t)
def seen_ids(g)
when is_valid(g)
do
gossip(g, :seen_ids)
end
@spec other_ids(t, list(NetID.t)) :: t
def other_ids(g, ids)
when is_valid(g) and
is_list(ids)
do
:ok = NetID.validate_list!(ids)
gossip(g, other_ids: ids)
end
@spec other_ids(t) :: list(NetID.t)
def other_ids(g)
when is_valid(g)
do
gossip(g, :other_ids)
end
@spec payload(t) :: tuple
def payload(g)
when is_valid(g)
do
gossip(g, :payload)
end
@spec payload(t, tuple) :: t
def payload(g, pl)
when is_valid(g) and
is_tuple(pl) and
tuple_size(pl) > 1
do
gossip(g, payload: pl)
end
@spec payload_relaxed(t, tuple) :: t
def payload_relaxed(g, pl)
when is_valid_relaxed(g) and
is_tuple(pl) and
tuple_size(pl) > 1
do
gossip(g, payload: pl)
end
@spec seen_netids(t) :: list(NetID.t)
def seen_netids(g)
when is_valid(g)
do
Enum.reduce(gossip(g, :seen_ids), [], fn(x, acc) ->
[Chatter.BroadcastID.origin(x)|acc]
end)
end
@spec distribution_list(t, list(NetID.t)) :: t
def distribution_list(g, ids)
when is_valid(g) and
is_list(ids)
do
:ok = NetID.validate_list!(ids)
gossip(g, distribution_list: ids)
end
@spec distribution_list(t) :: list(NetID.t)
def distribution_list(g)
when is_valid(g)
do
gossip(g, :distribution_list)
end
@spec remove_from_distribution_list(t, list(NetID.t)) :: t
def remove_from_distribution_list(g, [])
when is_valid(g)
do
g
end
def remove_from_distribution_list(g, to_remove)
when is_valid(g)
do
:ok = NetID.validate_list!(to_remove)
old_list = gossip(g, :distribution_list)
old_set = Enum.into(old_list, HashSet.new)
remove_set = Enum.into(to_remove, HashSet.new)
new_set = HashSet.difference(old_set, remove_set)
gossip(g, distribution_list: HashSet.to_list(new_set))
end
@spec add_to_distribution_list(t, list(NetID.t)) :: t
def add_to_distribution_list(g, [])
when is_valid(g)
do
g
end
def add_to_distribution_list(g, to_add)
when is_valid(g)
do
:ok = NetID.validate_list!(to_add)
old_list = gossip(g, :distribution_list)
old_set = Enum.into(old_list, HashSet.new)
add_set = Enum.into(to_add, HashSet.new)
new_set = HashSet.union(old_set, add_set)
gossip(g, distribution_list: HashSet.to_list(new_set))
end
@spec extract_netids(t) :: list(NetID.t)
def extract_netids(g)
when is_valid(g)
do
[gossip(g, :current_id) |> BroadcastID.origin | gossip(g, :distribution_list)] ++
Enum.map(gossip(g, :seen_ids), fn(x) -> BroadcastID.origin(x) end) ++
gossip(g, :other_ids)
|> Enum.uniq
end
@spec encode_with(t, map) :: binary
def encode_with(g, id_map)
when is_valid(g) and
is_map(id_map) # TODO: check map too ...
do
bin_current_id = gossip(g, :current_id) |> BroadcastID.encode_with(id_map)
bin_seen_ids = gossip(g, :seen_ids) |> BroadcastID.encode_list_with(id_map)
bin_other_ids = gossip(g, :other_ids) |> NetID.encode_list_with(id_map)
bin_distrib = gossip(g, :distribution_list) |> NetID.encode_list_with(id_map)
<< bin_current_id :: binary,
bin_seen_ids :: binary,
bin_other_ids :: binary,
bin_distrib :: binary >>
end
@spec decode_with(binary, map) :: {t, binary}
def decode_with(bin, id_map)
when is_binary(bin) and
byte_size(bin) > 0 and
is_map(id_map)
do
{decoded_current_id, remaining} = BroadcastID.decode_with(bin, id_map)
{decoded_seen_ids, remaining} = BroadcastID.decode_list_with(remaining, id_map)
{decoded_other_ids, remaining} = NetID.decode_list_with(remaining, id_map)
{decoded_distrib, remaining} = NetID.decode_list_with(remaining, id_map)
{ gossip([current_id: decoded_current_id,
seen_ids: decoded_seen_ids,
other_ids: decoded_other_ids,
distribution_list: decoded_distrib,
payload: :empty]),
remaining }
end
end