Lot of changes
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Andre Henriques 2024-01-16 19:59:01 +00:00
parent bf9d0cd34a
commit 33281da43f
3 changed files with 496 additions and 306 deletions

View File

@ -1,4 +1,8 @@
defmodule Utils do defmodule Utils do
@min_print_level 0
def safecast(p, m) when p == nil, do: IO.puts('Trying to safecast #{m} with p as nil') def safecast(p, m) when p == nil, do: IO.puts('Trying to safecast #{m} with p as nil')
def safecast(p, m) when is_pid(p), do: send(p, m) def safecast(p, m) when is_pid(p), do: send(p, m)
def safecast(p, m) do def safecast(p, m) do
@ -8,6 +12,10 @@ defmodule Utils do
end end
end end
def alter_name(name, part) do
String.to_atom(Atom.to_string(name) <> part)
end
def beb_broadcast(m, dest), do: for(p <- dest, do: safecast(p, m)) def beb_broadcast(m, dest), do: for(p <- dest, do: safecast(p, m))
def register_name(name, pid, link \\ true) do def register_name(name, pid, link \\ true) do
@ -26,6 +34,21 @@ defmodule Utils do
end end
end end
defmacro create_log(level) do
quote do
def log(msg) do
Utils._log(msg, unquote(level))
end
end
end
def _log(msg, level) do
if (@min_print_level < level) do
IO.puts(msg)
end
end
defmacro or_state(val, do: expr) do defmacro or_state(val, do: expr) do
quote do quote do
case unquote(val) do case unquote(val) do
@ -34,6 +57,17 @@ defmodule Utils do
end end
end end
end end
defmacro runfn(do: expr) do
quote do
def run(s) do
var!(state) = s
run(receive do
unquote(expr)
end)
end
end
end
end end
# #
@ -47,6 +81,8 @@ defmodule Paxos do
require Utils require Utils
import Utils import Utils
create_log 0
defmacro set_instmap(do: expr) do defmacro set_instmap(do: expr) do
quote do quote do
var!(map) = var!(state).instmap[var!(inst)] var!(map) = var!(state).instmap[var!(inst)]
@ -57,22 +93,21 @@ defmodule Paxos do
# Starts the Paxos replica with a specific name and some processes # Starts the Paxos replica with a specific name and some processes
def start(name, processes) do def start(name, processes, link \\ false) do
IO.puts("Starting paxos for #{name}") log("Starting paxos for #{name}")
pid = spawn(Paxos, :init, [name, name, processes]) pid = spawn(Paxos, :init, [name, processes])
register_name(name, pid, false) register_name(name, pid, link)
end end
# Init event must be the first # Init event must be the first
# one after the component is created # one after the component is created
def init(name, parent, processes) do def init(name, processes) do
EventualLeaderElector.start(name, processes) EventualLeaderElector.start(name, processes)
EagerReliableBroadcast.start(name, processes) EagerReliableBroadcast.start(name, processes)
state = %{ state = %{
name: name, name: name,
parent: parent,
processes: processes, processes: processes,
leader: nil, leader: nil,
instmap: %{}, instmap: %{},
@ -115,27 +150,25 @@ defmodule Paxos do
end end
end end
def run(state) do runfn do
run(
receive do
{:ele_trust, proc} -> {:ele_trust, proc} ->
IO.puts("#{state.name} - #{proc} is leader") log("#{state.name} - #{proc} is leader")
Enum.reduce(Map.keys(state.instmap), %{state | leader: proc}, fn inst, st -> Enum.reduce(Map.keys(state.instmap), %{state | leader: proc}, fn inst, st ->
prepare(st, inst) prepare(st, inst)
end) end)
{:propose, inst, value, pid_to_inform, action} -> {:propose, inst, value, pid_to_inform, action} ->
IO.puts("#{state.name} - Propose #{inspect(inst)} with action #{inspect(action)}") log("#{state.name} - Propose #{inspect(inst)} with action #{inspect(action)}")
cond do cond do
has_finished(state, inst, true) -> has_finished(state, inst, true) ->
IO.puts("#{state.name} - Has already decided for #{inspect(inst)} sending #{inspect(state.decided[inst])}") log("#{state.name} - Has already decided for #{inspect(inst)} sending #{inspect(state.decided[inst])}")
send(pid_to_inform, {:decision, inst, state.decided[inst]}) send(pid_to_inform, {:decision, inst, state.decided[inst]})
state state
action == :increase_ballot_number -> action == :increase_ballot_number ->
IO.puts("#{state.name} - Got request to increase ballot number for inst #{inst}") log("#{state.name} - Got request to increase ballot number for inst #{inst}")
state = has_or_create(state, inst) state = has_or_create(state, inst)
set_instmap do set_instmap do
@ -183,7 +216,7 @@ defmodule Paxos do
end end
{:rb_deliver, proc, {:prepare, proc, inst, ballot}} -> {:rb_deliver, proc, {:prepare, proc, inst, ballot}} ->
IO.puts("#{state.name} - prepare from #{proc}") log("#{state.name} - prepare from #{proc}")
cond do cond do
has_finished(state, inst) -> has_finished(state, inst) ->
@ -218,7 +251,7 @@ defmodule Paxos do
end end
{:nack, inst, ballot} -> {:nack, inst, ballot} ->
IO.puts("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)}") log("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)}")
cond do cond do
has_finished(state, inst) -> has_finished(state, inst) ->
@ -249,7 +282,7 @@ defmodule Paxos do
state state
true -> true ->
IO.puts("#{state.name} - got information to send abort") log("#{state.name} - got information to send abort")
if Map.has_key?(state.instmap, inst) and state.instmap[inst].pid_to_inform != nil do 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}) send(state.instmap[inst].pid_to_inform, {:abort, inst})
@ -259,7 +292,7 @@ defmodule Paxos do
end end
{:prepared, inst, ballot, accepted_ballot, accepted_value} -> {:prepared, inst, ballot, accepted_ballot, accepted_value} ->
IO.puts( log(
"#{state.name} - prepared #{inspect(inst)} #{inspect(ballot)} #{inspect(accepted_ballot)} #{inspect(accepted_value)}" "#{state.name} - prepared #{inspect(inst)} #{inspect(ballot)} #{inspect(accepted_ballot)} #{inspect(accepted_value)}"
) )
@ -277,7 +310,7 @@ defmodule Paxos do
prepared(state, inst) prepared(state, inst)
Ballot.compare(ballot, &>/2, 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") log("#{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) Process.send_after(self(), {:prepared, inst, ballot, accepted_ballot, accepted_value}, 100)
state state
@ -294,7 +327,7 @@ defmodule Paxos do
state = has_or_create(state, inst) state = has_or_create(state, inst)
if Ballot.compare(ballot, &>=/2, 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)}") log("#{state.name} - accept #{inspect(inst)} #{inspect(ballot)} #{inspect(value)}")
safecast(proc, {:accepted, inst, ballot}) safecast(proc, {:accepted, inst, ballot})
@ -306,14 +339,14 @@ defmodule Paxos do
} }
end end
else else
IO.puts("#{state.name} -> #{proc} nack") log("#{state.name} -> #{proc} nack")
safecast(proc, {:nack, inst, ballot}) safecast(proc, {:nack, inst, ballot})
state state
end end
end end
{:accepted, inst, ballot} -> {:accepted, inst, ballot} ->
IO.puts("#{state.name} - accepted #{inspect(inst)} #{inspect(ballot)}") log("#{state.name} - accepted #{inspect(inst)} #{inspect(ballot)}")
cond do cond do
has_finished(state, inst) -> has_finished(state, inst) ->
@ -333,19 +366,19 @@ defmodule Paxos do
end end
{:get_value, inst, pid_to_inform} -> {:get_value, inst, pid_to_inform} ->
# IO.puts("#{state.name} get_value") # log("#{state.name} get_value")
if has_finished(state, inst, true) do if has_finished(state, inst, true) do
send(pid_to_inform, {:get_value_res, inst, state.decided[inst]}) safecast(pid_to_inform, {:get_value_res, inst, state.decided[inst]})
end end
state state
{:rb_deliver, _, {:decide, inst, value}} -> {:rb_deliver, _, {:decide, inst, value}} ->
IO.puts("#{state.name} - decided #{inspect(inst)} #{inspect(value)}") log("#{state.name} - decided #{inspect(inst)} #{inspect(value)}")
or_state not has_finished(state, inst) do or_state not has_finished(state, inst) do
if Map.has_key?(state.instmap, inst) != nil and if Map.has_key?(state.instmap, inst) != nil and
state.instmap[inst].pid_to_inform != nil do state.instmap[inst].pid_to_inform != nil do
send(state.instmap[inst].pid_to_inform, {:decision, inst, value}) safecast(state.instmap[inst].pid_to_inform, {:decision, inst, value})
end end
%{ state | %{ state |
@ -354,8 +387,6 @@ defmodule Paxos do
} }
end end
end end
)
end
# #
# Puts process in the preapre state # Puts process in the preapre state
@ -379,7 +410,7 @@ defmodule Paxos do
true -> true ->
ballot = Ballot.inc(state.instmap[inst].ballot) ballot = Ballot.inc(state.instmap[inst].ballot)
IO.puts("#{state.name} - sending all prepare #{inst} #{inspect(ballot)}") log("#{state.name} - sending all prepare #{inst} #{inspect(ballot)}")
EagerReliableBroadcast.broadcast(state.name, {:prepare, state.name, inst, ballot}) EagerReliableBroadcast.broadcast(state.name, {:prepare, state.name, inst, ballot})
set_instmap do set_instmap do
@ -453,7 +484,7 @@ defmodule Paxos do
value = state.instmap[inst].ballot_value value = state.instmap[inst].ballot_value
if state.instmap[inst].action == :kill_before_decision do if state.instmap[inst].action == :kill_before_decision do
IO.puts("#{state.name} - Leader has action to die before decision #{inspect({:decide, inst, value})}") log("#{state.name} - Leader has action to die before decision #{inspect({:decide, inst, value})}")
Process.exit(self(), :kill) Process.exit(self(), :kill)
end end
@ -510,8 +541,8 @@ defmodule Paxos do
def get_decision_loop(input) do def get_decision_loop(input) do
{_, t} = input {_, t} = input
receive do receive do
{:get_value_res, inst} -> {:get_value_res, inst, v} ->
check_and_apply(inst, inst, input, &get_decision_loop/1) check_and_apply(v, inst, input, &get_decision_loop/1)
x -> x ->
Process.send_after(self(), x, 500) Process.send_after(self(), x, 500)
@ -568,26 +599,22 @@ defmodule EagerReliableBroadcast do
require Utils require Utils
import Utils import Utils
def get_rb_name(name) do
String.to_atom(Atom.to_string(name) <> "_rb")
end
def get_non_rb_name(name) do def get_non_rb_name(name) do
String.to_atom(String.replace(Atom.to_string(name), "_rb", "")) String.to_atom(String.replace(Atom.to_string(name), "_rb", ""))
end end
def start(name, processes) do def start(name, processes) do
pid = spawn(EagerReliableBroadcast, :init, [name, processes]) pid = spawn(EagerReliableBroadcast, :init, [name, processes])
register_name(get_rb_name(name), pid) register_name(alter_name(name, "_rb"), pid)
end end
# Init event must be the first # Init event must be the first
# one after the component is created # one after the component is created
def init(parent, processes) do def init(parent, processes) do
state = %{ state = %{
name: get_rb_name(parent), name: alter_name(parent, "_rb"),
parent: parent, parent: parent,
processes: Enum.map(processes, fn name -> get_rb_name(name) end), processes: Enum.map(processes, fn name -> alter_name(name, "_rb") end),
# Use this data structure to remember IDs of the delivered messages # Use this data structure to remember IDs of the delivered messages
delivered: %{}, delivered: %{},
# Use this variable to remember the last sequence number used to identify a message # Use this variable to remember the last sequence number used to identify a message
@ -597,9 +624,7 @@ defmodule EagerReliableBroadcast do
run(state) run(state)
end end
def run(state) do runfn do
run(
receive do
# Handle the broadcast request event # Handle the broadcast request event
{:broadcast, m} -> {:broadcast, m} ->
data_msg = {:data, state.name, state.seq_no, m} data_msg = {:data, state.name, state.seq_no, m}
@ -623,13 +648,11 @@ defmodule EagerReliableBroadcast do
end end
end end
end end
)
end
############# #############
# Interface # # Interface #
############# #############
def broadcast(name, m), do: safecast(get_rb_name(name), {:broadcast, m}) def broadcast(name, m), do: safecast(alter_name(name, "_rb"), {:broadcast, m})
end end
# #
@ -640,12 +663,8 @@ defmodule EventualLeaderElector do
require Utils require Utils
import Utils import Utils
def getEleName(name) do
String.to_atom(Atom.to_string(name) <> "_ele")
end
def start(name, processes) do def start(name, processes) do
new_name = getEleName(name) new_name = alter_name(name, "_ele")
pid = spawn(EventualLeaderElector, :init, [new_name, name, processes]) pid = spawn(EventualLeaderElector, :init, [new_name, name, processes])
register_name(new_name, pid) register_name(new_name, pid)
@ -654,7 +673,7 @@ defmodule EventualLeaderElector do
# Init event must be the first # Init event must be the first
# one after the component is created # one after the component is created
def init(name, parent, processes) do def init(name, parent, processes) do
processes = Enum.map(processes, fn name -> getEleName(name) end) processes = Enum.map(processes, fn name -> alter_name(name, "_ele") end)
state = %{ state = %{
name: name, name: name,
@ -677,9 +696,7 @@ defmodule EventualLeaderElector do
state state
end end
def run(state) do runfn do
run(
receive do
{:heartbeat_request, name, seq} -> {:heartbeat_request, name, seq} ->
safecast(name, {:heartbeat, state.parent, seq}) safecast(name, {:heartbeat, state.parent, seq})
state state
@ -701,6 +718,4 @@ defmodule EventualLeaderElector do
request_heartbeats(state) request_heartbeats(state)
end end
)
end
end end

161
lib/server.ex Normal file
View File

@ -0,0 +1,161 @@
defmodule ServerMacros do
def create_create_loop(name, do: match_exp, else: process_exp) do
function_name = :"#{name}_loop"
ast1 = quote do
{:timeout} -> :timeout
end
ast2 = quote do
value ->
Process.send_after(self(), value, 100)
unquote(function_name)(v)
end
ast3 = ast1 ++ match_exp ++ ast2
quote do
def unquote(function_name)(v) do
var!(v) = v
unquote(process_exp)
receive do
unquote(ast3)
end
end
end
end
def create_create_loop(name, do: exp) do
create_create_loop(name, do: exp, else: nil)
end
defmacro create_loop(name, clauses) do
create_create_loop(name, clauses)
end
defmacro try_propose(val, do: ready, else: recal_do) do
ast1 = quote do
{:timeout} -> unquote(recal_do)
{:abort} -> unquote(recal_do)
end
ast2 = quote do
{:decision, v} ->
var!(state) = apply_game(var!(state), v)
unquote(recal_do)
v ->
raise "Unknown message on try_propose #{inspect(v)}"
end
ast3 = ast1 ++ ready ++ ast2
quote do
v = Paxos.propose(var!(state).paxos, var!(state).instance, unquote(val), 1000)
case v do
unquote(ast3)
end
end
end
end
defmodule Server do
require ServerMacros
import ServerMacros
require Utils
import Utils
create_log 2
def start(name, participants) do
log("starting server")
pid = spawn(Server, :init, [name, participants])
register_name(name, pid, false)
end
def init(name, participants) do
paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end))
state = %{
name: name,
procs: participants,
games: %{},
paxos: paxos,
instance: 0,
}
run(state)
end
runfn do
{:start_game, participants, pid_to_inform} ->
{state, game_id} = try_to_create_game(state, participants)
safecast(pid_to_inform, {:start_game_ans, game_id})
state
end
def try_to_create_game(state, participants) do
game_ids = Map.keys(state.games)
new_game_id = Enum.at(Enum.sort(game_ids), length(game_ids) - 1) + 1
# TODO: randomize game state
new_game_state = [1, 1]
# TODO: randomize Initial hand value
hand = Enum.reduce(participants, %{}, fn p, acc -> Map.put(acc, p, 1) end)
try_propose {:start_game, new_game_id, participants, new_game_state, hand}
do
{:decision, {:start_game, ^new_game_id, ^participants, ^new_game_state, ^hand}} ->
state = apply_game(state, {:start_game, new_game_id, participants, new_game_state, hand})
{state, new_game_id}
else
try_to_create_game(state, participants)
end
end
#
# Apply Game States
#
def apply_game(state, {:start_game, game_id, participants, new_game_state, hand}) do
if state.name in participants do
%{state | games: Map.put(state.games, game_id, %{
game_state: new_game_state,
participants: participants,
hand: hand[state.name]
})}
else
%{state | games: Map.put(state.games, game_id, :not_playing_in_it)}
end
end
def apply_game(state, _), do: raise :do_not_know_how_to_apply_game_state
############
# Interface
############
create_loop :start_game do
{:start_game, game_id} ->
log("Started a game #{game_id}")
end
def start_game(name, participants) do
safecast(name, {:start_game, participants, self()})
start_game_loop(nil)
end
############
# Debug
############
def spinup(number_of_participants) do
procs = Enum.to_list(0..number_of_participants) |> Enum.map(fn n -> :"p#{n}" end)
Enum.map(procs, fn proc -> Server.start(proc, procs) end)
end
def kill (pids) do
pids |> Enum.map(fn m -> Process.exit(m, :kill) end)
end
end

View File

@ -1,9 +1,23 @@
defmodule Test do defmodule Test do
defmacro createfuncBase(name) do defmacro createfuncBase(name, do: do_exp, else: else_exp) do
quote do b1 = quote do
def unquote(name)(true), do: true false -> unquote(else_exp)
end end
b2 = b1 ++ do_exp
t = quote do
def test(v) do
case v do
unquote(b2)
end
end
end
IO.puts("test #{inspect(t)}")
t
end end
end end
@ -11,7 +25,7 @@ end
defmodule Test2 do defmodule Test2 do
require Test require Test
def test(), do: false Test.createfuncBase :lol do
true -> :test
Test.createfuncBase(:test) else :test1 end
end end