From 0e1e13950d0dd8ec73e9f0f7bd470dea6110f43d Mon Sep 17 00:00:00 2001 From: Andre Henriques Date: Tue, 16 Jan 2024 00:04:32 +0000 Subject: [PATCH] simplified paxos added more tests --- lib/paxos.ex | 101 +++++++++++++++++----------------- lib/paxos_test_aditional.ex | 54 +++++++++++++++++++ lib/test_script.exs | 104 +++++++++++++++++++----------------- 3 files changed, 157 insertions(+), 102 deletions(-) diff --git a/lib/paxos.ex b/lib/paxos.ex index 424f46b..165f9f7 100644 --- a/lib/paxos.ex +++ b/lib/paxos.ex @@ -1,51 +1,3 @@ - - -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 @@ -76,7 +28,6 @@ defmodule Paxos do processes: processes, leader: nil, instmap: %{}, - other_values: %{}, decided: %{} } @@ -90,6 +41,7 @@ defmodule Paxos do instmap = Map.put(state.instmap, inst, %{ value: value, + other_value: nil, ballot: Ballot.init(state.name, 0), aborted: false, ballot_value: nil, @@ -172,8 +124,6 @@ defmodule Paxos do end {:rb_deliver, proc, {:other_propose, inst, value}} -> - state = %{state | other_values: Map.put(state.other_values, inst, value)} - cond do has_finished(state, inst, true) -> EagerReliableBroadcast.broadcast( @@ -184,6 +134,7 @@ defmodule Paxos do true -> state = has_or_create(state, inst) + state = set_instmap(state, inst, fn map -> %{map | other_value: value} end) prepare(state, inst) end @@ -399,7 +350,7 @@ defmodule Paxos do state.instmap[inst] == nil -> state - state.instmap[inst].value == nil and state.other_values[inst] == nil -> + state.instmap[inst].value == nil and state.instmap[inst].other_value == nil -> state state.instmap[inst] != nil and state.instmap[inst].has_sent_prepare -> @@ -449,7 +400,7 @@ defmodule Paxos do a_val = if a_val == nil do if state.instmap[inst].value == nil do - state.other_values[inst] + state.instmap[inst].other_value else state.instmap[inst].value end @@ -556,3 +507,47 @@ defmodule Paxos do end end +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 diff --git a/lib/paxos_test_aditional.ex b/lib/paxos_test_aditional.ex index fa57f76..86c93e7 100644 --- a/lib/paxos_test_aditional.ex +++ b/lib/paxos_test_aditional.ex @@ -59,6 +59,60 @@ defmodule PaxosTestAditional do end end + # Leader crashes, no concurrent ballots + def run_non_leader_send_propose_after_leader_elected(name, participants, val) do + {cpid, pid} = PaxosTest.init(name, participants, true) + send(cpid, :ready) + + {status, val, a, spare} = + try do + receive do + :start -> + IO.puts("#{inspect(name)}: started") + + [leader | spare] = Enum.sort(participants) + [new_leader | _] = spare + + if name == new_leader do + # Wait a bit for the leader to be elected + Process.sleep(5000) + Paxos.propose(pid, 1, val, 10000) + end + + if name in spare do + {status, val} = PaxosTest.wait_for_decision(pid, 1, 15000) + + if status != :none, + do: IO.puts("#{name}: decided #{inspect(val)}"), + else: IO.puts("#{name}: No decision after 10 seconds") + + {status, val, 10, spare} + else + {:killed, :none, -1, spare} + end + end + rescue + _ -> {:none, :none, 10, []} + end + + send(cpid, :done) + + receive do + :all_done -> + Process.sleep(100) + + ql = + if name in spare do + IO.puts("#{name}: #{inspect(ql = Process.info(pid, :message_queue_len))}") + ql + else + {:message_queue_len, -1} + end + + PaxosTest.kill_paxos(pid, name) + send(cpid, {:finished, name, pid, status, val, a, ql}) + end + end def run_leader_should_nack_simple(name, participants, val) do {cpid, pid} = PaxosTest.init(name, participants, true) diff --git a/lib/test_script.exs b/lib/test_script.exs index 96a789f..6ae32ae 100755 --- a/lib/test_script.exs +++ b/lib/test_script.exs @@ -22,57 +22,63 @@ 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_non_leader_send_propose_after_leader_elected/3, TestUtil.get_dist_config(host, 5), 10, + "Non-Leader proposes after leader is elected, 5 nodes"}, + {&PaxosTestAditional.run_non_leader_send_propose_after_leader_elected/3, TestUtil.get_local_config(5), 10, + "Non-Leader proposes after leader is elected, 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"},