Current section
Files
Jump to
Current section
Files
lib/manifold/partitioner.ex
defmodule Manifold.Partitioner do
use GenServer
require Logger
alias Manifold.{Worker, Utils}
@gen_module Application.get_env(:manifold, :gen_module, GenServer)
## Client
@spec child_spec(Keyword.t) :: tuple
def child_spec(partitions, opts \\ []) do
import Supervisor.Spec, warn: false
supervisor(__MODULE__, [partitions, opts], id: Keyword.get(opts, :name, __MODULE__))
end
@spec start_link(Number.t, Keyword.t) :: GenServer.on_start
def start_link(partitions, opts \\ []) do
GenServer.start_link(__MODULE__, partitions, opts)
end
@spec send(partitioner :: GenServer.server(), pids :: [pid()], message :: term()) :: :ok
def send(partitioner, pids, message) do
@gen_module.cast(partitioner, {:send, pids, message})
end
## Server Callbacks
def init(partitions) do
# Set optimal process flags
Process.flag(:trap_exit, true)
Process.flag(:message_queue_data, :off_heap)
workers = for _ <- 0..partitions do
{:ok, pid} = Worker.start_link()
pid
end
schedule_next_hibernate()
{:ok, List.to_tuple(workers)}
end
def terminate(_reason, _state), do: :ok
def handle_call(:which_children, _from, state) do
children = for pid <- Tuple.to_list(state), is_pid(pid) do
{:undefined, pid, :worker, [Worker]}
end
{:reply, children, state}
end
def handle_call(:count_children, _from, state) do
{:reply, [
specs: 1,
active: tuple_size(state),
supervisors: 0,
workers: tuple_size(state)
], state}
end
def handle_call(_message, _from, state) do
{:reply, :error, state}
end
# Specialize handling cast to a single pid.
def handle_cast({:send, [pid], message}, state) do
partition = Utils.partition_for(pid, tuple_size(state))
Worker.send(elem(state, partition), [pid], message)
{:noreply, state}
end
def handle_cast({:send, pids, message}, state) do
partitions = tuple_size(state)
pids_by_partition = Utils.partition_pids(pids, partitions)
do_send(message, pids_by_partition, state, 0, partitions)
{:noreply, state}
end
def handle_cast(_message, state) do
{:noreply, state}
end
def handle_info({:EXIT, pid, reason}, state) do
Logger.warn "manifold worker exited: #{inspect reason}"
state = state
|> Tuple.to_list
|> Enum.map(fn
^pid -> Worker.start_link()
pid -> pid
end)
|> List.to_tuple
{:noreply, state}
end
def handle_info(:hibernate, state) do
schedule_next_hibernate()
{:noreply, state, :hibernate}
end
def handle_info(_message, state) do
{:noreply, state}
end
defp do_send(_message, _pids_by_partition, _workers, partitions, partitions), do: :ok
defp do_send(message, pids_by_partition, workers, partition, partitions) do
pids = elem(pids_by_partition, partition)
if pids != [] do
Worker.send(elem(workers, partition), pids, message)
end
do_send(message, pids_by_partition, workers, partition + 1, partitions)
end
defp schedule_next_hibernate() do
Process.send_after(self(), :hibernate, Utils.next_hibernate_delay())
end
end