swarm icon indicating copy to clipboard operation
swarm copied to clipboard

Update README.md example

Open bemesa21 opened this issue 3 years ago • 1 comments

Since Supervisor.Spec is deprecated I think that the documentation example could be updated. This PR updates the supervisor example on README.md, now DynamicSupervisor and child specifications are used.

Also there was a bug in the example, in the callback {:swarm, :end_handoff, state}. The state in the example is a tuple {name, delay} and the callback was sending back a wrong tuple as state: {name, state}

bemesa21 avatar Jan 19 '21 22:01 bemesa21

@bemesa21 I ended up updating the example before I saw your PR, however I used a plain Supervisor instead of a DynamicSupervisor. In this case I think Supervisor is correct because a new supervisor process is being spawned somewhere on the cluster, supervising a worker that is (or may be) transient. But I might be missing something... is there a reason you went for the DynamicSupervisor?

Here's my version, including your fix for {:swarm, :end_handoff, state}:

defmodule MyApp.Supervisor do
  use Supervisor

  def start_link(name) do
    Supervisor.start_link(__MODULE__, name, name: name)
  end

  def init(name) do
    children = [
      {MyApp.Worker, [name]}
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end

defmodule MyApp.Worker do
  use GenServer, restart: :transient

  @moduledoc """
  This is the worker process, in this case, it simply posts on a
  random recurring interval to stdout.
  """
  def start_link(name) do
    GenServer.start_link(__MODULE__, [name])
  end

  def init([name]) do
    {:ok, {name, :rand.uniform(5_000)}, 0}
  end

  # called when a handoff has been initiated due to changes
  # in cluster topology, valid response values are:
  #
  #   - `:restart`, to simply restart the process on the new node
  #   - `{:resume, state}`, to hand off some state to the new process
  #   - `:ignore`, to leave the process running on its current node
  #
  def handle_call({:swarm, :begin_handoff}, _from, {name, delay}) do
    {:reply, {:resume, delay}, {name, delay}}
  end

  # called after the process has been restarted on its new node,
  # and the old process' state is being handed off. This is only
  # sent if the return to `begin_handoff` was `{:resume, state}`.
  # **NOTE**: This is called *after* the process is successfully started,
  # so make sure to design your processes around this caveat if you
  # wish to hand off state like this.
  def handle_cast({:swarm, :end_handoff, {name, delay}}, _state) do
    {:noreply, {name, delay}}
  end

  # called when a network split is healed and the local process
  # should continue running, but a duplicate process on the other
  # side of the split is handing off its state to us. You can choose
  # to ignore the handoff state, or apply your own conflict resolution
  # strategy
  def handle_cast({:swarm, :resolve_conflict, _delay}, state) do
    {:noreply, state}
  end

  def handle_info(:timeout, {name, delay}) do
    IO.puts("#{inspect(name)} says hi!")
    Process.send_after(self(), :timeout, delay)
    {:noreply, {name, delay}}
  end

  # this message is sent when this process should die
  # because it is being moved, use this as an opportunity
  # to clean up
  def handle_info({:swarm, :die}, state) do
    {:stop, :shutdown, state}
  end
end

defmodule MyApp.ExampleUsage do
  @doc """
  Starts worker and registers name in the cluster, then joins the process
  to the `:foo` group
  """
  def start_worker(name) do
    # name, m, f, a, timeout \\ inf
    {:ok, pid} = Swarm.register_name(name, MyApp.Supervisor, :start_link, [name])
    Swarm.join(:foo, pid)
  end

  @doc """
  Gets the pid of the worker with the given name
  """
  def get_worker(name), do: Swarm.whereis_name(name)

  @doc """
  Gets all of the pids that are members of the `:foo` group
  """
  def get_foos(), do: Swarm.members(:foo)

  @doc """
  Call some worker by name
  """
  def call_worker(name, msg), do: GenServer.call({:via, :swarm, name}, msg)

  @doc """
  Cast to some worker by name
  """
  def cast_worker(name, msg), do: GenServer.cast({:via, :swarm, name}, msg)

  @doc """
  Publish a message to all members of group `:foo`
  """
  def publish_foos(msg), do: Swarm.publish(:foo, msg)

  @doc """
  Call all members of group `:foo` and collect the results,
  any failures or nil values are filtered out of the result list
  """
  def call_foos(msg), do: Swarm.multi_call(:foo, msg)
end

joeapearson avatar Feb 22 '21 19:02 joeapearson