179 lines
4.8 KiB
Elixir
179 lines
4.8 KiB
Elixir
|
defmodule Paxos do
|
||
|
def getPaxosName(name) do
|
||
|
String.to_atom(Atom.to_string(name) <> "_paxos")
|
||
|
end
|
||
|
|
||
|
def getOriginalName(name) do
|
||
|
String.to_atom(String.replace(Atom.to_string(name), "_paxos", ""))
|
||
|
end
|
||
|
|
||
|
def start(name, processes) do
|
||
|
new_name = getPaxosName(name)
|
||
|
pid = spawn(Paxos, :init, [new_name, name, processes])
|
||
|
|
||
|
Utils.register_name(new_name, pid)
|
||
|
end
|
||
|
|
||
|
# Init event must be the first
|
||
|
# one after the component is created
|
||
|
def init(name, parent, processes) do
|
||
|
processes = Enum.map(processes, fn name -> getPaxosName(name) end)
|
||
|
EventualLeaderElector.start(name, processes)
|
||
|
EagerReliableBroadcast.start(name, processes)
|
||
|
|
||
|
state = %{
|
||
|
name: name,
|
||
|
parent: parent,
|
||
|
processes: processes,
|
||
|
leader: nil,
|
||
|
value: nil,
|
||
|
other_value: nil,
|
||
|
ballot: 0,
|
||
|
ballot_value: nil,
|
||
|
prepared_values: [],
|
||
|
accepted: 0,
|
||
|
running_ballot: 0,
|
||
|
accepted_ballot: nil,
|
||
|
accepted_value: nil,
|
||
|
decided: false,
|
||
|
pid_to_inform: nil
|
||
|
}
|
||
|
|
||
|
run(state)
|
||
|
end
|
||
|
|
||
|
def run(state) do
|
||
|
run(
|
||
|
receive do
|
||
|
{:ele_trust, proc} ->
|
||
|
prepare(%{state | leader: proc})
|
||
|
|
||
|
{:propose, value, pid_to_inform} ->
|
||
|
if state.value == nil do
|
||
|
EagerReliableBroadcast.broadcast(state.name, {:other_propose, value})
|
||
|
prepare(%{state | value: value, pid_to_inform: pid_to_inform})
|
||
|
else
|
||
|
%{state | pid_to_inform: pid_to_inform}
|
||
|
end
|
||
|
|
||
|
{:rb_deliver, _proc, {:other_propose, value}} ->
|
||
|
%{state | other_value: value}
|
||
|
|
||
|
{:rb_deliver, _proc, {:prepare, proc, ballot}} ->
|
||
|
if ballot > state.running_ballot do
|
||
|
Utils.unicast({:prepared, ballot, state.accepted_ballot, state.accepted_value}, proc)
|
||
|
%{state | running_ballot: ballot}
|
||
|
else
|
||
|
Utils.unicast({:nack, ballot}, proc)
|
||
|
state
|
||
|
end
|
||
|
|
||
|
{:nack, ballot} ->
|
||
|
if state.leader == state.name and state.ballot == ballot do
|
||
|
prepare(state)
|
||
|
else
|
||
|
state
|
||
|
end
|
||
|
|
||
|
{:prepared, ballot, accepted_ballot, accepted_value} ->
|
||
|
if ballot == state.ballot do
|
||
|
prepared(%{
|
||
|
state
|
||
|
| prepared_values: state.prepared_values ++ [{accepted_ballot, accepted_value}]
|
||
|
})
|
||
|
else
|
||
|
state
|
||
|
end
|
||
|
|
||
|
{:rb_deliver, proc, {:accept, ballot, value}} ->
|
||
|
if ballot >= state.running_ballot do
|
||
|
Utils.unicast({:accepted, ballot}, proc)
|
||
|
%{state | running_ballot: ballot, accepted_value: value, accepted_ballot: ballot}
|
||
|
else
|
||
|
Utils.unicast({:nack, ballot}, proc)
|
||
|
state
|
||
|
end
|
||
|
|
||
|
{:accepted, ballot} ->
|
||
|
if state.leader == state.name and state.ballot == ballot do
|
||
|
accepted(%{state | accepted: state.accepted + 1})
|
||
|
else
|
||
|
state
|
||
|
end
|
||
|
|
||
|
{:rb_deliver, _, {:decide, value}} ->
|
||
|
if state.decided == true do
|
||
|
state
|
||
|
else
|
||
|
if state.pid_to_inform != nil do
|
||
|
send(state.pid_to_inform, {:decide, value})
|
||
|
end
|
||
|
|
||
|
%{state | decided: true}
|
||
|
end
|
||
|
end
|
||
|
)
|
||
|
end
|
||
|
|
||
|
#
|
||
|
# Puts process in the preapre state
|
||
|
#
|
||
|
|
||
|
def prepare(state) when state.leader != state.name, do: state
|
||
|
def prepare(state) when state.value == nil and state.other_value == nil, do: state
|
||
|
|
||
|
def prepare(state) do
|
||
|
ballot = state.ballot + 1
|
||
|
EagerReliableBroadcast.broadcast(state.name, {:prepare, state.name, ballot})
|
||
|
%{state | ballot: ballot, prepared_values: [], accepted: 0, ballot_value: nil}
|
||
|
end
|
||
|
|
||
|
#
|
||
|
# Process the prepared responses
|
||
|
#
|
||
|
def prepared(state) when state.leader != state.name, do: state
|
||
|
|
||
|
def prepared(state) when length(state.prepared_values) > length(state.processes) / 2 + 1 do
|
||
|
{_, a_val} =
|
||
|
Enum.reduce(state.prepared_values, {0, nil}, fn {bal, val}, {a_bal, a_val} ->
|
||
|
if a_bal > bal do
|
||
|
{a_bal, a_val}
|
||
|
else
|
||
|
{bal, val}
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
a_val =
|
||
|
if a_val == nil do
|
||
|
if state.value == nil do
|
||
|
state.other_value
|
||
|
else
|
||
|
state.value
|
||
|
end
|
||
|
else
|
||
|
a_val
|
||
|
end
|
||
|
|
||
|
EagerReliableBroadcast.broadcast(state.name, {:accept, state.ballot, a_val})
|
||
|
%{state | ballot_value: a_val}
|
||
|
end
|
||
|
|
||
|
def prepared(state), do: state
|
||
|
|
||
|
#
|
||
|
# Process the accepted responses
|
||
|
#
|
||
|
def accepted(state) when state.leader != state.name, do: state
|
||
|
|
||
|
def accepted(state) when state.accepted > length(state.processes) / 2 + 1 do
|
||
|
EagerReliableBroadcast.broadcast(state.name, {:decide, state.ballot_value})
|
||
|
state
|
||
|
end
|
||
|
|
||
|
def accepted(state), do: state
|
||
|
|
||
|
def propose(name, value) do
|
||
|
Utils.unicast({:propose, value}, getPaxosName(name))
|
||
|
end
|
||
|
end
|