Skip to content

Instantly share code, notes, and snippets.

@cr0t
Last active July 22, 2023 20:35
Show Gist options
  • Save cr0t/9ab28887f3378f9085cd9b0b9f8555e8 to your computer and use it in GitHub Desktop.
Save cr0t/9ab28887f3378f9085cd9b0b9f8555e8 to your computer and use it in GitHub Desktop.
Clusty: Auto-clustering Elixir micro-application (a demo of `libcluster` and `:pg`)
defmodule Clusty.Application do
use Application
@topologies local_epmd: [strategy: Cluster.Strategy.LocalEpmd]
@impl true
def start(_type, _args) do
children = [
{Cluster.Supervisor, [@topologies, [name: ClusterSupervisor]]},
Pidgy
]
opts = [strategy: :one_for_one, name: Clusty.Supervisor]
Supervisor.start_link(children, opts)
end
end

Run 2, 3, or more sessions in separate terminals:

iex --sname socrates --cookie monster -S mix
iex --sname plato --cookie monster -S mix
iex --sname aristotle --cookie monster -S mix

All the nodes will connect to each other automatically (thanks to libcluster and LocalEpmd strategy). After that we can "play" and send messages to the processes (with Pidgy.append/1). We can check all processes' states with Pidgy.all_states/1:

iex-aristotle> Pidgy.all_states()
[{#PID<22291.236.0>, []}, {#PID<22292.226.0>, []}, {#PID<0.229.0>, []}]
iex-aristotle> Pidgy.append(100_500)
:ok
iex-aristotle> Pidgy.all_states()
[
  {#PID<22291.236.0>, [100500]},
  {#PID<22292.226.0>, [100500]},
  {#PID<0.229.0>, []}
]

In the other terminal sessions we can also run Pidgy.all_states/1 or Pidgy.append/1.

defmodule Clusty.MixProject do
use Mix.Project
def project do
[
app: :clusty,
version: "0.1.0",
elixir: "~> 1.15",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger, :runtime_tools, :observer, :wx],
mod: {Clusty.Application, []}
]
end
defp deps do
[
{:libcluster, "~> 3.3.3"},
]
end
end
defmodule Pidgy do
use GenServer
@scope __MODULE__
@group :demo
###
### API
###
def pids(),
do: :pg.get_members(@scope, @group)
def all_states(),
do: Enum.map(pids(), &{&1, GenServer.call(&1, :get_state)})
# Send a message to everyone in the cluster (except ourselves)
def append(some_item) do
:pg.get_members(@scope, @group)
|> Kernel.--(:pg.get_local_members(@scope, @group))
|> Enum.each(fn pid ->
send(pid, {:broadcast, @group, {:append, some_item}})
end)
end
def start_link(opts \\ []),
do: GenServer.start_link(__MODULE__, opts)
###
### GenServer's Kitchen
###
def init(_) do
with {:ok, _pid} = :pg.start(@scope),
:ok = :pg.join(@scope, @group, self()) do
{:ok, []}
end
end
def handle_info({:broadcast, @group, {:append, item}}, state),
do: {:noreply, [item | state]}
def handle_call(:get_state, _from, state),
do: {:reply, state, state}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment