Elixir v1.3.0-rc.0 Agent View Source

Agents are a simple abstraction around state.

Often in Elixir there is a need to share or store state that must be accessed from different processes or by the same process at different points in time.

The Agent module provides a basic server implementation that allows state to be retrieved and updated via a simple API.

Examples

For example, in the Mix tool that ships with Elixir, we need to keep a set of all tasks executed by a given project. Since this set is shared, we can implement it with an Agent:

defmodule Mix.TasksServer do
  def start_link do
    Agent.start_link(fn -> MapSet.new end, name: __MODULE__)
  end

  @doc "Checks if the task has already executed"
  def executed?(task, project) do
    item = {task, project}
    Agent.get(__MODULE__, fn set ->
      item in set
    end)
  end

  @doc "Marks a task as executed"
  def put_task(task, project) do
    item = {task, project}
    Agent.update(__MODULE__, &MapSet.put(&1, item))
  end

  @doc "Resets the executed tasks and returns the previous list of tasks"
  def take_all() do
    Agent.get_and_update(__MODULE__, fn set ->
      {Enum.into(set, []), MapSet.new}
    end)
  end
end

Note that agents still provide a segregation between the client and server APIs, as seen in GenServers. In particular, all code inside the function passed to the agent is executed by the agent. This distinction is important because you may want to avoid expensive operations inside the agent, as it will effectively block the agent until the request is fulfilled.

Consider these two examples:

# Compute in the agent/server
def get_something(agent) do
  Agent.get(agent, fn state -> do_something_expensive(state) end)
end

# Compute in the agent/client
def get_something(agent) do
  Agent.get(agent, &(&1)) |> do_something_expensive()
end

The first function blocks the agent. The second function copies all the state to the client and then executes the operation in the client. The difference is whether the data is large enough to require processing in the server, at least initially, or small enough to be sent to the client cheaply.

Name Registration

An Agent is bound to the same name registration rules as GenServers. Read more about it in the GenServer docs.

A word on distributed agents

It is important to consider the limitations of distributed agents. Agents provide two APIs, one that works with anonymous functions and another that expects an explicit module, function, and arguments.

In a distributed setup with multiple nodes, the API that accepts anonymous functions only works if the caller (client) and the agent have the same version of the caller module.

Keep in mind this issue also shows up when performing “rolling upgrades” with agents. By rolling upgrades we mean the following situation: you wish to deploy a new version of your software by shutting down some of your nodes and replacing them with nodes running a new version of the software. In this setup, part of your environment will have one version of a given module and the other part another version (the newer one) of the same module.

The best solution is to simply use the explicit module, function, and arguments APIs when working with distributed agents.

Hot code swapping

An agent can have its code hot swapped live by simply passing a module, function, and args tuple to the update instruction. For example, imagine you have an agent named :sample and you want to convert its inner state from some dict structure to a map. It can be done with the following instruction:

