105 lines
2.8 KiB
Elixir
105 lines
2.8 KiB
Elixir
|
defmodule Phoenix.Tracker.Clock do
|
||
|
@moduledoc false
|
||
|
alias Phoenix.Tracker.State
|
||
|
|
||
|
@type context :: State.context
|
||
|
@type clock :: {State.name, context}
|
||
|
|
||
|
@doc """
|
||
|
Returns a list of replicas from a list of contexts.
|
||
|
"""
|
||
|
@spec clockset_replicas([clock]) :: [State.name]
|
||
|
def clockset_replicas(clockset) do
|
||
|
for {replica, _} <- clockset, do: replica
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Adds a replicas context to a clockset, keeping only dominate contexts.
|
||
|
"""
|
||
|
@spec append_clock([clock], clock) :: [clock]
|
||
|
def append_clock(clockset, {_, clock}) when map_size(clock) == 0, do: clockset
|
||
|
def append_clock(clockset, {node, clock}) do
|
||
|
big_clock = combine_clocks(clockset)
|
||
|
cond do
|
||
|
dominates?(clock, big_clock) -> [{node, clock}]
|
||
|
dominates?(big_clock, clock) -> clockset
|
||
|
true -> filter_clocks(clockset, {node, clock})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Checks if one clock causally dominates the other for all replicas.
|
||
|
"""
|
||
|
@spec dominates?(context, context) :: boolean
|
||
|
def dominates?(c1, c2) when map_size(c1) < map_size(c2), do: false
|
||
|
def dominates?(c1, c2) do
|
||
|
Enum.reduce_while(c2, true, fn {replica, clock}, true ->
|
||
|
if Map.get(c1, replica, 0) >= clock do
|
||
|
{:cont, true}
|
||
|
else
|
||
|
{:halt, false}
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Checks if one clock causally dominates the other for their shared replicas.
|
||
|
"""
|
||
|
def dominates_or_equal?(c1, c2) when c1 == %{} and c2 == %{}, do: true
|
||
|
def dominates_or_equal?(c1, _c2) when c1 == %{}, do: false
|
||
|
def dominates_or_equal?(c1, c2) do
|
||
|
Enum.reduce_while(c1, true, fn {replica, clock}, true ->
|
||
|
if clock >= Map.get(c2, replica, 0) do
|
||
|
{:cont, true}
|
||
|
else
|
||
|
{:halt, false}
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Returns the upper bound causal context of two clocks.
|
||
|
"""
|
||
|
def upperbound(c1, c2) do
|
||
|
Map.merge(c1, c2, fn _, v1, v2 -> max(v1, v2) end)
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Returns the lower bound causal context of two clocks.
|
||
|
"""
|
||
|
def lowerbound(c1, c2) do
|
||
|
Map.merge(c1, c2, fn _, v1, v2 -> min(v1, v2) end)
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Returns the clock with just provided replicas.
|
||
|
"""
|
||
|
def filter_replicas(c, replicas), do: Map.take(c, replicas)
|
||
|
|
||
|
@doc """
|
||
|
Returns replicas from the given clock.
|
||
|
"""
|
||
|
def replicas(c), do: Map.keys(c)
|
||
|
|
||
|
defp filter_clocks(clockset, {node, clock}) do
|
||
|
clockset
|
||
|
|> Enum.reduce({[], false}, fn {node2, clock2}, {set, insert} ->
|
||
|
if dominates?(clock, clock2) do
|
||
|
{set, true}
|
||
|
else
|
||
|
{[{node2, clock2}| set], insert || !dominates?(clock2, clock)}
|
||
|
end
|
||
|
end)
|
||
|
|> case do
|
||
|
{new_clockset, true} -> [{node, clock} | new_clockset]
|
||
|
{new_clockset, false} -> new_clockset
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp combine_clocks(clockset) do
|
||
|
clockset
|
||
|
|> Enum.map(fn {_, clocks} -> clocks end)
|
||
|
|> Enum.reduce(%{}, &upperbound(&1, &2))
|
||
|
end
|
||
|
end
|