class Concurrent::Agent

‘Agent` is inspired by Clojure’s [agent](clojure.org/agents) function. An agent is a shared, mutable variable providing independent, uncoordinated, asynchronous change of individual values. Best used when the value will undergo frequent, complex updates. Suitable when the result of an update does not need to be known immediately. ‘Agent` is (mostly) functionally equivalent to Clojure’s agent, except where the runtime prevents parity.

Agents are reactive, not autonomous - there is no imperative message loop and no blocking receive. The state of an Agent should be itself immutable and the ‘#value` of an Agent is always immediately available for reading by any thread without any messages, i.e. observation does not require cooperation or coordination.

Agent action dispatches are made using the various ‘#send` methods. These methods always return immediately. At some point later, in another thread, the following will happen:

  1. The given ‘action` will be applied to the state of the Agent and the `args`, if any were supplied.

  2. The return value of ‘action` will be passed to the validator lambda, if one has been set on the Agent.

  3. If the validator succeeds or if no validator was given, the return value of the given ‘action` will become the new `#value` of the Agent. See `#initialize` for details.

  4. If any observers were added to the Agent, they will be notified. See ‘#add_observer` for details.

  5. If during the ‘action` execution any other dispatches are made (directly or indirectly), they will be held until after the `#value` of the Agent has been changed.

If any exceptions are thrown by an action function, no nested dispatches will occur, and the exception will be cached in the Agent itself. When an Agent has errors cached, any subsequent interactions will immediately throw an exception, until the agent’s errors are cleared. Agent errors can be examined with ‘#error` and the agent restarted with `#restart`.

The actions of all Agents get interleaved amongst threads in a thread pool. At any point in time, at most one action for each Agent is being executed. Actions dispatched to an agent from another single agent or thread will occur in the order they were sent, potentially interleaved with actions dispatched to the same agent from other sources. The ‘#send` method should be used for actions that are CPU limited, while the `#send_off` method is appropriate for actions that may block on IO.

Unlike in Clojure, ‘Agent` cannot participate in `Concurrent::TVar` transactions.

## Example

“‘ def next_fibonacci(set = nil)

return [0, 1] if set.nil?
set + [set[-2..-1].reduce{|sum,x| sum + x }]

end

# create an agent with an initial value agent = Concurrent::Agent.new(next_fibonacci)

# send a few update requests 5.times do

agent.send{|set| next_fibonacci(set) }

end

# wait for them to complete agent.await

# get the current value agent.value #=> [0, 1, 1, 2, 3, 5, 8] “‘

## Observation

Agents support observers through the {Concurrent::Observable} mixin module. Notification of observers occurs every time an action dispatch returns and the new value is successfully validated. Observation will not occur if the action raises an exception, if validation fails, or when a {#restart} occurs.

When notified the observer will receive three arguments: ‘time`, `old_value`, and `new_value`. The `time` argument is the time at which the value change occurred. The `old_value` is the value of the Agent when the action began processing. The `new_value` is the value to which the Agent was set when the action completed. Note that `old_value` and `new_value` may be the same. This is not an error. It simply means that the action returned the same value.

## Nested Actions

It is possible for an Agent action to post further actions back to itself. The nested actions will be enqueued normally then processed after the outer action completes, in the order they were sent, possibly interleaved with action dispatches from other threads. Nested actions never deadlock with one another and a failure in a nested action will never affect the outer action.

Nested actions can be called using the Agent reference from the enclosing scope or by passing the reference in as a “send” argument. Nested actions cannot be post using ‘self` from within the action block/proc/lambda; `self` in this context will not reference the Agent. The preferred method for dispatching nested actions is to pass the Agent as an argument. This allows Ruby to more effectively manage the closing scope.

Prefer this:

“‘ agent = Concurrent::Agent.new(0) agent.send(agent) do |value, this|

this.send {|v| v + 42 }
3.14

end agent.value #=> 45.14 “‘

Over this:

“‘ agent = Concurrent::Agent.new(0) agent.send do |value|

agent.send {|v| v + 42 }
3.14

end “‘

@!macro agent_await_warning

**NOTE** Never, *under any circumstances*, call any of the "await" methods
({#await}, {#await_for}, {#await_for!}, and {#wait}) from within an action
block/proc/lambda. The call will block the Agent and will always fail.
Calling either {#await} or {#wait} (with a timeout of `nil`) will
hopelessly deadlock the Agent with no possibility of recovery.

@!macro thread_safe_variable_comparison

@see clojure.org/Agents Clojure Agents @see clojure.org/state Values and Change - Clojure’s approach to Identity and State