diff --git a/lib/paxos.ex b/lib/paxos.ex index b0fba5d..424f46b 100644 --- a/lib/paxos.ex +++ b/lib/paxos.ex @@ -1,3 +1,51 @@ + + +defmodule Ballot do + + def init(name, number \\ 0) do + {name, number} + end + + def inc(b, name \\ nil) do + {old_name, number} = b + + { + if name == nil do + old_name + else + name + end, + + number + 1 + } + end + + + defp lexicographical_compare(a, b) do + cond do + a == b -> 0 + a > b -> 1 + true -> -1 + end + end + + + defp diff({name1, number1}, {name2, number2}) do + diff = number1 - number2 + if diff == 0 do + lexicographical_compare(name1, name2) + else + diff + end + end + + def compare(b1, operator, b2) do + operator.(diff(b1, b2), 0) + end + +end + + # # # Possible actions @@ -42,7 +90,8 @@ defmodule Paxos do instmap = Map.put(state.instmap, inst, %{ value: value, - ballot: 0, + ballot: Ballot.init(state.name, 0), + aborted: false, ballot_value: nil, prepared_values: [], accepted: 0, @@ -58,8 +107,13 @@ defmodule Paxos do end end - def has_finished(state, inst) do - Map.has_key?(state.decided, inst) + def has_finished(state, inst, ignore_aborted \\ false) do + cond do + Map.has_key?(state.decided, inst) -> true + ignore_aborted -> false + Map.has_key?(state.instmap, inst) -> state.instmap[inst].aborted + true -> false + end end def run(state) do @@ -77,7 +131,7 @@ defmodule Paxos do IO.puts("#{state.name} - Propose #{inspect(inst)} with action #{inspect(action)}") cond do - has_finished(state, inst) -> + has_finished(state, inst, true) -> IO.puts("#{state.name} - Has already decided for #{inspect(inst)} sending #{inspect(state.decided[inst])}") send(pid_to_inform, {:decision, inst, state.decided[inst]}) state @@ -90,7 +144,7 @@ defmodule Paxos do # Inform the pid with timeout right way send(pid_to_inform, {:timeout, inst}); - set_instmap(state, inst, fn map -> %{map| ballot: map.ballot + 1} end) + set_instmap(state, inst, fn map -> %{map| ballot: Ballot.inc(map.ballot)} end) not Map.has_key?(state.instmap, inst) -> EagerReliableBroadcast.broadcast(state.name, {:other_propose, inst, value}) @@ -121,7 +175,7 @@ defmodule Paxos do state = %{state | other_values: Map.put(state.other_values, inst, value)} cond do - has_finished(state, inst) -> + has_finished(state, inst, true) -> EagerReliableBroadcast.broadcast( state.name, {:decide, inst, state.decided[inst]} @@ -154,7 +208,7 @@ defmodule Paxos do | ballot: ballot } end) - ballot > state.instmap[inst].ballot -> + Ballot.compare(ballot, &>/2, state.instmap[inst].ballot) -> Utils.unicast( {:prepared, inst, ballot, state.instmap[inst].accepted_ballot, state.instmap[inst].accepted_value}, @@ -167,7 +221,7 @@ defmodule Paxos do } end) true -> - Utils.unicast({:nack, inst, ballot, state.instmap[inst].ballot}, proc) + Utils.unicast({:nack, inst, ballot}, proc) state end @@ -178,14 +232,14 @@ defmodule Paxos do state - {:nack, inst, ballot, new_ballot} -> - IO.puts("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)} #{inspect(new_ballot)}") + {:nack, inst, ballot} -> + IO.puts("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)}") cond do has_finished(state, inst) -> state - state.leader == state.name and state.instmap[inst].ballot == ballot -> + state.leader == state.name and Ballot.compare(state.instmap[inst].ballot, &==/2, ballot) -> if Map.has_key?(state.instmap, inst) and state.instmap[inst].pid_to_inform != nil do send(state.instmap[inst].pid_to_inform, {:abort, inst}) end @@ -194,8 +248,9 @@ defmodule Paxos do set_instmap(state, inst, fn map -> %{ map | has_sent_accept: false, - ballot: new_ballot + 1, has_sent_prepare: false, + ballot: Ballot.inc(map.ballot), + aborted: true, } end) true -> @@ -227,7 +282,7 @@ defmodule Paxos do has_finished(state, inst) -> state - ballot == state.instmap[inst].ballot -> + Ballot.compare(ballot, &==/2, state.instmap[inst].ballot) -> state = set_instmap(state, inst, fn map -> %{ map @@ -236,7 +291,7 @@ defmodule Paxos do prepared(state, inst) - ballot > state.instmap[inst].ballot -> + Ballot.compare(ballot, &>/2, state.instmap[inst].ballot) -> IO.puts("#{state.name} - Probably recieved this before preare came to self sending with delay") Process.send_after(self(), {:prepared, inst, ballot, accepted_ballot, accepted_value}, 100) state @@ -253,7 +308,7 @@ defmodule Paxos do true -> state = has_or_create(state, inst) - if ballot >= state.instmap[inst].ballot do + if Ballot.compare(ballot, &>=/2, state.instmap[inst].ballot) do IO.puts("#{state.name} - accept #{inspect(inst)} #{inspect(ballot)} #{inspect(value)}") Utils.unicast({:accepted, inst, ballot}, proc) @@ -266,7 +321,7 @@ defmodule Paxos do } end) else IO.puts("#{state.name} -> #{proc} nack") - Utils.unicast({:nack, inst, ballot, state.instmap[inst].ballot}, proc) + Utils.unicast({:nack, inst, ballot}, proc) state end end @@ -278,7 +333,7 @@ defmodule Paxos do has_finished(state, inst) -> state - state.leader == state.name and state.instmap[inst].ballot == ballot -> + state.leader == state.name and Ballot.compare(state.instmap[inst].ballot, &==/2, ballot) -> accepted( set_instmap(state, inst, fn map -> %{ map @@ -298,7 +353,7 @@ defmodule Paxos do t < 0 -> send(pid_to_inform, {:get_value_res, inst}) - has_finished(state, inst) -> + has_finished(state, inst, true) -> send(pid_to_inform, {:get_value_res_actual, inst, state.decided[inst]}) true -> @@ -341,24 +396,28 @@ defmodule Paxos do def prepare(state, inst) do cond do - Map.get(state.instmap, inst) == nil and Map.get(state.other_values, inst) == nil -> + state.instmap[inst] == nil -> state - Map.get(state.instmap, inst) != nil and state.instmap[inst].has_sent_prepare -> + state.instmap[inst].value == nil and state.other_values[inst] == nil -> state - Map.get(state.instmap, inst) != nil and state.instmap[inst].has_sent_accept -> + state.instmap[inst] != nil and state.instmap[inst].has_sent_prepare -> + state + + state.instmap[inst] != nil and state.instmap[inst].has_sent_accept -> state true -> - ballot = state.instmap[inst].ballot + 1 - IO.puts("#{state.name} - sending all prepare #{inst} #{ballot}") + ballot = Ballot.inc(state.instmap[inst].ballot) + IO.puts("#{state.name} - sending all prepare #{inst} #{inspect(ballot)}") EagerReliableBroadcast.broadcast(state.name, {:prepare, state.name, inst, ballot}) set_instmap(state, inst, fn map -> %{ map | prepared_values: [], accepted: 0, + aborted: false, ballot_value: nil, has_sent_prepare: true, has_sent_accept: false @@ -375,12 +434,12 @@ defmodule Paxos do if length(state.instmap[inst].prepared_values) >= floor(length(state.processes) / 2) + 1 and not state.instmap[inst].has_sent_accept do {_, a_val} = - Enum.reduce(state.instmap[inst].prepared_values, {0, nil}, fn {bal, val}, + Enum.reduce(state.instmap[inst].prepared_values, {Ballot.init(state.name, 0), nil}, fn {bal, val}, {acc_bal, acc_val} -> cond do val == nil -> {acc_bal, acc_val} - acc_bal > bal -> + Ballot.compare(acc_bal, &>/2, bal) -> {acc_bal, acc_val} true -> {bal, val} @@ -496,3 +555,4 @@ defmodule Paxos do end end end + diff --git a/lib/paxos_test_aditional.ex b/lib/paxos_test_aditional.ex index 8c7c210..fa57f76 100644 --- a/lib/paxos_test_aditional.ex +++ b/lib/paxos_test_aditional.ex @@ -71,7 +71,7 @@ defmodule PaxosTestAditional do IO.puts("#{inspect(name)}: started") [leader | spare] = Enum.sort(participants) - increase_ballot = Enum.take(spare, floor(length(participants) / 2)) + increase_ballot = Enum.take(spare, floor(length(participants) / 2) + 1) if name == leader do # Propose when passed with :kill_before_decision will die right before a decision is selected @@ -145,7 +145,7 @@ defmodule PaxosTestAditional do participants = Enum.sort(participants) [leader | spare ] = participants - increase_ballot = Enum.take(spare, floor(length(participants) / 2)) + increase_ballot = Enum.take(spare, floor(length(participants) / 2) + 1) [non_leader | _] = Enum.reverse(spare) if name == non_leader do diff --git a/lib/test_script.exs b/lib/test_script.exs index 1002a82..96a789f 100755 --- a/lib/test_script.exs +++ b/lib/test_script.exs @@ -22,57 +22,57 @@ test_suite = [ # Use TestUtil.get_local_config(n) to generate a single-node configuration # consisting of n processes, all running on the same node. - {&PaxosTest.run_simple/3, TestUtil.get_local_config(3), 10, - "No failures, no concurrent ballots, 3 local procs"}, - {&PaxosTest.run_simple/3, TestUtil.get_dist_config(host, 3), 10, - "No failures, no concurrent ballots, 3 nodes"}, - {&PaxosTest.run_simple/3, TestUtil.get_local_config(5), 10, - "No failures, no concurrent ballots, 5 local procs"}, - {&PaxosTest.run_simple_2/3, TestUtil.get_dist_config(host, 3), 10, - "No failures, 2 concurrent ballots, 3 nodes"}, - {&PaxosTest.run_simple_2/3, TestUtil.get_local_config(3), 10, - "No failures, 2 concurrent ballots, 3 local procs"}, - {&PaxosTest.run_simple_3/3, TestUtil.get_local_config(3), 10, - "No failures, 2 concurrent instances, 3 local procs"}, - {&PaxosTest.run_simple_many_1/3, TestUtil.get_dist_config(host, 5), 10, - "No failures, many concurrent ballots 1, 5 nodes"}, - {&PaxosTest.run_simple_many_1/3, TestUtil.get_local_config(5), 10, - "No failures, many concurrent ballots 1, 5 local procs"}, - {&PaxosTest.run_simple_many_2/3, TestUtil.get_dist_config(host, 5), 10, - "No failures, many concurrent ballots 2, 5 nodes"}, - {&PaxosTest.run_simple_many_2/3, TestUtil.get_local_config(5), 10, - "No failures, many concurrent ballots 2, 5 local procs"}, - {&PaxosTest.run_non_leader_crash/3, TestUtil.get_dist_config(host, 3), 10, - "One non-leader crashes, no concurrent ballots, 3 nodes"}, - {&PaxosTest.run_non_leader_crash/3, TestUtil.get_local_config(3), 10, - "One non-leader crashes, no concurrent ballots, 3 local procs"}, - {&PaxosTest.run_minority_non_leader_crash/3, TestUtil.get_dist_config(host, 5), 10, - "Minority non-leader crashes, no concurrent ballots"}, - {&PaxosTest.run_minority_non_leader_crash/3, TestUtil.get_local_config(5), 10, - "Minority non-leader crashes, no concurrent ballots"}, - {&PaxosTest.run_leader_crash_simple/3, TestUtil.get_dist_config(host, 5), 10, - "Leader crashes, no concurrent ballots, 5 nodes"}, - {&PaxosTest.run_leader_crash_simple/3, TestUtil.get_local_config(5), 10, - "Leader crashes, no concurrent ballots, 5 local procs"}, - {&PaxosTest.run_leader_crash_simple_2/3, TestUtil.get_dist_config(host, 7), 10, - "Leader and some non-leaders crash, no concurrent ballots, 7 nodes"}, - {&PaxosTest.run_leader_crash_simple_2/3, TestUtil.get_local_config(7), 10, - "Leader and some non-leaders crash, no concurrent ballots, 7 local procs"}, - {&PaxosTest.run_leader_crash_complex/3, TestUtil.get_dist_config(host, 11), 10, - "Cascading failures of leaders and non-leaders, 11 nodes"}, - {&PaxosTest.run_leader_crash_complex/3, TestUtil.get_local_config(11), 10, - "Cascading failures of leaders and non-leaders, 11 local procs"}, - {&PaxosTest.run_leader_crash_complex_2/3, TestUtil.get_dist_config(host, 11), 10, - "Cascading failures of leaders and non-leaders, random delays, 7 nodes"}, - {&PaxosTest.run_leader_crash_complex_2/3, TestUtil.get_local_config(11), 10, - "Cascading failures of leaders and non-leaders, random delays, 7 local procs"}, + # {&PaxosTest.run_simple/3, TestUtil.get_local_config(3), 10, + # "No failures, no concurrent ballots, 3 local procs"}, + # {&PaxosTest.run_simple/3, TestUtil.get_dist_config(host, 3), 10, + # "No failures, no concurrent ballots, 3 nodes"}, + # {&PaxosTest.run_simple/3, TestUtil.get_local_config(5), 10, + # "No failures, no concurrent ballots, 5 local procs"}, + # {&PaxosTest.run_simple_2/3, TestUtil.get_dist_config(host, 3), 10, + # "No failures, 2 concurrent ballots, 3 nodes"}, + # {&PaxosTest.run_simple_2/3, TestUtil.get_local_config(3), 10, + # "No failures, 2 concurrent ballots, 3 local procs"}, + # {&PaxosTest.run_simple_3/3, TestUtil.get_local_config(3), 10, + # "No failures, 2 concurrent instances, 3 local procs"}, + # {&PaxosTest.run_simple_many_1/3, TestUtil.get_dist_config(host, 5), 10, + # "No failures, many concurrent ballots 1, 5 nodes"}, + # {&PaxosTest.run_simple_many_1/3, TestUtil.get_local_config(5), 10, + # "No failures, many concurrent ballots 1, 5 local procs"}, + # {&PaxosTest.run_simple_many_2/3, TestUtil.get_dist_config(host, 5), 10, + # "No failures, many concurrent ballots 2, 5 nodes"}, + # {&PaxosTest.run_simple_many_2/3, TestUtil.get_local_config(5), 10, + # "No failures, many concurrent ballots 2, 5 local procs"}, + # {&PaxosTest.run_non_leader_crash/3, TestUtil.get_dist_config(host, 3), 10, + # "One non-leader crashes, no concurrent ballots, 3 nodes"}, + # {&PaxosTest.run_non_leader_crash/3, TestUtil.get_local_config(3), 10, + # "One non-leader crashes, no concurrent ballots, 3 local procs"}, + # {&PaxosTest.run_minority_non_leader_crash/3, TestUtil.get_dist_config(host, 5), 10, + # "Minority non-leader crashes, no concurrent ballots"}, + # {&PaxosTest.run_minority_non_leader_crash/3, TestUtil.get_local_config(5), 10, + # "Minority non-leader crashes, no concurrent ballots"}, + # {&PaxosTest.run_leader_crash_simple/3, TestUtil.get_dist_config(host, 5), 10, + # "Leader crashes, no concurrent ballots, 5 nodes"}, + # {&PaxosTest.run_leader_crash_simple/3, TestUtil.get_local_config(5), 10, + # "Leader crashes, no concurrent ballots, 5 local procs"}, + # {&PaxosTest.run_leader_crash_simple_2/3, TestUtil.get_dist_config(host, 7), 10, + # "Leader and some non-leaders crash, no concurrent ballots, 7 nodes"}, + # {&PaxosTest.run_leader_crash_simple_2/3, TestUtil.get_local_config(7), 10, + # "Leader and some non-leaders crash, no concurrent ballots, 7 local procs"}, + # {&PaxosTest.run_leader_crash_complex/3, TestUtil.get_dist_config(host, 11), 10, + # "Cascading failures of leaders and non-leaders, 11 nodes"}, + # {&PaxosTest.run_leader_crash_complex/3, TestUtil.get_local_config(11), 10, + # "Cascading failures of leaders and non-leaders, 11 local procs"}, + # {&PaxosTest.run_leader_crash_complex_2/3, TestUtil.get_dist_config(host, 11), 10, + # "Cascading failures of leaders and non-leaders, random delays, 7 nodes"}, + # {&PaxosTest.run_leader_crash_complex_2/3, TestUtil.get_local_config(11), 10, + # "Cascading failures of leaders and non-leaders, random delays, 7 local procs"}, - # Aditional Test functions + # # Aditional Test functions - {&PaxosTestAditional.run_leader_crash_simple_before_decision/3, TestUtil.get_dist_config(host, 5), 10, - "Leader crashes right before decision, no concurrent ballots, 5 nodes"}, - {&PaxosTestAditional.run_leader_crash_simple_before_decision/3, TestUtil.get_local_config(5), 10, - "Leader crashes right before decision, no concurrent ballots, 5 local procs"}, + # {&PaxosTestAditional.run_leader_crash_simple_before_decision/3, TestUtil.get_dist_config(host, 5), 10, + # "Leader crashes right before decision, no concurrent ballots, 5 nodes"}, + # {&PaxosTestAditional.run_leader_crash_simple_before_decision/3, TestUtil.get_local_config(5), 10, + # "Leader crashes right before decision, no concurrent ballots, 5 local procs"}, {&PaxosTestAditional.run_leader_should_nack_simple/3, TestUtil.get_dist_config(host, 5), 10, "Leader should nack before decision and then come to decision, no concurrent ballots, 5 nodes"},