Current section
Files
Jump to
Current section
Files
lib/agentex.ex
defmodule Agentex do
@moduledoc ~S"""
Distributed `Agent` implementation, providing multi-node agents on top of Mnesia.
"""
use Application
require Logger
@sleep_time 1_000
@default_table Agentex.Simple.Kv
defp nodes do
self = node()
nodes = :agentex
|> Application.get_env(:nodes, [self])
|> Enum.filter(fn
^self -> false
_ -> true
end)
Logger.debug(fn -> "☆#{inspect node()}☆ ⇒ nodes are: #{inspect nodes}" end)
wait_for_nodes = Application.get_env(:agentex, :wait_for_nodes, 30_000)
attempts = Integer.floor_div(wait_for_nodes, @sleep_time) + 1
Enum.any?(1..attempts, fn i ->
Process.sleep(@sleep_time)
Enum.each(nodes, &Node.connect/1)
Logger.debug(fn -> "Attempt ##{i}. Nodes: #{inspect Node.list}" end)
Enum.count(Node.list) == Enum.count(nodes)
end)
nodes = [self | Node.list]
i_am_chuck_norris = case Enum.sort(nodes) do
[^self | _] -> true
_ -> false
end
{:ok, {i_am_chuck_norris, nodes}}
end
defp initialize(preparation) do
Amnesia.stop
Amnesia.Schema.destroy
# if preparation[:drop], do: Mix.Tasks.Amnesia.Drop.run ["-d", Atom.to_string(@database)]
# if preparation[:create], do: Mix.Tasks.Amnesia.Create.run ["-d", Atom.to_string(@database), "--memory"]
{:ok, {i_am_chuck_norris, nodes}} = nodes()
Logger.info(fn -> "★★★ Nodes connected: #{inspect nodes}" end)
if i_am_chuck_norris, do: Logger.warn "☆☆☆ I am Chuck Norris!"
Amnesia.Schema.create(nodes)
database = Application.get_env(:agentex, :database, Agentex.Simple)
if database == Agentex.Simple, do: use(Agentex.DB)
:rpc.multicall(nodes, Amnesia, :start, [])
if i_am_chuck_norris, do: apply(database, :create!, [])
database
end
def start(_type, args) do
import Supervisor.Spec, warn: false
database = initialize(args)
Supervisor.start_link(
Enum.map(apply(database, :tables, []),
&worker(Agentex.Bond, [{database, &1}], id: Module.concat([Agentex, Bond, database, &1]))),
[strategy: :one_for_one, name: Agentex.Supervisor])
end
def stop(_state) do
case nodes() do
{:ok, {true, _}} ->
database = Application.get_env(:agentex, :database, Agentex.Simple)
apply(database, :destroy!, [])
:ok
_ -> :ok
end
end
##############################################################################
@doc ~S"""
Retrieves the value for the given key and optional type.
## Examples
iex> Agentex.put(:pi, 3.14)
iex> Agentex.get!(:pi)
3.14
iex> Agentex.put(:rgb, %{r: 255, g: 0, b: 128})
iex> Agentex.get(:rgb)
%Agentex.Simple.Kv{key: :rgb, value: %{b: 128, g: 0, r: 255}}
iex> Agentex.put(:rgb, %{r: 255, g: 0, b: 128})
iex> Agentex.get(:rgb)
%Agentex.Simple.Kv{key: :rgb, value: %{r: 255, g: 0, b: 128}}
iex> Agentex.put(:rgb, %{r: 255, g: 0, b: 128})
iex> Agentex.get!(:rgb)
%{r: 255, g: 0, b: 128}
iex> Agentex.put(:rrr, [r: 255, r: 0, r: 128])
iex> Agentex.get!(:rrr)
[r: 255, r: 0, r: 128]
iex> Agentex.put(:rrr, [{:r, 255}, {:r, 0}, {:r, 128}])
iex> Agentex.get!(:rrr)
[r: 255, r: 0, r: 128]
"""
def get(name \\ Agentex.Namer.table(@default_table), key)
def get([name], key), do: get(name, key)
def get(name, key), do: Agentex.Bond.get(name, key)
def get!(name \\ Agentex.Namer.table(@default_table), key)
def get!([name], key), do: get!(name, key)
def get!(name, key), do: Agentex.Bond.get!(name, key)
def put(name \\ Agentex.Namer.table(@default_table), key, value)
def put([name], key, value), do: put(name, key, value)
def put(name, key, value), do: Agentex.Bond.put(name, key, value)
##############################################################################
end