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

Concurrent::Agent::ValidationError

Error message

Concurrent::Agent::ValidationError

What it means

`Agent#restart(new_value, opts)` is the recovery path for a failed agent: it first raises `Error('agent is not failed')` unless `failed?`, then validates `new_value` with the agent's `:validate` proc via `ns_validate`, raising Concurrent::Agent::ValidationError when the value is rejected — leaving the agent failed. The validator is the same proc supplied at creation (`Concurrent::Agent.new(0, validate: -> v { v.is_a?(Integer) })`); it is not exposed publicly, so keep your own reference if you need to pre-check values.

Source

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

    # 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
      # @return [Boolean] true

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Pass a value that satisfies the agent's validator — e.g. `agent.restart(Integer(params[:count]))` instead of the raw string.
  2. Keep the validator proc in a constant/reusable lambda, and call it yourself before `restart` to fail fast with a better message.
  3. If the value contract changed, fix the restart payload at its source; relaxing `:validate` requires creating the agent anew.
  4. Guard with `agent.failed?` first so you do not hit the sibling 'agent is not failed' error.

Example fix

// before
agent.restart(params[:count]) # String from a web form -> ValidationError

// after
agent.restart(Integer(params[:count]))
Defensive patterns

Strategy: try-catch

Validate before calling

# keep the validator reusable at creation time
VALID = -> v { v.is_a?(Integer) }
agent = Concurrent::Agent.new(0, validate: VALID)
# before restarting:
raise ArgumentError, 'invalid restart value' unless agent.failed? && VALID.call(new_value)

Try / catch

begin
  agent.restart(new_value)
rescue Concurrent::Agent::ValidationError
  agent.restart(0) # known-valid fallback satisfying the validator
rescue Concurrent::Agent::Error
  # agent was not failed; nothing to restart
end

Prevention

When it happens

Trigger: `agent.restart('oops')` on an agent created with `validate: -> v { v.is_a?(Integer) }` after it entered a failed state; restarting with a value whose shape differs from the initial one (nil for a numeric-validated agent); restart values taken raw from form params or parsed JSON.

Common situations: Error-recovery code that restores a hardcoded default without honoring the validator; validators tightened later so previously accepted restart values now fail; restart payloads built from user input.

Related errors


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