Skip to content

dazzlerocks/swarm_dynamic_supervisor

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Swarm.DynamicSupervisor

Hex.pm Version

Supervisor for Swarm registered processes to handle process crashes like regular DynamicSupervisor.

This supervisor acts like regular DynamicSupervisor, however it stores names registered by Swarm in id part of process definition.

For now joining processes to groups after restart don't work! I didn't need this functionality for now, but I have plans to implement it in the future.

Installation

The package can be installed by adding swarm_dynamic_supervisor to your list of dependencies in mix.exs:

def deps do
  [
    {:swarm_dynamic_supervisor, "~> 0.1.0"}
  ]
end

Example

The following example shows a simple case based on example from Swarm documentation, but using Swarm.DynamicSupervisor. The processes will be distributed across the cluster, will be discoverable by name from anywhere in cluster, will be restarted on node failures and will be restarted and registered by the same name on unhandled process crashes (like with regular DynamicSupervisor). After restart it's not determined that the process will restarted on the same node.

You can check it in action in example project.

defmodule MyApp.Supervisor do
  @moduledoc """
  This is the supervisor for the worker processes you wish to distribute
  across the cluster, Swarm is primarily designed around the use case
  where you are dynamically creating many workers in response to events. It
  works with other use cases as well, but that's the ideal use case.
  """
  use Swarm.DynamicSupervisor

  def start_link() do
    Swarm.DynamicSupervisor.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init(_) do
    Swarm.DynamicSupervisor.init(strategy: :one_for_one)
  end

  @doc """
  Registers a new worker, and creates the worker process

  Notice that there is a required field `id` in child_spec. It's used for registering
  name of process in Swarm. You no longer have to call `Swarm.register_name/5`
  explicitly anymore.
  """
  def start_child(name) do
    spec = Supervisor.child_spec(MyApp.Worker, id: name, start: {MyApp.Worker, :start_link, [name]})
    Swarm.DynamicSupervisor.start_child(__MODULE__, spec)
  end
end

defmodule MyApp.Worker do
  @moduledoc """
  This is the worker process, in this case, it simply posts on a
  random recurring interval to stdout.
  """
  use GenServer, restart: :transient

  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

  # crash process for testing
  def handle_call(:crash, _from, _state) do
    raise :crash
  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, delay}, {name, _}) 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
  ...snip...

  @doc """
  Starts worker and registers name in the cluster
  """
  def start_worker(name) do
    {:ok, pid} = MyApp.Supervisor.start_child(name)
  end

  def crash(name) do
    GenServer.call({:via, :swarm, name}, :crash)
  end

  ...snip...
end

TODO

  • Add processes to groups after restart
  • Fix hexdocs

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Elixir 100.0%