README.md

# Swarm

This project is an attempt at a smart distributed registry, which automatically
shifts processes around based on changes to cluster topology. It is designed
for Elixir and Erlang apps built on OTP conventions.

## Installation

```elixir
defp deps do
  [{:swarm, "~> 0.1.0"}]
end
```

## Features

- automatic cluster formation/healing based on gossip
  via UDP, using a configurable port/multicast address
- automatic distribution of registered processes across
  the cluster based on a consistent hashing algorithm,
  where names are partitioned across nodes based on their hash.
- easy handoff of processes between one node and another, including
  handoff of current process state. You may indicate whether the
  handoff should simply restart the process on the new node, start
  the process and then send it the handoff message containing state,
  or ignore the handoff and remain on it's current node.
- can do simple registration with `{:via, :swarm, name}`
- both an Erlang and Elixir API

## Restrictions

- auto-balancing of processes in the cluster require registrations be done via
  `register_name/4`, which takes module/function/args params, and handles starting
  the process for you. The MFA must return `{:ok, pid}` or a plain pid.
  This is how `swarm` handles process handoff between nodes, and automatic restarts when nodedown
  events occur and the cluster topology changes.

## Example

The following example shows a simple case where workers are dynamically created in response
to some events under a supervisor, and we want them to be distributed across the cluster and
be discoverable by name from anywhere in the cluster. Swarm is a perfect fit for this
situation.

```elixir
defmodule MyApp.WorkerSup 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 Supervisor

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

  def init(_) do
    children = [
      worker(MyApp.Worker, [], restart: :transient)
    ]
    supervise(children, strategy: :simple_one_for_one)
  end

  @doc """
  Registers a new worker, and creates the worker process
  """
  def register(worker_args) when is_list(worker_args) do
    {:ok, _pid} = Supervisor.start_child(__MODULE__, worker_args)
  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.
  """
  def start_link([name: name]), do: GenServer.start_link(__MODULE__, [name])
  def init(name), do: {:ok, {name, :rand.uniform(5_000)}, 0}

  # called when a handoff has been initiated due to changes
  # in cluster topology
  def handle_call({:swarm, :begin_handoff}, {name, delay}) do
    {:reply, {:resume, delay}, {name, delay}}
  end
  # called after the process has been restarted and state
  # is being handed off to the new process, this is only
  # sent if the return to `begin_handoff` was `{:resume, state}`.
  def handle_call({:swarm, :end_handoff, delay}, {name, _}) do
    {:reply, :ok, {name, delay}}
  end
  def handle_call(_, _, state), do: {:noreply, state}

  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
  # so that it may shutdown cleanly
  def handle_info({:swarm, :die}, state) do
    {:stop, :shutdown, state}
  end
  def handle_info(_, state), do: {:noreply, state}
end

defmodule MyApp.Listener do
  ...snip...
  def start_worker(name) do
    # Starts worker and registers name in the cluster
    {:ok, pid} = Swarm.register(name, MyApp.Supervisor, :register, [name: name])
    # Registers some metadata to be associated with the worker
    Swarm.join(:foo, pid)
  end
  # Gets the pid of the worker with the given name
  def get_worker(name) do
    Swarm.whereis_name(name)
  end
  # Gets all of the pids associated with workers with the given property
  def get_foos() do
    Swarm.members(:foo)
  end
  def call_worker(name, msg) do
    GenServer.call({:via, :swarm, name}, msg)
  end
  def send_worker(name, msg) do
    GenServer.cast({:via, :swarm, name}, msg)
  end
  def publish_foos(msg) do
    Swarm.publish(:foo, msg)
  end
  def call_foos(msg) do
    Swarm.multicall(:foo, msg)
  end
  ...snip...
end
```

## License

MIT

## TODO

- testing
- documentation