From 0d4ff31916b5d8c9b757d7070253b93a6a6724cf Mon Sep 17 00:00:00 2001 From: cbishopvelti Date: Sun, 14 Dec 2025 11:03:04 +0000 Subject: [PATCH 1/8] changed pg2 to pg --- lib/squabble/pg.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/squabble/pg.ex b/lib/squabble/pg.ex index d03177b..b5a8df8 100644 --- a/lib/squabble/pg.ex +++ b/lib/squabble/pg.ex @@ -10,8 +10,7 @@ defmodule Squabble.PG do """ @spec join() :: :ok def join() do - :ok = :pg2.create(@key) - :ok = :pg2.join(@key, self()) + :ok = :pg.join(@key, self()) end @doc """ @@ -35,7 +34,7 @@ defmodule Squabble.PG do """ @spec members() :: [pid()] def members() do - :pg2.get_members(:squabble) + :pg.get_members(:squabble) end @doc """ From 2cdb18865b14ff35b76f991bd37b9cc04748e0b6 Mon Sep 17 00:00:00 2001 From: cbishopvelti Date: Sun, 14 Dec 2025 11:25:06 +0000 Subject: [PATCH 2/8] fixed state.size --- lib/squabble/server.ex | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/squabble/server.ex b/lib/squabble/server.ex index b658657..59035f8 100644 --- a/lib/squabble/server.ex +++ b/lib/squabble/server.ex @@ -82,9 +82,10 @@ defmodule Squabble.Server do "Starting an election for term #{term}, announcing candidacy" end, type: :squabble) + case check_term_newer(state, term) do {:ok, :newer} -> - if state.size == 1 do + if length(PG.members()) == 1 do voted_leader(state, 1) else PG.broadcast(fn pid -> @@ -305,7 +306,11 @@ defmodule Squabble.Server do """ @spec check_majority_votes(State.t()) :: {:ok, :majority} | {:error, :not_enough} def check_majority_votes(state) do - case length(state.votes) >= state.size / 2 do + + current_size = length(PG.members()) + current_size = if current_size < 1, do: 1, else: current_size + + case length(state.votes) >= current_size / 2 do true -> {:ok, :majority} From 46e05d759e910e6310281efd7a186e2672a7ddb6 Mon Sep 17 00:00:00 2001 From: cbishopvelti Date: Sun, 14 Dec 2025 11:56:12 +0000 Subject: [PATCH 3/8] SeCall not_leader callback --- lib/squabble/leader.ex | 2 ++ lib/squabble/server.ex | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/lib/squabble/leader.ex b/lib/squabble/leader.ex index 394d366..17d5e0d 100644 --- a/lib/squabble/leader.ex +++ b/lib/squabble/leader.ex @@ -14,4 +14,6 @@ defmodule Squabble.Leader do A node went down, callback from the squabble leader """ @callback node_down() :: :ok + + @callback not_leader(election_term()) :: :ok end diff --git a/lib/squabble/server.ex b/lib/squabble/server.ex index 59035f8..48f5470 100644 --- a/lib/squabble/server.ex +++ b/lib/squabble/server.ex @@ -246,6 +246,10 @@ defmodule Squabble.Server do {:ok, state} _ -> + Enum.each(winner_subscriptions(state), fn module -> + module.not_leader(state.term) + end) + {:ok, state} end end From 4c5d2955c424554355b9f4e65f83c7897c63f4a3 Mon Sep 17 00:00:00 2001 From: cbishopvelti Date: Sun, 14 Dec 2025 12:13:26 +0000 Subject: [PATCH 4/8] Added not_leader callback --- lib/squabble/server.ex | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/squabble/server.ex b/lib/squabble/server.ex index 48f5470..ecf870d 100644 --- a/lib/squabble/server.ex +++ b/lib/squabble/server.ex @@ -188,6 +188,14 @@ defmodule Squabble.Server do :ets.insert(@key, {:is_leader?, false}) + if leader_node != node() do + Enum.each(winner_subscriptions(state), fn module -> + if function_exported?(module, :not_leader, 1) do + module.not_leader(term) + end + end) + end + state = state |> Map.put(:term, term) @@ -246,10 +254,6 @@ defmodule Squabble.Server do {:ok, state} _ -> - Enum.each(winner_subscriptions(state), fn module -> - module.not_leader(state.term) - end) - {:ok, state} end end @@ -389,4 +393,9 @@ defmodule Squabble.Server do {:ok, state} end end + + @impl true + def not_leader(_term) do + :ok + end end From 2343652f7fed0d273583bb5d69b656073036bdb0 Mon Sep 17 00:00:00 2001 From: cbishopvelti Date: Sun, 14 Dec 2025 12:19:54 +0000 Subject: [PATCH 5/8] Added not_leader callback 2 --- lib/squabble/server.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/squabble/server.ex b/lib/squabble/server.ex index ecf870d..a2e2880 100644 --- a/lib/squabble/server.ex +++ b/lib/squabble/server.ex @@ -106,6 +106,12 @@ defmodule Squabble.Server do "Someone already won this round, not starting" end, type: :squabble) + Enum.each(winner_subscriptions(state), fn module -> + if function_exported?(module, :not_leader, 1) do + module.not_leader(state.term) + end + end) + {:ok, state} {:error, :older} -> From f53efc9d6415360918c2d4d9e43107d5a054d149 Mon Sep 17 00:00:00 2001 From: cbishopvelti Date: Sun, 14 Dec 2025 12:32:12 +0000 Subject: [PATCH 6/8] Added startup callback --- lib/squabble/leader.ex | 8 ++++++++ lib/squabble/server.ex | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/squabble/leader.ex b/lib/squabble/leader.ex index 17d5e0d..e316249 100644 --- a/lib/squabble/leader.ex +++ b/lib/squabble/leader.ex @@ -15,5 +15,13 @@ defmodule Squabble.Leader do """ @callback node_down() :: :ok + @doc """ + Callback if we're not the leader + """ @callback not_leader(election_term()) :: :ok + + @doc""" + Callback on startup + """ + @callback startup(election_term()) :: :ok end diff --git a/lib/squabble/server.ex b/lib/squabble/server.ex index a2e2880..d49a1ab 100644 --- a/lib/squabble/server.ex +++ b/lib/squabble/server.ex @@ -56,6 +56,12 @@ defmodule Squabble.Server do Squabble.leader_check(pid) end) + Enum.each(winner_subscriptions(state), fn module -> + if function_exported?(module, :startup, 1) do + module.startup(state.term) + end + end) + {:ok, state} end @@ -119,6 +125,14 @@ defmodule Squabble.Server do "This term has already completed, not starting" end, type: :squabble) + if state.state != "leader" do + Enum.each(winner_subscriptions(state), fn module -> + if function_exported?(module, :not_leader, 1) do + module.not_leader(state.term) + end + end) + end + {:ok, state} end end @@ -404,4 +418,8 @@ defmodule Squabble.Server do def not_leader(_term) do :ok end + @impl true + def startup(_term) do + :ok + end end From 6952270ac444023a9f0b1f93cfdb830f1da0aad5 Mon Sep 17 00:00:00 2001 From: cbishopvelti Date: Sun, 14 Dec 2025 12:58:37 +0000 Subject: [PATCH 7/8] Added startup callback 3 --- lib/squabble.ex | 4 ++++ lib/squabble/server.ex | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/squabble.ex b/lib/squabble.ex index 3db9d6f..16d1866 100644 --- a/lib/squabble.ex +++ b/lib/squabble.ex @@ -90,6 +90,10 @@ defmodule Squabble do size = Keyword.get(opts, :size, 1) subscriptions = Keyword.get(opts, :subscriptions, []) + Enum.each(subscriptions, fn module -> + Code.ensure_loaded(module) + end) + state = %State{ state: "candidate", size: size, diff --git a/lib/squabble/server.ex b/lib/squabble/server.ex index d49a1ab..bad4cb2 100644 --- a/lib/squabble/server.ex +++ b/lib/squabble/server.ex @@ -57,10 +57,10 @@ defmodule Squabble.Server do end) Enum.each(winner_subscriptions(state), fn module -> - if function_exported?(module, :startup, 1) do - module.startup(state.term) - end - end) + if function_exported?(module, :startup, 1) do + module.startup(state.term) + end + end) {:ok, state} end From cf5b68e0c3965a6039b216816eb36bad58e846b8 Mon Sep 17 00:00:00 2001 From: cbishopvelti Date: Sun, 14 Dec 2025 14:15:52 +0000 Subject: [PATCH 8/8] updated readme --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bdf738d..b877b48 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,14 @@ In order to connect multiple nodes you should also set up [libcluster](https://g ## Configuration -Configure Squabble when you start the worker in your supervision tree. This should go _after_ `libcluster` if you're using that. All nodes should be connected before starting Squabble. +Configure Squabble when you start the worker in your supervision tree. This should go _after_ `:pg` and `libcluster` if you're using that. All nodes should be connected before starting Squabble. ```elixir children = [ + %{ + id: :pg, + start: {:pg, :start_link, []} + }, {Squabble, [subscriptions: [MyApp.Leader], size: 1]} ] ``` @@ -52,5 +56,13 @@ defmodule MyApp.Leader do @impl true def node_down() do end + + @impl true + def startup() do + end + + @impl true + def not_leader() do + end end ```