ruby-concurrency/concurrent-ruby · error · Concurrent::Agent::Error

agent is not failed

Error message

agent is not failed

What it means

Agent#restart is the recovery path for an agent in :fail error mode whose action raised: it replaces the value, optionally clears queued actions, and resumes processing. Restoring a healthy agent is meaningless (its state is live and actions are flowing), so restart raises Agent::Error unless failed? is true.

Source

Thrown at lib/concurrent-ruby/concurrent/agent.rb:427

    # then un-fails the Agent so that action dispatches are allowed again. If
    # the `:clear_actions` option is give and true, any actions queued on the
    # Agent that were being held while it was failed will be discarded,
    # otherwise those held actions will proceed. The `new_value` must pass the
    # validator if any, or `restart` will raise an exception and the Agent will
    # remain failed with its old {#value} and {#error}. Observers, if any, will
    # not be notified of the new state.
    #
    # @param [Object] new_value the new value for the Agent once restarted
    # @param [Hash] opts the configuration options
    # @option opts [Symbol] :clear_actions true if all enqueued but unprocessed
    #   actions should be discarded on restart, else false (default: false)
    # @return [Boolean] true
    #
    # @raise [Concurrent:AgentError] when not failed
    def restart(new_value, opts = {})
      clear_actions = opts.fetch(:clear_actions, false)
      synchronize do
        raise Error.new('agent is not failed') unless failed?
        raise ValidationError unless ns_validate(new_value)
        @current.value = new_value
        @error.value   = nil
        @queue.clear if clear_actions
        ns_post_next_job unless @queue.empty?
      end
      true
    end

    class << self

      # Blocks the current thread (indefinitely!) until all actions dispatched
      # thus far to all the given Agents, from this thread or nested by the
      # given Agents, have occurred. Will block when any of the agents are
      # failed. Will never return if a failed Agent is restart with
      # `:clear_actions` true.
      #
      # @param [Array<Concurrent::Agent>] agents the Agents on which to wait

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Guard the call: agent.restart(new_value) if agent.failed?.
  2. In supervision loops, re-check failed? each cycle and restart at most once per observed failure.
  3. If you only want to change a healthy agent's value, use the normal update path (agent << new_value or agent.send { |old| ... }) - restart is not a setter.

Example fix

# before
agent.restart(fresh_value)   # raises unless the agent is failed

# after
if agent.failed?
  agent.restart(fresh_value, clear_actions: true)
else
  agent << fresh_value       # normal state change
end
Defensive patterns

Strategy: validation

Validate before calling

agent.restart(new_value, clear_actions: true) if agent.failed?

Try / catch

begin
  agent.restart(new_value)
rescue Concurrent::Agent::Error
  agent << new_value   # healthy agent: normal state change instead
end

Prevention

When it happens

Trigger: agent.restart(value) on an agent that never failed, or on one that already restarted - e.g. an unconditional restart in a supervision/recovery loop, or two threads that both observed the failure and both call restart (the second raises).

Common situations: Defensive supervision code calling restart 'just in case' each cycle; retry loops that restart on every iteration although only the first call is valid; races between multiple monitors of the same agent.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21). Data as JSON: /api/errors/7d4fe058f9addb98. Report an issue: GitHub.