Packages

Mostly-transparent batching of remote messages in Erlang/Elixir cluster

Current section

Files

Jump to
batched_communication lib batched_communication.ex
Raw

lib/batched_communication.ex

use Croma
defmodule BatchedCommunication do
@moduledoc File.read!(Path.join([__DIR__, "..", "README.md"])) |> String.replace_prefix("# BatchedCommunication\n\n", "")
import Kernel, except: [send: 2]
alias BatchedCommunication.{Compression, Sender}
@type dest :: pid | atom | {atom, node}
@type message :: any
@type reply :: any
@doc """
Sends an asynchronous message to the given destination process.
When you want to batch multiple messages to the same destination node,
you can use this function as a replacement for `GenServer.cast/2`.
"""
defun cast(dest :: dest, msg :: message) :: :ok do
send(dest, {:"$gen_cast", msg})
end
@doc """
Makes a synchronous request to the given destination process.
When you want to batch multiple messages to the same destination node,
you can use this function as a replacement for `GenServer.call/3`.
"""
defun call(dest :: dest, msg :: message, t :: timeout | {:clean_timeout, timeout} | {:dirty_timeout, timeout} \\ 5000) :: reply do
ref = Process.monitor(dest)
try do
send(dest, {:"$gen_call", {self(), ref}, msg})
rescue
ArgumentError -> :ok # no process found for the name
end
receive do
{^ref, reply} -> Process.demonitor(ref, [:flush]); reply
{:DOWN, ^ref, _, _, :noconnection} -> exit({{:nodedown, get_node(dest)}, {__MODULE__, :call, [dest, msg, t]}})
{:DOWN, ^ref, _, _, reason} -> exit({reason, {__MODULE__, :call, [dest, msg, t]}})
after
timeout(t) ->
Process.demonitor(ref, [:flush])
exit({:timeout, {__MODULE__, :call, [dest, msg, t]}})
end
end
defp get_node({_name, node}), do: node
defp get_node(p) when is_pid(p), do: node(p)
defp timeout({:dirty_timeout, t}), do: t
defp timeout({:clean_timeout, t}), do: t
defp timeout(t ), do: t
@doc """
Sends a reply to a client that has sent a synchronous request.
When you want to batch multiple messages to the same destination node,
you can use this function as a replacement for `GenServer.reply/2`.
"""
defun reply({pid, tag} :: {pid, reference}, reply :: reply) :: :ok do
send(pid, {tag, reply})
end
@doc """
Sends `message` to the destination process `dest` with a batching mechanism.
"""
defun send(dest :: dest, message :: message) :: :ok do
case dest do
a when is_atom(a) -> Kernel.send(a, message)
{a, n} when n == node() -> Kernel.send(a, message)
{a, n} -> Sender.enqueue(n, a, message)
pid when is_pid(pid) ->
case node(pid) do
n when n == node() -> Kernel.send(pid, message)
n -> Sender.enqueue(n, pid, message)
end
end
:ok
end
@type configurations :: %{
max_wait_time: pos_integer,
max_messages_per_batch: pos_integer,
compression: Compression.t,
}
@doc """
Gets the current configurations.
"""
defun get_configurations() :: configurations do
Sender.get_configurations()
end
@doc """
Sets maximum wait time (in milliseconds) before sending messages as a batch.
When a `BatchedCommunication.Sender` process receives a message for a particular destination node,
it starts a timer with the maximum wait time.
When the timer fires the accumulated messages are sent in one batch.
Defaults to `100` milliseconds.
"""
defun change_max_wait_time(time :: g[pos_integer]) :: :ok do
Sender.change_property_in_all_senders(:max_wait_time, time)
end
@doc """
Sets maximum number of messages to accumulate in one batch.
When a `BatchedCommunication.Sender` process has messages more than or equal to this maximum,
it immediately (i.e., without waiting for timer; see also `change_max_wait_time/1`) sends the messages in one batch.
Defaults to `100` messages.
"""
defun change_max_messages_per_batch(max :: g[pos_integer]) :: :ok do
Sender.change_property_in_all_senders(:max_messages_per_batch, max)
end
@doc """
Changes whether to compress each batch of messages or not.
Currently supported values are `:gzip` and `:raw` (no compression).
Defaults to `:gzip`.
"""
defun change_compression(compression :: v[Compression.t]) :: :ok do
Sender.change_property_in_all_senders(:compression, compression)
end
end