{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

The agent’s state will be added to the given list as the first argument.

Link to this section Summary

Types

The agent reference

The agent name

Return values of start* functions

The agent state

Functions

Performs a cast (fire and forget) operation on the agent state

Performs a cast (fire and forget) operation on the agent state

Gets an agent value via the given function

Gets an agent value via the given function

Gets and updates the agent state in one operation

Gets and updates the agent state in one operation

Starts an agent process without links (outside of a supervision tree)

Starts an agent with the given module function and arguments

Starts an agent linked to the current process with the given function

Starts an agent linked to the current process with the given module function and arguments

Stops the agent with the given reason

Updates the agent state

Link to this section Types

Link to this type agent() View Source
agent() :: pid | {atom, node} | name

The agent reference

Link to this type name() View Source
name() :: atom | {:global, term} | {:via, module, term}

The agent name

Link to this type on_start() View Source
on_start ::
  {:ok, pid} |
  {:error, {:already_started, pid} | term}

Return values of start* functions

Link to this type state() View Source
state() :: term

The agent state

Link to this section Functions

Link to this function cast(agent, fun) View Source
cast(agent, (state -> state)) :: :ok

Performs a cast (fire and forget) operation on the agent state.

The function fun is sent to the agent which invokes the function passing the agent state. The function must return the new state.

Note that cast returns :ok immediately, regardless of whether the destination node or agent exists.

Link to this function cast(agent, module, fun, args) View Source
cast(agent, module, atom, [term]) :: :ok

Performs a cast (fire and forget) operation on the agent state.

Same as cast/2 but a module, function and args are expected instead of an anonymous function. The state is added as first argument to the given list of args.

Link to this function get(agent, fun, timeout \\ 5000) View Source
get(agent, (state -> a), timeout) :: a when a: var

Gets an agent value via the given function.

The function fun is sent to the agent which invokes the function passing the agent state. The result of the function invocation is returned.

A timeout can also be specified (it has a default value of 5000).

Link to this function get(agent, module, fun, args, timeout \\ 5000) View Source
get(agent, module, atom, [term], timeout) :: any

Gets an agent value via the given function.

Same as get/3 but a module, function and args are expected instead of an anonymous function. The state is added as first argument to the given list of args.

Link to this function get_and_update(agent, fun, timeout \\ 5000) View Source
get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var

Gets and updates the agent state in one operation.

The function fun is sent to the agent which invokes the function passing the agent state. The function must return a tuple with two elements, the first being the value to return (i.e. the get value) and the second one is the new state.

A timeout can also be specified (it has a default value of 5000).

Link to this function get_and_update(agent, module, fun, args, timeout \\ 5000) View Source
get_and_update(agent, module, atom, [term], timeout) :: any

Gets and updates the agent state in one operation.

Same as get_and_update/3 but a module, function and args are expected instead of an anonymous function. The state is added as first argument to the given list of args.

Link to this function start(fun, options \\ []) View Source
start((() -> term), GenServer.options) :: on_start

Starts an agent process without links (outside of a supervision tree).

See start_link/2 for more information.

Link to this function start(module, fun, args, options \\ []) View Source
start(module, atom, [any], GenServer.options) :: on_start

Starts an agent with the given module function and arguments.

Similar to start/2 but a module, function and args are expected instead of an anonymous function.

Link to this function start_link(fun, options \\ []) View Source
start_link((() -> term), GenServer.options) :: on_start

Starts an agent linked to the current process with the given function.

This is often used to start the agent as part of a supervision tree.

Once the agent is spawned, the given function is invoked and its return value is used as the agent state. Note that start_link does not return until the given function has returned.

Options

The :name option is used for registration as described in the module documentation.

If the :timeout option is present, the agent is allowed to spend at most the given number of milliseconds on initialization or it will be terminated and the start function will return {:error, :timeout}.

If the :debug option is present, the corresponding function in the :sys module will be invoked.

If the :spawn_opt option is present, its value will be passed as options to the underlying process as in Process.spawn/4.

Return values

If the server is successfully created and initialized, the function returns {:ok, pid}, where pid is the pid of the server. If an agent with the specified name already exists, the function returns {:error, {:already_started, pid}} with the pid of that process.

If the given function callback fails with reason, the function returns {:error, reason}.

Link to this function start_link(module, fun, args, options \\ []) View Source
start_link(module, atom, [any], GenServer.options) :: on_start

Starts an agent linked to the current process with the given module function and arguments.

Same as start_link/2 but a module, function and args are expected instead of an anonymous function.

Link to this function stop(agent, reason \\ :normal, timeout \\ :infinity) View Source
stop(agent, reason :: term, timeout) :: :ok

Stops the agent with the given reason.

It returns :ok if the server terminates with the given reason, if it terminates with another reason, the call will exit.

This function keeps OTP semantics regarding error reporting. If the reason is any other than :normal, :shutdown or {:shutdown, _}, an error report will be logged.

Link to this function update(agent, fun, timeout \\ 5000) View Source
update(agent, (state -> state), timeout) :: :ok

Updates the agent state.

The function fun is sent to the agent which invokes the function passing the agent state. The function must return the new state.

A timeout can also be specified (it has a default value of 5000). This function always returns :ok.

Link to this function update(agent, module, fun, args, timeout \\ 5000) View Source
update(agent, module, atom, [term], timeout) :: :ok

Updates the agent state.

Same as update/3 but a module, function and args are expected instead of an anonymous function. The state is added as first argument to the given list of args.