Packages

The implementation of the `Agent` backing up any container implementing `Access`. The default one is map. All the handlers are exposed to ease the implementation.

Current section

3 Versions

Jump to

Compare versions

6 files changed
+142 additions
-9 deletions
  @@ -1,6 +1,6 @@
1 - # ![Logo](/stuff/agency-48x48.png?raw=true)  Agency
1 + # ![Logo](https://github.com/am-kantox/agency/stuff/agency-48x48.png?raw=true)  Agency
2 2
3 - ![Test](https://github.com/am-kantox/agency/workflows/Test/badge.svg)  **One more unnecessary abstraction on top of `Agent`**
3 + ![Test](https://github.com/am-kantox/agency/workflows/Test/badge.svg)    [![Kantox ❤ OSS](https://img.shields.io/badge/❤-kantox_oss-informational.svg)](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…