Current section
3 Versions
Jump to
Current section
3 Versions
Compare versions
6
files changed
+142
additions
-9
deletions
| @@ -1,6 +1,6 @@ | |
| 1 | - # Â Â Agency |
| 1 | + # Â Â Agency |
| 2 2 | |
| 3 | - Â Â **One more unnecessary abstraction on top of `Agent`** |
| 3 | +     [](https://kantox.com/)    **One more unnecessary abstraction on top of `Agent`** |
| 4 4 | |
| 5 5 | ## Introduction |
| 6 6 | |
| @@ -30,5 +30,9 @@ def deps do | |
| 30 30 | end |
| 31 31 | ``` |
| 32 32 | |
| 33 | - ## [Documentation](https://hexdocs.pm/agency). |
| 33 | + ### Changelog |
| 34 34 | |
| 35 | + - **`0.3.1`** `Agency.Multi` support is not optional anymore |
| 36 | + - **`0.3.0`** `Agency.Multi` supporting the locally distributed agency (eliminating `:gen_server` mailbox bottleneck) |
| 37 | + |
| 38 | + ## [Documentation](https://hexdocs.pm/agency). |
| @@ -6,12 +6,18 @@ | |
| 6 6 | {<<"files">>, |
| 7 7 | [<<"stuff">>,<<"stuff/agency-128x128.png">>,<<"stuff/agency-48x48.png">>, |
| 8 8 | <<"stuff/images">>,<<"stuff/images/logo.xcf">>,<<"lib">>, |
| 9 | - <<"lib/scaffold.ex">>,<<"lib/agency">>,<<"lib/agency/impl.ex">>, |
| 10 | - <<"lib/agency.ex">>,<<"mix.exs">>,<<"README.md">>]}. |
| 9 | + <<"lib/scaffold.ex">>,<<"lib/agency.ex">>,<<"lib/agency">>, |
| 10 | + <<"lib/agency/multi.ex">>,<<"lib/agency/default.ex">>, |
| 11 | + <<"lib/agency/impl.ex">>,<<"mix.exs">>,<<"README.md">>]}. |
| 11 12 | {<<"licenses">>,[<<"MIT">>]}. |
| 12 13 | {<<"links">>, |
| 13 14 | [{<<"Docs">>,<<"https://hexdocs.pm/agency">>}, |
| 14 15 | {<<"GitHub">>,<<"https://github.com/am-kantox/agency">>}]}. |
| 15 16 | {<<"name">>,<<"agency">>}. |
| 16 | - {<<"requirements">>,[]}. |
| 17 | - {<<"version">>,<<"0.2.0">>}. |
| 17 | + {<<"requirements">>, |
| 18 | + [[{<<"app">>,<<"libring">>}, |
| 19 | + {<<"name">>,<<"libring">>}, |
| 20 | + {<<"optional">>,false}, |
| 21 | + {<<"repository">>,<<"hexpm">>}, |
| 22 | + {<<"requirement">>,<<"~> 1.0">>}]]}. |
| 23 | + {<<"version">>,<<"0.3.1">>}. |
| @@ -39,6 +39,11 @@ defmodule Agency do | |
| 39 39 | |
| 40 40 | `use Agency` accepts two options: |
| 41 41 | |
| 42 | + - `name: GenServer.name()` the name, this process will be identified by. |
| 43 | + Optional, if not given the only instance of the `Agent` is allowed. |
| 44 | + If given, all the subsequent calls to CRUD functions must include |
| 45 | + this name as the first parameter. In that case, multiple instances |
| 46 | + are allowed |
| 42 47 | - `into: Access.t()` the container to be used by `Agent` |
| 43 48 | - `data: map() | keyword()` the static data to be held by the instances |
| @@ -0,0 +1,5 @@ | |
| 1 | + defmodule Agency.Default do |
| 2 | + @moduledoc false |
| 3 | + |
| 4 | + use Agency |
| 5 | + end |
| @@ -0,0 +1,112 @@ | |
| 1 | + defmodule Agency.Multi do |
| 2 | + @moduledoc """ |
| 3 | + `Supervisor` managing a hashring of agents behind. |
| 4 | + |
| 5 | + It starts a `DynamicSupervisor` that takes care of all the ringed children. |
| 6 | + """ |
| 7 | + |
| 8 | + defmodule Dyno do |
| 9 | + @moduledoc false |
| 10 | + use DynamicSupervisor |
| 11 | + |
| 12 | + @spec start_link(keyword()) :: GenServer.on_start() |
| 13 | + def start_link(opts \\ []), |
| 14 | + do: DynamicSupervisor.start_link(__MODULE__, opts, name: __MODULE__) |
| 15 | + |
| 16 | + @impl true |
| 17 | + def init(_opts), |
| 18 | + do: DynamicSupervisor.init(strategy: :one_for_one) |
| 19 | + end |
| 20 | + |
| 21 | + defmodule Init do |
| 22 | + @moduledoc false |
| 23 | + use GenServer |
| 24 | + |
| 25 | + @spec start_link(keyword()) :: GenServer.on_start() |
| 26 | + def start_link(opts), |
| 27 | + do: GenServer.start_link(__MODULE__, opts, name: __MODULE__) |
| 28 | + |
| 29 | + @impl true |
| 30 | + def init(opts) do |
| 31 | + if Keyword.get(opts, :lazy?, false) do |
| 32 | + {:ok, opts, {:continue, :children}} |
| 33 | + else |
| 34 | + {:noreply, opts} = handle_continue(:children, opts) |
| 35 | + {:ok, opts} |
| 36 | + end |
| 37 | + end |
| 38 | + |
| 39 | + @impl true |
| 40 | + def handle_continue(:children, opts) do |
| 41 | + for i <- 0..opts[:count], i > 0 do |
| 42 | + name = |
| 43 | + opts[:agent_opts] |
| 44 | + |> Keyword.get(:name, opts[:agent]) |
| 45 | + |> Module.concat("Agent_#{i}") |
| 46 | + |
| 47 | + agent_opts = Keyword.put(opts[:agent_opts], :name, name) |
| 48 | + spec = %{id: agent_opts[:name], start: {opts[:agent], :start_link, [agent_opts]}} |
| 49 | + |
| 50 | + DynamicSupervisor.start_child(Dyno, spec) |
| 51 | + HashRing.Managed.add_node(Module.concat(opts[:name], "Ring"), agent_opts[:name]) |
| 52 | + end |
| 53 | + |
| 54 | + {:noreply, opts} |
| 55 | + end |
| 56 | + end |
| 57 | + |
| 58 | + use Supervisor |
| 59 | + |
| 60 | + @type option :: |
| 61 | + {:count, integer()} |
| 62 | + | {:name, atom()} |
| 63 | + | {:agent, module()} |
| 64 | + | {:agent_opts, keyword()} |
| 65 | + @type options :: [option()] |
| 66 | + |
| 67 | + @spec start_link(opts :: options()) :: GenServer.on_start() |
| 68 | + @doc """ |
| 69 | + Starts the supervisor for multy-agency. |
| 70 | + """ |
| 71 | + def start_link(opts \\ []) do |
| 72 | + opts = |
| 73 | + opts |
| 74 | + |> Keyword.put_new(:count, 0) |
| 75 | + |> Keyword.put_new(:name, __MODULE__) |
| 76 | + |> Keyword.put_new(:agent, Agency.Default) |
| 77 | + |> Keyword.put_new(:agent_opts, []) |
| 78 | + |
| 79 | + Supervisor.start_link(__MODULE__, opts, name: opts[:name]) |
| 80 | + end |
| 81 | + |
| 82 | + @impl true |
| 83 | + @doc false |
| 84 | + def init(opts) do |
| 85 | + ring = Module.concat(opts[:name], "Ring") |
| 86 | + {:ok, _pid} = HashRing.Managed.new(ring) |
| 87 | + Process.put(:ring, ring) |
| 88 | + Supervisor.init([{Dyno, []}, {Init, opts}], strategy: :rest_for_one) |
| 89 | + end |
| 90 | + |
| 91 | + Enum.each(Agency.__functions__(), fn {fun, arity} -> |
| 92 | + args = Macro.generate_arguments(arity, __MODULE__) |
| 93 | + |
| 94 | + @doc "Routes the call to the proper agent based on hashring" |
| 95 | + def unquote(fun)(unquote_splicing(args)) do |
| 96 | + [key | _] = args = [unquote_splicing(args)] |
| 97 | + |
| 98 | + :ring |
| 99 | + |> Process.get(Module.concat(__MODULE__, "Ring")) |
| 100 | + |> HashRing.Managed.key_to_node(key) |
| 101 | + |> case do |
| 102 | + {:error, {:invalid_ring, :no_nodes}} -> |
| 103 | + unquote(fun)(unquote_splicing(args)) |
| 104 | + |
| 105 | + agent -> |
| 106 | + agent_module = agent |> Module.split() |> Enum.split(-1) |> elem(0) |> Module.concat() |
| 107 | + |
| 108 | + apply(agent_module, unquote(fun), [agent | args]) |
| 109 | + end |
| 110 | + end |
| 111 | + end) |
| 112 | + end |
Loading more files…