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